feat: 文本框接入样板

This commit is contained in:
unanmed 2024-12-23 23:09:50 +08:00
parent e5e0458891
commit 734980eb6a
12 changed files with 367 additions and 88 deletions

View File

@ -855,7 +855,7 @@ actions.prototype._sys_keyDownCtrl = function () {
core.status.event.id == 'action' && core.status.event.id == 'action' &&
core.status.event.data.type == 'text' core.status.event.data.type == 'text'
) { ) {
core.doAction(); this._clickAction_text();
return true; return true;
} }
if ( if (
@ -1117,16 +1117,25 @@ actions.prototype._clickAction_text = function () {
// 正在淡入淡出的话不执行 // 正在淡入淡出的话不执行
if (core.status.event.animateUI) return; if (core.status.event.animateUI) return;
var data = core.clone(core.status.event.data.current); const Store = Mota.require('module', 'Render').TextboxStore;
if (typeof data == 'string') data = { type: 'text', text: data }; const store = Store.get('main-textbox');
// var data = core.clone(core.status.event.data.current);
// if (typeof data == 'string') data = { type: 'text', text: data };
// 打字机效果显示全部文字 // 打字机效果显示全部文字
if (core.status.event.interval != null) { if (store.typing) {
data.showAll = true; store.endType();
core.insertAction(data);
core.doAction();
return; return;
} else {
store.hide();
} }
// if (core.status.event.interval != null) {
// data.showAll = true;
// core.insertAction(data);
// core.doAction();
// return;
// }
if (!data.code) { if (!data.code) {
core.ui._animateUI('hide', null, core.doAction); core.ui._animateUI('hide', null, core.doAction);

View File

@ -1553,24 +1553,70 @@ 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;
data.text = core.replaceText(data.text, prefix); const Store = Mota.require('module', 'Render').TextboxStore;
var ctx = data.code ? '__text__' + data.code : null; const store = Store.get('main-textbox');
data.ctx = ctx; const { text } = data;
if (core.getContextByName(ctx) && !data.showAll) { let title = '';
core.ui._animateUI('hide', ctx, function () { let inTitle = false;
core.ui.drawTextBox(data.text, data); let titleStartIndex = 0;
core.ui._animateUI('show', ctx, function () { let titleEndIndex = 0;
if (data.async) core.doAction(); for (let i = 0; i < text.length; i++) {
}); const char = text[i];
});
return; if (inTitle) {
} if (char === '\\' && text[i + 1] === ']') {
core.ui.drawTextBox(data.text, data); title += ']';
if (!data.showAll) { i++;
core.ui._animateUI('show', ctx, function () { } else if (char === ']') {
if (data.async) core.doAction(); inTitle = false;
}); titleEndIndex = i + 1;
break;
} else {
title += char;
}
continue;
}
if (char === '\t' && text[i + 1] === '[') {
inTitle = true;
titleStartIndex = i;
// 跳转至方括号内
i++;
continue;
}
if (char === '\\' && text[i + 1] === 't' && text[i + 2] === '[') {
inTitle = true;
titleStartIndex = i;
// 跳转至方括号内
i += 2;
continue;
}
} }
const showTitle =
text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
store.show();
store.modify({ text: showTitle, title });
// data.text = core.replaceText(data.text, prefix);
// var ctx = data.code ? '__text__' + data.code : null;
// data.ctx = ctx;
// if (core.getContextByName(ctx) && !data.showAll) {
// core.ui._animateUI('hide', ctx, function () {
// core.ui.drawTextBox(data.text, data);
// core.ui._animateUI('show', ctx, function () {
// if (data.async) core.doAction();
// });
// });
// return;
// }
// core.ui.drawTextBox(data.text, data);
// if (!data.showAll) {
// core.ui._animateUI('show', ctx, function () {
// if (data.async) core.doAction();
// });
// }
}; };
events.prototype._action_moveTextBox = function (data, x, y, prefix) { events.prototype._action_moveTextBox = function (data, x, y, prefix) {

View File

@ -22,7 +22,7 @@ main.floors.snowShop=
"而且,一共就只有三件装备(" "而且,一共就只有三件装备("
], ],
"7,5": [ "7,5": [
"\t[商店老板,N636]\b[up,7,5]请随意挑选", "\t[商店老板]请随意挑选",
{ {
"type": "openShop", "type": "openShop",
"id": "snowShop", "id": "snowShop",

View File

@ -16,11 +16,11 @@ import { Transform } from '../transform';
import { isSetEqual } from '../utils'; import { isSetEqual } from '../utils';
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
import { Sprite } from '../sprite'; import { Sprite } from '../sprite';
import { onTick } from '../renderer'; import { ContainerProps, 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 EventEmitter from 'eventemitter3';
import { Container } from '../container'; import { Text } from '../preset';
export const enum WordBreak { export const enum WordBreak {
/** 不换行 */ /** 不换行 */
@ -46,7 +46,7 @@ Mota.require('var', 'loading').once('coreInit', () => {
testCanvas.freeze(); testCanvas.freeze();
}); });
export interface TextContentProps { interface TextContentRenderData {
text: string; text: string;
x?: number; x?: number;
y?: number; y?: number;
@ -80,8 +80,14 @@ export interface TextContentProps {
fill?: boolean; fill?: boolean;
/** 是否描边 */ /** 是否描边 */
stroke?: boolean; stroke?: boolean;
/** 是否无视打字机,强制全部显示 */
showAll?: boolean;
} }
export interface TextContentProps
extends ContainerProps,
TextContentRenderData {}
export type TextContentEmits = { export type TextContentEmits = {
typeEnd: () => void; typeEnd: () => void;
typeStart: () => void; typeStart: () => void;
@ -103,9 +109,8 @@ interface TextContentData {
interface TextContentRenderable { interface TextContentRenderable {
x: number; x: number;
y: number;
/** 行高为0时表示两行间为默认行距 */ /** 行高为0时表示两行间为默认行距 */
height: number; lineHeight: number;
/** 这一行文字的高度,即 measureText 算出的高度 */ /** 这一行文字的高度,即 measureText 算出的高度 */
textHeight: number; textHeight: number;
/** 这一行的文字 */ /** 这一行的文字 */
@ -138,7 +143,8 @@ const textContentOptions = {
'fillStyle', 'fillStyle',
'strokeStyle', 'strokeStyle',
'strokeWidth', 'strokeWidth',
'stroke' 'stroke',
'showAll'
], ],
emits: ['typeEnd', 'typeStart'] emits: ['typeEnd', 'typeStart']
} satisfies SetupComponentOptions< } satisfies SetupComponentOptions<
@ -155,7 +161,7 @@ export const TextContent = defineComponent<
if (props.width && props.width <= 0) { if (props.width && props.width <= 0) {
logger.warn(41, String(props.width)); logger.warn(41, String(props.width));
} }
const renderData: Required<TextContentProps> = { const renderData: Required<TextContentRenderData> = shallowReactive({
text: props.text, text: props.text,
textAlign: props.textAlign ?? TextAlign.Left, textAlign: props.textAlign ?? TextAlign.Left,
x: props.x ?? 0, x: props.x ?? 0,
@ -174,8 +180,9 @@ export const TextContent = defineComponent<
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,
}; showAll: props.showAll ?? false
});
const ensureProps = () => { const ensureProps = () => {
for (const [key, value] of Object.entries(props)) { for (const [key, value] of Object.entries(props)) {
@ -242,9 +249,11 @@ export const TextContent = defineComponent<
const time = Date.now(); const time = Date.now();
const char = const char =
Math.floor((time - startTime) / renderData.interval!) + fromChar; Math.floor((time - startTime) / renderData.interval!) + fromChar;
if (!isFinite(char)) { if (!isFinite(char) || renderData.showAll) {
renderable.forEach(v => (v.pointer = v.text.length)); renderable.forEach(v => (v.pointer = v.text.length));
needUpdate = false; needUpdate = false;
linePointer = dirtyIndex.length;
emit('typeEnd');
return; return;
} }
while (linePointer < dirtyIndex.length) { while (linePointer < dirtyIndex.length) {
@ -259,6 +268,7 @@ export const TextContent = defineComponent<
break; break;
} }
} }
if (linePointer >= dirtyIndex.length) { if (linePointer >= dirtyIndex.length) {
needUpdate = false; needUpdate = false;
renderable.forEach(v => (v.pointer = v.text.length)); renderable.forEach(v => (v.pointer = v.text.length));
@ -282,21 +292,23 @@ export const TextContent = defineComponent<
ctx.strokeStyle = renderData.strokeStyle; ctx.strokeStyle = renderData.strokeStyle;
ctx.lineWidth = renderData.strokeWidth; ctx.lineWidth = renderData.strokeWidth;
let y = renderable[0]?.textHeight ?? 0;
renderable.forEach(v => { renderable.forEach(v => {
if (v.pointer === 0) return; if (v.pointer === 0) return;
const text = v.text.slice(0, v.pointer); const text = v.text.slice(0, v.pointer);
if (renderData.textAlign === TextAlign.Left) { if (renderData.textAlign === TextAlign.Left) {
if (renderData.stroke) ctx.strokeText(text, v.x, v.y); if (renderData.stroke) ctx.strokeText(text, v.x, y);
if (renderData.fill) ctx.fillText(text, v.x, v.y); if (renderData.fill) ctx.fillText(text, v.x, y);
} else if (renderData.textAlign === TextAlign.Center) { } else if (renderData.textAlign === TextAlign.Center) {
const x = (renderData.width - v.x) / 2 + v.x; const x = (renderData.width - v.x) / 2 + v.x;
if (renderData.stroke) ctx.strokeText(text, x, v.y); if (renderData.stroke) ctx.strokeText(text, x, y);
if (renderData.fill) ctx.fillText(text, x, v.y); if (renderData.fill) ctx.fillText(text, x, y);
} else { } else {
const x = renderData.width; const x = renderData.width;
if (renderData.stroke) ctx.strokeText(text, x, v.y); if (renderData.stroke) ctx.strokeText(text, x, y);
if (renderData.fill) ctx.fillText(text, x, v.y); if (renderData.fill) ctx.fillText(text, x, y);
} }
y += v.textHeight + v.lineHeight;
}); });
}; };
@ -323,7 +335,7 @@ export const TextContent = defineComponent<
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.lineHeight,
0 0
); );
// 第一个比较特殊,需要特判 // 第一个比较特殊,需要特判
@ -335,12 +347,11 @@ export const TextContent = defineComponent<
renderable.push({ renderable.push({
text: text.slice(start, end), text: text.slice(start, end),
x: 0, x: 0,
y: startY, lineHeight: renderData.lineHeight!,
height: renderData.lineHeight!,
textHeight: height, textHeight: height,
pointer: startPointer, pointer: startPointer,
from: start, from: start,
to: end to: end ?? text.length
}); });
for (let i = index + 1; i < lines.length; i++) { for (let i = index + 1; i < lines.length; i++) {
@ -353,12 +364,11 @@ export const TextContent = defineComponent<
renderable.push({ renderable.push({
text: text.slice(start, end), text: text.slice(start, end),
x: 0, x: 0,
y: startY, lineHeight: renderData.lineHeight!,
height: renderData.lineHeight!,
textHeight: height, textHeight: height,
pointer: 0, pointer: 0,
from: start, from: start,
to: end to: end ?? text.length
}); });
} }
emit('typeStart'); emit('typeStart');
@ -443,9 +453,8 @@ export const TextContent = defineComponent<
return () => { return () => {
return ( return (
<sprite <sprite
{...renderData}
ref={spriteElement} ref={spriteElement}
hd
antiAliasing={true}
x={renderData.x} x={renderData.x}
y={renderData.y} y={renderData.y}
width={renderData.width} width={renderData.width}
@ -456,14 +465,23 @@ export const TextContent = defineComponent<
}; };
}, textContentOptions); }, textContentOptions);
export interface TextboxProps extends TextContentProps { export interface TextboxProps extends TextContentProps, ContainerProps {
id?: string;
/** 背景颜色 */ /** 背景颜色 */
backColor?: CanvasStyle; backColor?: CanvasStyle;
/** 背景 winskin */ /** 背景 winskin */
winskin?: string; winskin?: string;
/** 边框与文字间的距离默认为8 */ /** 边框与文字间的距离默认为8 */
padding?: number; padding?: number;
/** 标题 */
title?: string;
/** 标题字体 */
titleFont?: string;
/** 标题填充样式 */
titleFill?: CanvasStyle;
/** 标题描边样式 */
titleStroke?: CanvasStyle;
/** 标题文字与边框间的距离默认为4 */
titlePadding?: number;
} }
type TextboxEmits = TextContentEmits; type TextboxEmits = TextContentEmits;
@ -473,7 +491,23 @@ const textboxOptions = {
props: (textContentOptions.props as (keyof TextboxProps)[]).concat([ props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
'backColor', 'backColor',
'winskin', 'winskin',
'id' 'id',
'padding',
'alpha',
'hidden',
'anchorX',
'anchorY',
'antiAliasing',
'cache',
'composite',
'fall',
'hd',
'transform',
'type',
'zIndex',
'titleFill',
'titleStroke',
'titleFont'
]), ]),
emits: textContentOptions.emits emits: textContentOptions.emits
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>; } satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
@ -494,24 +528,110 @@ export const Textbox = defineComponent<
data.width ??= 200; data.width ??= 200;
data.height ??= 200; data.height ??= 200;
data.id ??= ''; data.id ??= '';
data.alpha ??= 1;
data.titleFill ??= '#000';
data.titleStroke ??= 'transparent';
data.titleFont ??= '16px Verdana';
data.titlePadding ??= 4;
const store = TextboxStore.use(props.id ?? getNextTextboxId(), data); const titleElement = ref<Text>();
const hidden = ref(false); const titleWidth = ref(data.titlePadding * 2);
const titleHeight = ref(data.titlePadding * 2);
const contentY = computed(() => {
const height = titleHeight.value;
return data.title ? height : 0;
});
const contentWidth = computed(() => data.width! - data.padding! * 2);
const contentHeight = computed(
() => data.height! - data.padding! * 2 - contentY.value
);
const calTitleSize = (text: string) => {
if (!titleElement.value) return;
const { width, height } = titleElement.value;
titleWidth.value = width + data.titlePadding! * 2;
titleHeight.value = height + data.titlePadding! * 2;
data.title = text;
};
watch(titleElement, (value, old) => {
old?.off('setText', calTitleSize);
value?.on('setText', calTitleSize);
if (value) calTitleSize(value?.text);
});
onUnmounted(() => {
titleElement.value?.off('setText', calTitleSize);
});
// ----- store
/** 结束打字机 */
const storeEmits: TextboxStoreEmits = {
endType() {
data.showAll = true;
}
};
const store = TextboxStore.use(
props.id ?? getNextTextboxId(),
data,
storeEmits
);
const hidden = ref(data.hidden);
store.on('hide', () => (hidden.value = true)); store.on('hide', () => (hidden.value = true));
store.on('show', () => (hidden.value = false)); store.on('show', () => (hidden.value = false));
onUpdated(() => { store.on('update', value => {
for (const [key, value] of Object.entries(props)) { if (value.title) {
// @ts-ignore titleElement.value?.requestBeforeFrame(() => {
if (!isNil(value)) data[key] = value; const { width, height } = titleElement.value!;
titleWidth.value = width + data.padding! * 2;
titleHeight.value = height + data.padding! * 2;
});
} }
}); });
const contentWidth = computed(() => data.width! - data.padding! * 2); const onTypeStart = () => {
const contentHeight = computed(() => data.height! - data.padding! * 2); store.emitTypeStart();
};
const onTypeEnd = () => {
data.showAll = false;
store.emitTypeEnd();
};
return () => { return () => {
return ( return (
<container hidden={hidden.value} id="11111"> <container {...data} hidden={hidden.value} alpha={data.alpha}>
{data.title ? (
<container
zIndex={10}
width={titleWidth.value}
height={titleHeight.value}
>
{props.winskin ? (
<winskin></winskin>
) : (
<g-rect
x={0}
y={0}
width={titleWidth.value}
height={titleHeight.value}
></g-rect>
)}
<text
ref={titleElement}
text={data.title}
x={data.titlePadding}
y={data.titlePadding}
fillStyle={data.titleFill}
strokeStyle={data.titleStroke}
font={data.titleFont}
></text>
</container>
) : (
''
)}
{slots.default ? ( {slots.default ? (
slots.default(data) slots.default(data)
) : props.winskin ? ( ) : props.winskin ? (
@ -521,19 +641,24 @@ export const Textbox = defineComponent<
// todo // todo
<g-rect <g-rect
x={0} x={0}
y={0} y={contentY.value}
width={data.width ?? 200} width={data.width ?? 200}
height={data.height ?? 200} height={(data.height ?? 200) - contentY.value}
fill fill
fillStyle={data.backColor} fillStyle={data.backColor}
></g-rect> ></g-rect>
)} )}
<TextContent <TextContent
{...data} {...data}
x={data.padding} hidden={false}
y={data.padding} x={data.padding!}
y={contentY.value + data.padding!}
width={contentWidth.value} width={contentWidth.value}
height={contentHeight.value} height={contentHeight.value}
onTypeEnd={onTypeEnd}
onTypeStart={onTypeStart}
zIndex={0}
showAll={data.showAll}
></TextContent> ></TextContent>
</container> </container>
); );
@ -721,19 +846,53 @@ function testHeight(text: string, font: string) {
return ctx.measureText(text).fontBoundingBoxAscent; return ctx.measureText(text).fontBoundingBoxAscent;
} }
interface TextboxStoreEmits {
endType: () => void;
}
interface TextboxStoreEvent { interface TextboxStoreEvent {
update: [value: TextboxProps]; update: [value: TextboxProps];
show: []; show: [];
hide: []; hide: [];
typeStart: [];
typeEnd: [];
} }
export class TextboxStore extends EventEmitter<TextboxStoreEvent> { export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
static list: Map<string, TextboxStore> = new Map(); static list: Map<string, TextboxStore> = new Map();
private constructor(private readonly data: TextboxProps) { typing: boolean = false;
private constructor(
private readonly data: TextboxProps,
private readonly emits: TextboxStoreEmits
) {
super(); super();
} }
/**
*
*/
emitTypeStart() {
this.typing = true;
this.emit('typeStart');
}
/**
*
*/
emitTypeEnd() {
this.typing = false;
this.emit('typeEnd');
}
/**
*
*/
endType() {
this.emits.endType();
}
/** /**
* *
*/ */
@ -772,8 +931,8 @@ export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
* @param id id * @param id id
* @param props * @param props
*/ */
static use(id: string, props: TextboxProps) { static use(id: string, props: TextboxProps, emits: TextboxStoreEmits) {
const store = new TextboxStore(props); const store = new TextboxStore(props, emits);
if (this.list.has(id)) { if (this.list.has(id)) {
logger.warn(42, id); logger.warn(42, id);
} }

View File

@ -13,6 +13,7 @@ import { PopText } from '@/plugin/fx/pop';
import { FloorChange } from '@/plugin/fallback'; import { FloorChange } from '@/plugin/fallback';
import { createApp } from './renderer'; import { createApp } from './renderer';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Textbox } from './components';
let main: MotaRenderer; let main: MotaRenderer;
@ -55,6 +56,21 @@ Mota.require('var', 'loading').once('coreInit', () => {
<layer layer="fg2" zIndex={50}></layer> <layer layer="fg2" zIndex={50}></layer>
<PopText id="pop-main" zIndex={80}></PopText> <PopText id="pop-main" zIndex={80}></PopText>
</layer-group> </layer-group>
<Textbox
id="main-textbox"
text=""
hidden
width={480}
height={150}
y={330}
zIndex={30}
fillStyle={'#000'}
titleFill={'#000'}
font="16px normal"
titleFont="20px normal"
interval={25}
lineHeight={6}
></Textbox>
<FloorChange id="floor-change" zIndex={50}></FloorChange> <FloorChange id="floor-change" zIndex={50}></FloorChange>
</container> </container>
); );

View File

@ -446,10 +446,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
endFn: end endFn: end
}; };
RenderItem.tickerMap.set(id, delegation); RenderItem.tickerMap.set(id, delegation);
RenderItem.ticker.add(fn);
if (typeof time === 'number' && time < 2147438647 && time > 0) { if (typeof time === 'number' && time < 2147438647 && time > 0) {
delegation.timeout = window.setTimeout(() => { delegation.timeout = window.setTimeout(() => {
RenderItem.ticker.remove(fn); RenderItem.tickerMap.delete(id);
end?.(); end?.();
}, time); }, time);
} }
@ -616,6 +615,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
namespace?: ElementNamespace, namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null parentComponent?: ComponentInternalInstance | null
): void { ): void {
if (isNil(prevValue) && isNil(nextValue)) return;
switch (key) { switch (key) {
case 'x': { case 'x': {
if (!this.assertType(nextValue, 'number', key)) return; if (!this.assertType(nextValue, 'number', key)) return;
@ -722,13 +722,16 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
} }
} }
RenderItem.ticker.add(() => { RenderItem.ticker.add(time => {
// slice 是为了让函数里面的 request 进入下一帧执行 // slice 是为了让函数里面的 request 进入下一帧执行
if (beforeFrame.length > 0) { if (beforeFrame.length > 0) {
const arr = beforeFrame.slice(); const arr = beforeFrame.slice();
beforeFrame.splice(0); beforeFrame.splice(0);
arr.forEach(v => v()); arr.forEach(v => v());
} }
RenderItem.tickerMap.forEach(v => {
v.fn(time);
});
if (renderFrame.length > 0) { if (renderFrame.length > 0) {
const arr = renderFrame.slice(); const arr = renderFrame.slice();
renderFrame.splice(0); renderFrame.splice(0);

View File

@ -7,7 +7,9 @@ import { AutotileRenderable, RenderableData } from '../cache';
type CanvasStyle = string | CanvasGradient | CanvasPattern; type CanvasStyle = string | CanvasGradient | CanvasPattern;
export interface ETextEvent extends ERenderItemEvent {} export interface ETextEvent extends ERenderItemEvent {
setText: [text: string];
}
export class Text extends RenderItem<ETextEvent> { export class Text extends RenderItem<ETextEvent> {
text: string; text: string;
@ -67,6 +69,7 @@ export class Text extends RenderItem<ETextEvent> {
this.text = text; this.text = text;
this.calBox(); this.calBox();
if (this.parent) this.update(this); if (this.parent) this.update(this);
this.emit('setText', text);
} }
/** /**
@ -120,10 +123,10 @@ export class Text extends RenderItem<ETextEvent> {
this.setText(nextValue); this.setText(nextValue);
return; return;
case 'fillStyle': case 'fillStyle':
this.setStyle(nextValue); this.setStyle(nextValue, this.strokeStyle);
return; return;
case 'strokeStyle': case 'strokeStyle':
this.setStyle(void 0, nextValue); this.setStyle(this.fillStyle, nextValue);
return; return;
case 'font': case 'font':
if (!this.assertType(nextValue, 'string', key)) return; if (!this.assertType(nextValue, 'string', key)) return;

View File

@ -7,7 +7,7 @@ import {
import { ERenderItemEvent, RenderItem } from '../item'; import { ERenderItemEvent, RenderItem } from '../item';
import { tagMap } from './map'; import { tagMap } from './map';
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
import { Comment, Text } from '../preset/misc'; import { Comment, ETextEvent, Text } from '../preset/misc';
export const { createApp, render } = createRenderer<RenderItem, RenderItem>({ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
patchProp: function ( patchProp: function (
@ -48,7 +48,7 @@ export const { createApp, render } = createRenderer<RenderItem, RenderItem>({
return onCreate(namespace, isCustomizedBuiltIn, vnodeProps); return onCreate(namespace, isCustomizedBuiltIn, vnodeProps);
}, },
createText: function (text: string): RenderItem<ERenderItemEvent> { createText: function (text: string): RenderItem<ETextEvent> {
if (!/^\s*$/.test(text)) logger.warn(38); if (!/^\s*$/.test(text)) logger.warn(38);
return new Text(text); return new Text(text);
}, },

View File

@ -4,7 +4,14 @@ import { ElementNamespace, VNodeProps } from 'vue';
import { Container } from '../container'; import { Container } from '../container';
import { MotaRenderer } from '../render'; import { MotaRenderer } from '../render';
import { Sprite } from '../sprite'; import { Sprite } from '../sprite';
import { Comment, Icon, Image, Text, Winskin } from '../preset/misc'; import {
Comment,
ETextEvent,
Icon,
Image,
Text,
Winskin
} from '../preset/misc';
import { Shader } from '../shader'; import { Shader } from '../shader';
import { Animate, Damage, EDamageEvent, Layer, LayerGroup } from '../preset'; import { Animate, Damage, EDamageEvent, Layer, LayerGroup } from '../preset';
import { import {
@ -90,6 +97,40 @@ const standardElementNoCache = (
}; };
}; };
const enum ElementState {
None = 0,
Cache = 1,
Fall = 2
}
/**
* standardElementFor
*/
const se = (
Item: new (
type: RenderItemPosition,
cache?: boolean,
fall?: boolean
) => RenderItem,
position: RenderItemPosition,
state: ElementState
) => {
const defaultCache = !!(state & ElementState.Cache);
const defautFall = !!(state & ElementState.Fall);
return (_0: any, _1: any, props?: any) => {
if (!props) return new Item('absolute');
else {
const {
type = position,
cache = defaultCache,
fall = defautFall
} = props;
return new Item(type, cache, fall);
}
};
};
// Default elements // Default elements
tagMap.register('container', standardElement(Container)); tagMap.register('container', standardElement(Container));
tagMap.register('template', standardElement(Container)); tagMap.register('template', standardElement(Container));
@ -97,7 +138,7 @@ tagMap.register('mota-renderer', (_0, _1, props) => {
return new MotaRenderer(props?.id); return new MotaRenderer(props?.id);
}); });
tagMap.register('sprite', standardElement(Sprite)); tagMap.register('sprite', standardElement(Sprite));
tagMap.register('text', (_0, _1, props) => { tagMap.register<ETextEvent, Text>('text', (_0, _1, props) => {
if (!props) return new Text(); if (!props) return new Text();
else { else {
const { type = 'static', text = '' } = props; const { type = 'static', text = '' } = props;
@ -182,13 +223,13 @@ tagMap.register<EDamageEvent, Damage>('damage', (_0, _1, props) => {
tagMap.register('animation', (_0, _1, props) => { tagMap.register('animation', (_0, _1, props) => {
return new Animate(); return new Animate();
}); });
tagMap.register('g-rect', standardElementNoCache(Rect)); tagMap.register('g-rect', se(Rect, 'absolute', ElementState.None));
tagMap.register('g-circle', standardElementNoCache(Circle)); tagMap.register('g-circle', se(Circle, 'absolute', ElementState.None));
tagMap.register('g-ellipse', standardElementNoCache(Ellipse)); tagMap.register('g-ellipse', se(Ellipse, 'absolute', ElementState.None));
tagMap.register('g-line', standardElementNoCache(Line)); tagMap.register('g-line', se(Line, 'absolute', ElementState.None));
tagMap.register('g-bezier', standardElementNoCache(BezierCurve)); tagMap.register('g-bezier', se(BezierCurve, 'absolute', ElementState.None));
tagMap.register('g-quad', standardElementNoCache(QuadraticCurve)); tagMap.register('g-quad', se(QuadraticCurve, 'absolute', ElementState.None));
tagMap.register('g-path', standardElementNoCache(Path)); tagMap.register('g-path', se(Path, 'absolute', ElementState.None));
tagMap.register('icon', standardElementNoCache(Icon)); tagMap.register('icon', standardElementNoCache(Icon));
tagMap.register('winskin', (_0, _1, props) => { tagMap.register('winskin', (_0, _1, props) => {
if (!props) return new Winskin(core.material.images.images['winskin.png']); if (!props) return new Winskin(core.material.images.images['winskin.png']);

View File

@ -69,7 +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'.", "42": "Repeated 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."
} }

View File

@ -41,6 +41,7 @@ import type * as Animation from 'mutate-animate';
import type * as RenderUtils from '@/core/render/utils'; import type * as RenderUtils from '@/core/render/utils';
import type { WeatherController } from '@/module/weather/weather'; import type { WeatherController } from '@/module/weather/weather';
import type { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import type { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import type { TextboxStore } from '@/core/render';
interface ClassInterface { interface ClassInterface {
// 渲染进程与游戏进程通用 // 渲染进程与游戏进程通用
@ -123,6 +124,7 @@ interface ModuleInterface {
Camera: typeof Camera; Camera: typeof Camera;
MotaOffscreenCanvas2D: typeof MotaOffscreenCanvas2D; MotaOffscreenCanvas2D: typeof MotaOffscreenCanvas2D;
Utils: typeof RenderUtils; Utils: typeof RenderUtils;
TextboxStore: typeof TextboxStore;
}; };
State: { State: {
ItemState: typeof ItemState; ItemState: typeof ItemState;

View File

@ -11,7 +11,7 @@ const FSHOST = 'http://127.0.0.1:3000/';
const custom = [ const custom = [
'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom', 'container', 'image', 'sprite', 'shader', 'text', 'comment', 'custom',
'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon' 'layer', 'layer-group', 'animate', 'damage', 'graphics', 'icon', 'winskin'
] ]
// https://vitejs.dev/config/ // https://vitejs.dev/config/