mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 04:19:30 +08:00
feat: 文本框接入样板
This commit is contained in:
parent
e5e0458891
commit
734980eb6a
@ -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);
|
||||||
|
@ -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) {
|
||||||
|
@ -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",
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
|
@ -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']);
|
||||||
|
@ -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."
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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/
|
||||||
|
Loading…
Reference in New Issue
Block a user