fix: 文本框的配置问题

This commit is contained in:
unanmed 2025-09-28 16:01:09 +08:00
parent 71c88369e1
commit 5bdd41159b
8 changed files with 91 additions and 33 deletions

View File

@ -174,6 +174,8 @@ export const TextContent = defineComponent<
const renderContent = (canvas: MotaOffscreenCanvas2D) => { const renderContent = (canvas: MotaOffscreenCanvas2D) => {
const ctx = canvas.ctx; const ctx = canvas.ctx;
ctx.textBaseline = 'top'; ctx.textBaseline = 'top';
ctx.lineWidth = props.strokeWidth ?? 2;
ctx.lineJoin = 'round';
for (const data of renderable) { for (const data of renderable) {
if (data.cut) break; if (data.cut) break;
switch (data.type) { switch (data.type) {
@ -182,14 +184,15 @@ export const TextContent = defineComponent<
ctx.fillStyle = data.fillStyle; ctx.fillStyle = data.fillStyle;
ctx.strokeStyle = data.strokeStyle; ctx.strokeStyle = data.strokeStyle;
ctx.font = data.font; ctx.font = data.font;
const text = data.text.slice(0, data.pointer); const text = data.text.slice(0, data.pointer);
if (props.fill ?? true) {
ctx.fillText(text, data.x, data.y);
}
if (props.stroke) { if (props.stroke) {
ctx.strokeText(text, data.x, data.y); ctx.strokeText(text, data.x, data.y);
} }
if (props.fill ?? true) {
ctx.fillText(text, data.x, data.y);
}
break; break;
} }
case TextContentType.Icon: { case TextContentType.Icon: {
@ -250,7 +253,7 @@ export interface TextboxProps extends TextContentProps, DefaultProps {
/** 标题文字与边框间的距离默认为4 */ /** 标题文字与边框间的距离默认为4 */
titlePadding?: number; titlePadding?: number;
/** 图标 */ /** 图标 */
icon?: AllIds; icon?: AllIdsWithNone;
/** 最大宽度 */ /** 最大宽度 */
width: number; width: number;
} }
@ -408,8 +411,8 @@ export const Textbox = defineComponent<
titleElement.value?.requestBeforeFrame(() => { titleElement.value?.requestBeforeFrame(() => {
if (titleElement.value) { if (titleElement.value) {
const { width, height } = titleElement.value; const { width, height } = titleElement.value;
tw.value = width + data.padding! * 2; tw.value = width + data.titlePadding! * 2;
th.value = height + data.padding! * 2; th.value = height + data.titlePadding! * 2;
} }
}); });
}); });
@ -481,7 +484,7 @@ export const Textbox = defineComponent<
id={props.id} id={props.id}
hidden={hidden.value} hidden={hidden.value}
alpha={data.alpha} alpha={data.alpha}
loc={props.loc} loc={data.loc}
> >
{data.title && ( {data.title && (
<container zIndex={10} loc={[0, 0, tw.value, th.value]}> <container zIndex={10} loc={[0, 0, tw.value, th.value]}>
@ -502,6 +505,7 @@ export const Textbox = defineComponent<
fillStyle={data.titleFill} fillStyle={data.titleFill}
strokeStyle={data.titleStroke} strokeStyle={data.titleStroke}
font={data.titleFont} font={data.titleFont}
strokeWidth={2}
></text> ></text>
</container> </container>
)} )}
@ -520,7 +524,7 @@ export const Textbox = defineComponent<
></g-rect> ></g-rect>
)} )}
{hasIcon.value && ( {hasIcon.value && (
<icon icon={data.icon!} loc={iconLoc.value} animate /> <icon icon={data.icon as AllIds} loc={iconLoc.value} animate />
)} )}
{hasIcon.value && ( {hasIcon.value && (
<g-rect <g-rect

View File

@ -336,6 +336,12 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
this.config[key] = value; this.config[key] = value;
} }
} }
if (config.font) {
this.config.fontFamily = config.font.family;
this.config.fontSize = config.font.size;
this.config.fontItalic = config.font.italic;
this.config.fontWeight = config.font.weight;
}
this.parser.setStatus({ this.parser.setStatus({
fillStyle: this.config.fillStyle, fillStyle: this.config.fillStyle,
fontFamily: this.config.fontFamily, fontFamily: this.config.fontFamily,
@ -528,7 +534,8 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
return; return;
} }
this.emit('typeStart'); this.emit('typeStart');
this.lastTypeTime = Date.now(); // 减去间隔是为了第一个字可以立刻打出来,不然看起来有延迟
this.lastTypeTime = Date.now() - this.config.interval - 1;
this.typing = true; this.typing = true;
} }
@ -628,11 +635,11 @@ export class TextContentParser {
* @param st * @param st
*/ */
setStatus(st: Partial<ParserStatus>) { setStatus(st: Partial<ParserStatus>) {
if (!isNil(st.fillStyle)) this.status.fillStyle = st.fillStyle; if (!isNil(st.fillStyle)) this.initStatus.fillStyle = st.fillStyle;
if (!isNil(st.fontSize)) this.status.fontSize = st.fontSize; if (!isNil(st.fontSize)) this.initStatus.fontSize = st.fontSize;
if (!isNil(st.fontFamily)) this.status.fontFamily = st.fontFamily; if (!isNil(st.fontFamily)) this.initStatus.fontFamily = st.fontFamily;
if (!isNil(st.fontItalic)) this.status.fontItalic = st.fontItalic; if (!isNil(st.fontItalic)) this.initStatus.fontItalic = st.fontItalic;
if (!isNil(st.fontWeight)) this.status.fontWeight = st.fontWeight; if (!isNil(st.fontWeight)) this.initStatus.fontWeight = st.fontWeight;
} }
/** /**
@ -856,6 +863,7 @@ export class TextContentParser {
this.font = this.buildFont(); this.font = this.buildFont();
this.resolved = ''; this.resolved = '';
this.wordBreak = [0]; this.wordBreak = [0];
this.wordBreakRule = this.config.wordBreak;
this.nodePointer = 0; this.nodePointer = 0;
this.blockPointer = 0; this.blockPointer = 0;
this.nowNode = 0; this.nowNode = 0;
@ -975,7 +983,6 @@ export class TextContentParser {
} }
this.addTextNode(text.length, false); this.addTextNode(text.length, false);
return this.splitLines(width); return this.splitLines(width);
} }
@ -1198,7 +1205,11 @@ export class TextContentParser {
this.newLine(); this.newLine();
const nextStart = this.wordBreak[index]; const nextStart = this.wordBreak[index];
const nextEnd = this.wordBreak[end]; const nextEnd = this.wordBreak[end];
this.bsStart = index; if (index === this.bsStart) {
this.bsStart = this.bsStart + 1;
} else {
this.bsStart = index;
}
this.bsEnd = end; this.bsEnd = end;
const metrics = this.measure(node, nextStart, nextEnd); const metrics = this.measure(node, nextStart, nextEnd);
if (metrics.width < width) { if (metrics.width < width) {
@ -1338,11 +1349,9 @@ export class TextContentParser {
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (node.type === TextContentType.Text) { if (node.type === TextContentType.Text) {
this.wordBreak = [node.text.length]; this.wordBreak = [0, node.text.length];
} }
const pointer = const block = this.generateBlock(node, 1);
node.type === TextContentType.Text ? node.text.length : 1;
const block = this.generateBlock(node, pointer);
this.pushBlock(block, 1); this.pushBlock(block, 1);
} }
this.newLine(); this.newLine();

View File

@ -66,7 +66,7 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
* *
* @param id id * @param id id
*/ */
setIcon(id: AllIds | AllNumbers) { setIcon(id: AllIdsWithNone | AllNumbers) {
if (id === 0 || id === 'none') { if (id === 0 || id === 'none') {
this.renderable = void 0; this.renderable = void 0;
return; return;

View File

@ -83,7 +83,7 @@ const MainScene = defineComponent(() => {
font: new Font('normal'), font: new Font('normal'),
titleFont: new Font('normal', 20, 'px', 700), titleFont: new Font('normal', 20, 'px', 700),
winskin: 'winskin2.png', winskin: 'winskin2.png',
interval: 100, interval: 30,
lineHeight: 4, lineHeight: 4,
width: MAP_WIDTH width: MAP_WIDTH
}; };

View File

@ -52,6 +52,7 @@ export class Text extends RenderItem<ETextEvent> {
ctx.strokeStyle = this.strokeStyle ?? 'transparent'; ctx.strokeStyle = this.strokeStyle ?? 'transparent';
ctx.font = this.font.string(); ctx.font = this.font.string();
ctx.lineWidth = this.strokeWidth; ctx.lineWidth = this.strokeWidth;
ctx.lineJoin = 'round';
if (this.strokeStyle) { if (this.strokeStyle) {
ctx.strokeText(this.text, stroke, this.descent + stroke + SAFE_PAD); ctx.strokeText(this.text, stroke, this.descent + stroke + SAFE_PAD);

View File

@ -1044,7 +1044,7 @@ setText_s
tooltip : setText设置文本的属性,颜色为RGB三元组或RGBA四元组,打字间隔为剧情文字添加的时间间隔,为整数或不填,字符间距为字符之间的距离,为整数或不填。 tooltip : setText设置文本的属性,颜色为RGB三元组或RGBA四元组,打字间隔为剧情文字添加的时间间隔,为整数或不填,字符间距为字符之间的距离,为整数或不填。
helpUrl : /_docs/#/instruction helpUrl : /_docs/#/instruction
previewBlock : true previewBlock : true
default : ["main-textbox","","","","","","","",false,false,"","","",'rgba(255,255,255,1)',"",'rgba(255,255,255,1)',"",true,false,"",'rgba(255,255,255,1)',"","",true,false,"",'null','null',"","",""] default : ["main-textbox","","","","","","","",false,false,"","","",'rgba(255,255,255,1)',"",'rgba(0,0,0,1)',"",true,false,"",'rgba(0,0,0,0.9)',"","",true,false,"",'null','null',"","",""]
Bool_0 = Bool_0 ? (', "fontItalic": '+Bool_0) : ''; Bool_0 = Bool_0 ? (', "fontItalic": '+Bool_0) : '';
Bool_1 = Bool_1 ? (', "keepLast": '+Bool_1) : ''; Bool_1 = Bool_1 ? (', "keepLast": '+Bool_1) : '';
Bool_2 = !Bool_2 ? (', "fill": '+Bool_2) : ''; Bool_2 = !Bool_2 ? (', "fill": '+Bool_2) : '';
@ -1057,7 +1057,7 @@ IntString_2 = IntString_2 ? (', "width": '+IntString_2) : '';
IntString_3 = IntString_3 ? (', "height": '+IntString_3) : ''; IntString_3 = IntString_3 ? (', "height": '+IntString_3) : '';
IntString_4 = IntString_4 ? (', "fontSize": '+IntString_4) : ''; IntString_4 = IntString_4 ? (', "fontSize": '+IntString_4) : '';
IntString_5 = IntString_5? (', "fontWeight": ' + IntString_5) : ''; IntString_5 = IntString_5? (', "fontWeight": ' + IntString_5) : '';
IntString_6 = IntString_6 ? (', " interval": '+IntString_6) : ''; IntString_6 = IntString_6 ? (', "interval": '+IntString_6) : '';
IntString_7 = IntString_7 ? (', "lineHeight": ' + IntString_7) : ''; IntString_7 = IntString_7 ? (', "lineHeight": ' + IntString_7) : '';
IntString_8 = IntString_8? (', "strokeWidth": ' + IntString_8) : ''; IntString_8 = IntString_8? (', "strokeWidth": ' + IntString_8) : '';
IntString_9 = IntString_9? (', "padding": ' + IntString_9) : ''; IntString_9 = IntString_9? (', "padding": ' + IntString_9) : '';

View File

@ -1579,7 +1579,15 @@ events.prototype._action_text = function (data) {
loc[3] ??= 200; loc[3] ??= 200;
const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data; const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data;
store.show(); store.show();
store.modify({ title, icon, loc: [x, y, width, height], width }); store.modify({
title,
icon,
x,
y,
width,
height,
loc: [x, y, width, height]
});
store.setText(text); store.setText(text);
core.events.nowTextbox = textbox; core.events.nowTextbox = textbox;
}; };
@ -1600,7 +1608,15 @@ events.prototype._action_autoText = function (data) {
loc[3] ??= 200; loc[3] ??= 200;
const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data; const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data;
store.show(); store.show();
store.modify({ title, icon, loc: [x, y, width, height], width }); store.modify({
title,
icon,
x,
y,
width,
height,
loc: [x, y, width, height]
});
store.setText(text); store.setText(text);
setTimeout(() => { setTimeout(() => {
@ -1623,10 +1639,11 @@ events.prototype._action__label = function (data, x, y, prefix) {
}; };
events.prototype._action_setText = function (data) { events.prototype._action_setText = function (data) {
const isNil = value => value !== null && value !== void 0; const isNil = value => value === null || value === void 0;
const { textbox = 'main-textbox' } = data; const { textbox = 'main-textbox' } = data;
const Store = Mota.require('@user/client-modules').TextboxStore; const Store = Mota.require('@user/client-modules').TextboxStore;
const Font = Mota.require('@motajs/render-vue').Font; const { TextAlign, WordBreak } = Mota.require('@user/client-modules');
const Font = Mota.require('@motajs/render-style').Font;
const store = Store.get(textbox); const store = Store.get(textbox);
if (!store) { if (!store) {
core.doAction(); core.doAction();
@ -1651,6 +1668,10 @@ events.prototype._action_setText = function (data) {
}); });
// config // config
const config = { const config = {
x,
y,
width,
height,
loc: newLoc, loc: newLoc,
font: newFont, font: newFont,
keepLast: data.keepLast, keepLast: data.keepLast,
@ -1664,15 +1685,36 @@ events.prototype._action_setText = function (data) {
backColor: data.backColor, backColor: data.backColor,
winskin: data.winskin, winskin: data.winskin,
padding: data.padding, padding: data.padding,
titleFill: isNil(data.titleFill), titleFill: isNil(data.titleFill) ? 'gold' : 'transparent',
titleStroke: !!data.titleStroke, titleStroke: data.titleStroke ? 'black' : 'transparent',
titlePadding: data.titlePadding, titlePadding: data.titlePadding,
textAlign: data.textAlign,
wordBreak: data.wordBreak,
ignoreLineStart: data.ignoreLineStart, ignoreLineStart: data.ignoreLineStart,
ignoreLineEnd: data.ignoreLineEnd, ignoreLineEnd: data.ignoreLineEnd,
breakChars: data.breakChars breakChars: data.breakChars
}; };
switch (data.textAlign) {
case 'left':
config.textAlign = TextAlign.Left;
break;
case 'center':
config.textAlign = TextAlign.Center;
break;
case 'right':
config.textAlign = TextAlign.Right;
break;
}
switch (data.wordBreak) {
case 'none':
config.wordBreak = WordBreak.None;
break;
case 'space':
config.wordBreak = WordBreak.Space;
break;
case 'all':
config.wordBreak = WordBreak.All;
break;
}
store.modify(config); store.modify(config);
core.doAction(); core.doAction();

View File

@ -25,7 +25,9 @@ type ItemCls = 'tools' | 'items' | 'equips' | 'constants';
/** /**
* id * id
*/ */
type AllIds = keyof IdToNumber | 'none'; type AllIds = keyof IdToNumber;
type AllIdsWithNone = AllIds | 'none';
/** /**
* *