mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
fix: 更新策略 & 文本框显示问题
This commit is contained in:
parent
df993a7242
commit
bca679d4b1
@ -1117,7 +1117,7 @@ actions.prototype._clickAction_text = function () {
|
||||
// 正在淡入淡出的话不执行
|
||||
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');
|
||||
|
||||
// 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) {
|
||||
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 { text } = data;
|
||||
let title = '';
|
||||
@ -1594,10 +1594,10 @@ events.prototype._action_text = function (data, x, y, prefix) {
|
||||
}
|
||||
}
|
||||
|
||||
const showTitle =
|
||||
text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
|
||||
const showText = text.slice(0, titleStartIndex) + text.slice(titleEndIndex);
|
||||
store.show();
|
||||
store.modify({ text: showTitle, title });
|
||||
store.modify({ title });
|
||||
store.setText(showText);
|
||||
|
||||
// data.text = core.replaceText(data.text, prefix);
|
||||
// var ctx = data.code ? '__text__' + data.code : null;
|
||||
|
@ -13,7 +13,7 @@ function checkSupport() {
|
||||
sleep(3000).then(() => {
|
||||
tip(
|
||||
'warning',
|
||||
`您的浏览器不支持WebGL,大部分特效将会无法显示,建议使用新版浏览器`
|
||||
`您的浏览器不支持WebGL,大部分效果将会无法显示,请更新你的浏览器`
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -21,7 +21,7 @@ function checkSupport() {
|
||||
sleep(3000).then(() => {
|
||||
tip(
|
||||
'warning',
|
||||
`您的浏览器不支持WebGL2,一部分特效将会无法显示,建议使用新版浏览器`
|
||||
`您的浏览器不支持WebGL2,大部分效果将会无法显示,请更新你的浏览器`
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -54,10 +54,10 @@ type UniformFunc<
|
||||
type UniformBinderValue<N extends UniformBinderNum> = N extends 1
|
||||
? number
|
||||
: N extends 2
|
||||
? [number, number]
|
||||
: N extends 3
|
||||
? [number, number, number]
|
||||
: [number, number, number, number];
|
||||
? [number, number]
|
||||
: N extends 3
|
||||
? [number, number, number]
|
||||
: [number, number, number, number];
|
||||
|
||||
interface UniformBinder<
|
||||
N extends UniformBinderNum,
|
||||
|
@ -80,9 +80,9 @@ export class Container<E extends EContainerEvent = EContainerEvent>
|
||||
append(parent: RenderItem): void {
|
||||
super.append(parent);
|
||||
if (this.root) {
|
||||
const root = this.root;
|
||||
this.forEachChild(ele => {
|
||||
ele.checkRoot();
|
||||
this.root?.connect(ele);
|
||||
ele.setRoot(root);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -104,6 +104,7 @@ export class Container<E extends EContainerEvent = EContainerEvent>
|
||||
this.sortedChildren = [...this.children].sort(
|
||||
(a, b) => a.zIndex - b.zIndex
|
||||
);
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected propagateEvent<T extends ActionType>(
|
||||
|
@ -437,6 +437,15 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除由 `requireCanvas` 申请的画布,当画布不再使用时,可以用该方法删除画布
|
||||
* @param canvas 要删除的画布
|
||||
*/
|
||||
protected deleteCanvas(canvas: MotaOffscreenCanvas2D) {
|
||||
if (!this.canvases.delete(canvas)) return;
|
||||
canvas.delete();
|
||||
}
|
||||
|
||||
//#region 修改元素属性
|
||||
|
||||
/**
|
||||
@ -457,8 +466,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
pos(x: number, y: number) {
|
||||
// 这个函数会调用 update,因此不再手动调用 update
|
||||
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 {
|
||||
if (this.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
if (this.hidden) return;
|
||||
this.parent?.update(item);
|
||||
if (this._parent) {
|
||||
if (this.cacheDirty && this._parent.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
if (this.hidden) return;
|
||||
this._parent.update(item);
|
||||
} else {
|
||||
if (this.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateTransform() {
|
||||
@ -650,6 +664,12 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
|
||||
//#region 父子关系
|
||||
|
||||
setRoot(item: RenderItem & IRenderTreeRoot) {
|
||||
this._root?.disconnect(this);
|
||||
this._root = item;
|
||||
item.connect(item);
|
||||
}
|
||||
|
||||
checkRoot(): RenderItem | null {
|
||||
if (this._root) return this._root;
|
||||
if (this.isRoot) return this;
|
||||
|
@ -30,7 +30,10 @@ export class Text extends RenderItem<ETextEvent> {
|
||||
super(type, false);
|
||||
|
||||
this.text = text;
|
||||
if (text.length > 0) this.calBox();
|
||||
if (text.length > 0) {
|
||||
this.calBox();
|
||||
this.emit('setText', text);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(
|
||||
@ -70,7 +73,7 @@ export class Text extends RenderItem<ETextEvent> {
|
||||
setText(text: string) {
|
||||
this.text = text;
|
||||
this.calBox();
|
||||
if (this.parent) this.update(this);
|
||||
this.update(this);
|
||||
this.emit('setText', text);
|
||||
}
|
||||
|
||||
@ -81,7 +84,7 @@ export class Text extends RenderItem<ETextEvent> {
|
||||
setFont(font: string) {
|
||||
this.font = font;
|
||||
this.calBox();
|
||||
if (this.parent) this.update(this);
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -410,8 +413,7 @@ export class Winskin extends RenderItem<EWinskinEvent> {
|
||||
Winskin.patternMap.set(this.imageName, winskinPattern);
|
||||
}
|
||||
this.patternCache = winskinPattern;
|
||||
pattern.delete();
|
||||
this.canvases.delete(pattern);
|
||||
this.deleteCanvas(pattern);
|
||||
return winskinPattern;
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,8 @@ import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
onUnmounted,
|
||||
onUpdated,
|
||||
ref,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
@ -13,7 +13,7 @@ import {
|
||||
} from 'vue';
|
||||
import { logger } from '@/core/common/logger';
|
||||
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 { SetupComponentOptions } from './types';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
@ -22,7 +22,9 @@ import {
|
||||
ITextContentConfig,
|
||||
TextContentTyper,
|
||||
TyperRenderable,
|
||||
TextContentType
|
||||
TextContentType,
|
||||
WordBreak,
|
||||
TextAlign
|
||||
} from './textboxTyper';
|
||||
|
||||
export interface TextContentProps
|
||||
@ -34,8 +36,6 @@ export interface TextContentProps
|
||||
fill?: boolean;
|
||||
/** 是否描边 */
|
||||
stroke?: boolean;
|
||||
/** 是否忽略打字机,直接显伤全部 */
|
||||
showAll?: boolean;
|
||||
}
|
||||
|
||||
export type TextContentEmits = {
|
||||
@ -43,6 +43,18 @@ export type TextContentEmits = {
|
||||
typeStart: () => void;
|
||||
};
|
||||
|
||||
export interface TextContentExpose {
|
||||
/**
|
||||
* 重新开始打字
|
||||
*/
|
||||
retype(): void;
|
||||
|
||||
/**
|
||||
* 立刻显示所有文字
|
||||
*/
|
||||
showAll(): void;
|
||||
}
|
||||
|
||||
const textContentOptions = {
|
||||
props: [
|
||||
'breakChars',
|
||||
@ -50,7 +62,6 @@ const textContentOptions = {
|
||||
'fontSize',
|
||||
'fontWeight',
|
||||
'fontItalic',
|
||||
'height',
|
||||
'ignoreLineEnd',
|
||||
'ignoreLineStart',
|
||||
'interval',
|
||||
@ -58,17 +69,14 @@ const textContentOptions = {
|
||||
'lineHeight',
|
||||
'text',
|
||||
'textAlign',
|
||||
'width',
|
||||
'wordBreak',
|
||||
'x',
|
||||
'y',
|
||||
'fill',
|
||||
'fillStyle',
|
||||
'strokeStyle',
|
||||
'strokeWidth',
|
||||
'stroke',
|
||||
'showAll',
|
||||
'loc'
|
||||
'loc',
|
||||
'width'
|
||||
],
|
||||
emits: ['typeEnd', 'typeStart']
|
||||
} satisfies SetupComponentOptions<
|
||||
@ -81,45 +89,42 @@ export const TextContent = defineComponent<
|
||||
TextContentProps,
|
||||
TextContentEmits,
|
||||
keyof TextContentEmits
|
||||
>((props, { emit }) => {
|
||||
if (props.width && props.width <= 0) {
|
||||
>((props, { emit, expose }) => {
|
||||
const width = computed(() => props.width ?? props.loc?.[2] ?? 200);
|
||||
if (width.value < 0) {
|
||||
logger.warn(41, String(props.width));
|
||||
}
|
||||
|
||||
const typer = new TextContentTyper(props);
|
||||
let renderable: TyperRenderable[] = [];
|
||||
let needUpdate = false;
|
||||
let nowText = '';
|
||||
|
||||
watch(props, value => {
|
||||
typer.setConfig(value);
|
||||
});
|
||||
|
||||
const retype = () => {
|
||||
if (props.showAll) {
|
||||
typer.typeAll();
|
||||
}
|
||||
if (props.text === nowText) return;
|
||||
if (needUpdate) return;
|
||||
needUpdate = true;
|
||||
if (!spriteElement.value) {
|
||||
needUpdate = false;
|
||||
}
|
||||
nowText = props.text ?? '';
|
||||
renderable = [];
|
||||
|
||||
spriteElement.value?.requestBeforeFrame(() => {
|
||||
typer.setConfig(props);
|
||||
typer.setText(props.text ?? '');
|
||||
typer.type();
|
||||
if (props.showAll) {
|
||||
typer.typeAll();
|
||||
}
|
||||
needUpdate = false;
|
||||
});
|
||||
};
|
||||
|
||||
onUpdated(retype);
|
||||
const showAll = () => {
|
||||
typer.typeAll();
|
||||
};
|
||||
|
||||
watch(props, value => {
|
||||
typer.setConfig(value);
|
||||
retype();
|
||||
});
|
||||
|
||||
expose({ retype, showAll });
|
||||
|
||||
const spriteElement = shallowRef<Sprite>();
|
||||
const renderContent = (canvas: MotaOffscreenCanvas2D) => {
|
||||
@ -164,19 +169,15 @@ export const TextContent = defineComponent<
|
||||
return () => {
|
||||
return (
|
||||
<sprite
|
||||
{...props}
|
||||
loc={props.loc}
|
||||
ref={spriteElement}
|
||||
x={props.x}
|
||||
y={props.y}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
render={renderContent}
|
||||
></sprite>
|
||||
);
|
||||
};
|
||||
}, textContentOptions);
|
||||
|
||||
export interface TextboxProps extends TextContentProps, ContainerProps {
|
||||
export interface TextboxProps extends TextContentProps, DefaultProps {
|
||||
/** 背景颜色 */
|
||||
backColor?: CanvasStyle;
|
||||
/** 背景 winskin */
|
||||
@ -195,6 +196,28 @@ export interface TextboxProps extends TextContentProps, ContainerProps {
|
||||
titlePadding?: number;
|
||||
}
|
||||
|
||||
export interface TextboxExpose {
|
||||
/**
|
||||
* 显示这个文本框
|
||||
*/
|
||||
show(): void;
|
||||
|
||||
/**
|
||||
* 隐藏这个文本框
|
||||
*/
|
||||
hide(): void;
|
||||
|
||||
/**
|
||||
* 重新开始打字
|
||||
*/
|
||||
retype(): void;
|
||||
|
||||
/**
|
||||
* 立刻显示所有文字
|
||||
*/
|
||||
showAll(): void;
|
||||
}
|
||||
|
||||
type TextboxEmits = TextContentEmits;
|
||||
type TextboxSlots = SlotsType<{
|
||||
default: (data: TextboxProps) => VNode[];
|
||||
@ -205,23 +228,14 @@ const textboxOptions = {
|
||||
props: (textContentOptions.props as (keyof TextboxProps)[]).concat([
|
||||
'backColor',
|
||||
'winskin',
|
||||
'id',
|
||||
'padding',
|
||||
'alpha',
|
||||
'hidden',
|
||||
'anchorX',
|
||||
'anchorY',
|
||||
'anti',
|
||||
'cache',
|
||||
'composite',
|
||||
'fall',
|
||||
'hd',
|
||||
'transform',
|
||||
'type',
|
||||
'zIndex',
|
||||
'titleFill',
|
||||
'titleStroke',
|
||||
'titleFont'
|
||||
'titleFont',
|
||||
'titlePadding',
|
||||
'id',
|
||||
'hidden',
|
||||
'title'
|
||||
]),
|
||||
emits: textContentOptions.emits
|
||||
} satisfies SetupComponentOptions<TextboxProps, {}, string, TextboxSlots>;
|
||||
@ -236,153 +250,206 @@ export const Textbox = defineComponent<
|
||||
TextboxEmits,
|
||||
keyof TextboxEmits,
|
||||
TextboxSlots
|
||||
>((props, { slots }) => {
|
||||
const data = shallowReactive({ ...props });
|
||||
data.padding ??= 8;
|
||||
data.width ??= 200;
|
||||
data.height ??= 200;
|
||||
data.id ??= '';
|
||||
data.alpha ??= 1;
|
||||
data.titleFill ??= '#000';
|
||||
data.titleStroke ??= 'transparent';
|
||||
data.titleFont ??= '16px Verdana';
|
||||
data.titlePadding ??= 4;
|
||||
>((props, { slots, expose }) => {
|
||||
const contentData = shallowReactive<TextContentProps>({});
|
||||
const data = shallowReactive<TextboxProps>({});
|
||||
|
||||
const setContentData = () => {
|
||||
contentData.breakChars = props.breakChars ?? '';
|
||||
contentData.fontFamily = props.fontFamily ?? 'Verdana';
|
||||
contentData.fontSize = props.fontSize ?? 16;
|
||||
contentData.fontWeight = props.fontWeight ?? 500;
|
||||
contentData.fontItalic = props.fontItalic ?? false;
|
||||
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 titleWidth = ref(data.titlePadding * 2);
|
||||
const titleHeight = ref(data.titlePadding * 2);
|
||||
const content = ref<TextContentExpose>();
|
||||
const hidden = ref(props.hidden);
|
||||
/** 标题宽度 */
|
||||
const tw = ref(data.titlePadding! * 2);
|
||||
/** 标题高度 */
|
||||
const th = ref(data.titlePadding! * 2);
|
||||
const contentY = computed(() => {
|
||||
const height = titleHeight.value;
|
||||
const height = th.value;
|
||||
return data.title ? height : 0;
|
||||
});
|
||||
const backHeight = computed(() => data.height! - contentY.value);
|
||||
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;
|
||||
const onSetText = () => {
|
||||
nextTick(() => {
|
||||
titleElement.value?.requestBeforeFrame(() => {
|
||||
if (titleElement.value) {
|
||||
const { width, height } = titleElement.value;
|
||||
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
|
||||
|
||||
/** 结束打字机 */
|
||||
const storeEmits: TextboxStoreEmits = {
|
||||
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(
|
||||
props.id ?? getNextTextboxId(),
|
||||
data,
|
||||
contentData,
|
||||
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 = () => {
|
||||
store.emitTypeStart();
|
||||
};
|
||||
|
||||
const onTypeEnd = () => {
|
||||
data.showAll = false;
|
||||
store.emitTypeEnd();
|
||||
};
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<container {...data} hidden={hidden.value} alpha={data.alpha}>
|
||||
{data.title ? (
|
||||
<container
|
||||
zIndex={10}
|
||||
width={titleWidth.value}
|
||||
height={titleHeight.value}
|
||||
>
|
||||
{slots.title ? (
|
||||
slots.title(data)
|
||||
) : props.winskin ? (
|
||||
<winskin image={props.winskin}></winskin>
|
||||
) : (
|
||||
<g-rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={titleWidth.value}
|
||||
height={titleHeight.value}
|
||||
fillStyle={data.backColor}
|
||||
></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(data)
|
||||
) : props.winskin ? (
|
||||
<winskin image={props.winskin}></winskin>
|
||||
) : (
|
||||
<g-rect
|
||||
x={0}
|
||||
y={contentY.value}
|
||||
width={data.width ?? 200}
|
||||
height={(data.height ?? 200) - contentY.value}
|
||||
fill
|
||||
fillStyle={data.backColor}
|
||||
></g-rect>
|
||||
)}
|
||||
<TextContent
|
||||
{...data}
|
||||
id=""
|
||||
hidden={false}
|
||||
x={data.padding!}
|
||||
y={contentY.value + data.padding!}
|
||||
width={contentWidth.value}
|
||||
height={contentHeight.value}
|
||||
onTypeEnd={onTypeEnd}
|
||||
onTypeStart={onTypeStart}
|
||||
zIndex={0}
|
||||
showAll={data.showAll}
|
||||
></TextContent>
|
||||
</container>
|
||||
);
|
||||
};
|
||||
expose<TextboxExpose>({
|
||||
show() {
|
||||
hidden.value = false;
|
||||
},
|
||||
hide() {
|
||||
hidden.value = true;
|
||||
},
|
||||
retype() {
|
||||
content.value?.retype();
|
||||
},
|
||||
showAll() {
|
||||
content.value?.showAll();
|
||||
}
|
||||
});
|
||||
|
||||
return () => (
|
||||
<container
|
||||
id={props.id}
|
||||
hidden={hidden.value}
|
||||
alpha={data.alpha}
|
||||
loc={props.loc}
|
||||
>
|
||||
{data.title && (
|
||||
<container zIndex={10} loc={[0, 0, tw.value, th.value]}>
|
||||
{slots.title ? (
|
||||
slots.title(data)
|
||||
) : props.winskin ? (
|
||||
<winskin
|
||||
image={props.winskin}
|
||||
loc={[0, 0, tw.value, th.value]}
|
||||
></winskin>
|
||||
) : (
|
||||
<g-rect loc={[0, 0, tw.value, th.value]}></g-rect>
|
||||
)}
|
||||
<text
|
||||
ref={titleElement}
|
||||
text={data.title}
|
||||
loc={[data.titlePadding, data.titlePadding]}
|
||||
fillStyle={data.titleFill}
|
||||
strokeStyle={data.titleStroke}
|
||||
font={data.titleFont}
|
||||
></text>
|
||||
</container>
|
||||
)}
|
||||
{slots.default ? (
|
||||
slots.default(data)
|
||||
) : props.winskin ? (
|
||||
<winskin
|
||||
image={props.winskin}
|
||||
loc={[0, contentY.value, data.width!, backHeight.value]}
|
||||
></winskin>
|
||||
) : (
|
||||
<g-rect
|
||||
loc={[0, contentY.value, data.width!, backHeight.value]}
|
||||
fill
|
||||
fillStyle={data.backColor}
|
||||
></g-rect>
|
||||
)}
|
||||
<TextContent
|
||||
{...contentData}
|
||||
ref={content}
|
||||
x={data.padding!}
|
||||
y={contentY.value + data.padding!}
|
||||
width={contentWidth.value}
|
||||
height={contentHeight.value}
|
||||
onTypeEnd={onTypeEnd}
|
||||
onTypeStart={onTypeStart}
|
||||
></TextContent>
|
||||
</container>
|
||||
);
|
||||
}, textboxOptions);
|
||||
|
||||
interface TextboxStoreEmits {
|
||||
endType: () => void;
|
||||
hide: () => void;
|
||||
show: () => void;
|
||||
update: (value: TextboxProps) => void;
|
||||
setText: (text: string) => void;
|
||||
}
|
||||
|
||||
interface TextboxStoreEvent {
|
||||
@ -436,21 +503,30 @@ export class TextboxStore extends EventEmitter<TextboxStoreEvent> {
|
||||
// @ts-expect-error 无法推导
|
||||
if (!isNil(value)) this.data[key] = value;
|
||||
}
|
||||
this.emits.update(this.data);
|
||||
this.emit('update', this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置显示的文本
|
||||
* @param text 要显示的文本
|
||||
*/
|
||||
setText(text: string) {
|
||||
this.emits.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示文本框
|
||||
*/
|
||||
show() {
|
||||
this.emit('show');
|
||||
this.emits.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏文本框
|
||||
*/
|
||||
hide() {
|
||||
this.emit('hide');
|
||||
this.emits.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -422,6 +422,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
||||
* 开始打字
|
||||
*/
|
||||
type() {
|
||||
if (this.typing) return;
|
||||
if (this.config.interval === 0) {
|
||||
this.emit('typeStart');
|
||||
this.typeChars(Infinity);
|
||||
@ -438,6 +439,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
||||
* 立即显示所有文字
|
||||
*/
|
||||
typeAll() {
|
||||
if (!this.typing) return;
|
||||
this.typeChars(Infinity);
|
||||
this.render?.(this.renderData, false);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { defineComponent } from 'vue';
|
||||
import { UIController } from '@/core/system';
|
||||
import { mainSceneUI } from './ui/main';
|
||||
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
|
||||
import { TextboxStore } from './components';
|
||||
|
||||
export function create() {
|
||||
const main = new MotaRenderer();
|
||||
@ -34,6 +35,10 @@ export function create() {
|
||||
console.log(main);
|
||||
}
|
||||
|
||||
Mota.register('module', 'MainUI', {
|
||||
TextboxStore
|
||||
});
|
||||
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export * from './use';
|
||||
|
@ -57,9 +57,7 @@ const MainScene = defineComponent(() => {
|
||||
const mainTextboxProps: Props<typeof Textbox> = {
|
||||
text: '',
|
||||
hidden: true,
|
||||
width: 480,
|
||||
height: 150,
|
||||
y: 330,
|
||||
loc: [0, 330, 480, 150],
|
||||
zIndex: 30,
|
||||
fillStyle: '#fff',
|
||||
titleFill: 'gold',
|
||||
@ -67,7 +65,7 @@ const MainScene = defineComponent(() => {
|
||||
titleFont: '700 20px normal',
|
||||
winskin: 'winskin2.png',
|
||||
interval: 100,
|
||||
lineHeight: 6
|
||||
lineHeight: 4
|
||||
};
|
||||
|
||||
const map = ref<LayerGroup>();
|
||||
|
Loading…
Reference in New Issue
Block a user