diff --git a/packages-user/client-modules/src/render/components/textbox.tsx b/packages-user/client-modules/src/render/components/textbox.tsx index d1ec44d..18703f0 100644 --- a/packages-user/client-modules/src/render/components/textbox.tsx +++ b/packages-user/client-modules/src/render/components/textbox.tsx @@ -174,6 +174,8 @@ export const TextContent = defineComponent< const renderContent = (canvas: MotaOffscreenCanvas2D) => { const ctx = canvas.ctx; ctx.textBaseline = 'top'; + ctx.lineWidth = props.strokeWidth ?? 2; + ctx.lineJoin = 'round'; for (const data of renderable) { if (data.cut) break; switch (data.type) { @@ -182,14 +184,15 @@ export const TextContent = defineComponent< ctx.fillStyle = data.fillStyle; ctx.strokeStyle = data.strokeStyle; ctx.font = data.font; + const text = data.text.slice(0, data.pointer); - if (props.fill ?? true) { - ctx.fillText(text, data.x, data.y); - } if (props.stroke) { ctx.strokeText(text, data.x, data.y); } + if (props.fill ?? true) { + ctx.fillText(text, data.x, data.y); + } break; } case TextContentType.Icon: { @@ -250,7 +253,7 @@ export interface TextboxProps extends TextContentProps, DefaultProps { /** 标题文字与边框间的距离,默认为4 */ titlePadding?: number; /** 图标 */ - icon?: AllIds; + icon?: AllIdsWithNone; /** 最大宽度 */ width: number; } @@ -408,8 +411,8 @@ export const Textbox = defineComponent< titleElement.value?.requestBeforeFrame(() => { if (titleElement.value) { const { width, height } = titleElement.value; - tw.value = width + data.padding! * 2; - th.value = height + data.padding! * 2; + tw.value = width + data.titlePadding! * 2; + th.value = height + data.titlePadding! * 2; } }); }); @@ -481,7 +484,7 @@ export const Textbox = defineComponent< id={props.id} hidden={hidden.value} alpha={data.alpha} - loc={props.loc} + loc={data.loc} > {data.title && ( @@ -502,6 +505,7 @@ export const Textbox = defineComponent< fillStyle={data.titleFill} strokeStyle={data.titleStroke} font={data.titleFont} + strokeWidth={2} > )} @@ -520,7 +524,7 @@ export const Textbox = defineComponent< > )} {hasIcon.value && ( - + )} {hasIcon.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({ fillStyle: this.config.fillStyle, fontFamily: this.config.fontFamily, @@ -528,7 +534,8 @@ export class TextContentTyper extends EventEmitter { return; } this.emit('typeStart'); - this.lastTypeTime = Date.now(); + // 减去间隔是为了第一个字可以立刻打出来,不然看起来有延迟 + this.lastTypeTime = Date.now() - this.config.interval - 1; this.typing = true; } @@ -628,11 +635,11 @@ export class TextContentParser { * @param st 要设置为的状态,不填的表示不变 */ setStatus(st: Partial) { - if (!isNil(st.fillStyle)) this.status.fillStyle = st.fillStyle; - if (!isNil(st.fontSize)) this.status.fontSize = st.fontSize; - if (!isNil(st.fontFamily)) this.status.fontFamily = st.fontFamily; - if (!isNil(st.fontItalic)) this.status.fontItalic = st.fontItalic; - if (!isNil(st.fontWeight)) this.status.fontWeight = st.fontWeight; + if (!isNil(st.fillStyle)) this.initStatus.fillStyle = st.fillStyle; + if (!isNil(st.fontSize)) this.initStatus.fontSize = st.fontSize; + if (!isNil(st.fontFamily)) this.initStatus.fontFamily = st.fontFamily; + if (!isNil(st.fontItalic)) this.initStatus.fontItalic = st.fontItalic; + if (!isNil(st.fontWeight)) this.initStatus.fontWeight = st.fontWeight; } /** @@ -856,6 +863,7 @@ export class TextContentParser { this.font = this.buildFont(); this.resolved = ''; this.wordBreak = [0]; + this.wordBreakRule = this.config.wordBreak; this.nodePointer = 0; this.blockPointer = 0; this.nowNode = 0; @@ -975,7 +983,6 @@ export class TextContentParser { } this.addTextNode(text.length, false); - return this.splitLines(width); } @@ -1198,7 +1205,11 @@ export class TextContentParser { this.newLine(); const nextStart = this.wordBreak[index]; const nextEnd = this.wordBreak[end]; - this.bsStart = index; + if (index === this.bsStart) { + this.bsStart = this.bsStart + 1; + } else { + this.bsStart = index; + } this.bsEnd = end; const metrics = this.measure(node, nextStart, nextEnd); if (metrics.width < width) { @@ -1338,11 +1349,9 @@ export class TextContentParser { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (node.type === TextContentType.Text) { - this.wordBreak = [node.text.length]; + this.wordBreak = [0, node.text.length]; } - const pointer = - node.type === TextContentType.Text ? node.text.length : 1; - const block = this.generateBlock(node, pointer); + const block = this.generateBlock(node, 1); this.pushBlock(block, 1); } this.newLine(); diff --git a/packages-user/client-modules/src/render/elements/misc.ts b/packages-user/client-modules/src/render/elements/misc.ts index 548062f..811537d 100644 --- a/packages-user/client-modules/src/render/elements/misc.ts +++ b/packages-user/client-modules/src/render/elements/misc.ts @@ -66,7 +66,7 @@ export class Icon extends RenderItem implements IAnimateFrame { * 设置图标 * @param id 图标id */ - setIcon(id: AllIds | AllNumbers) { + setIcon(id: AllIdsWithNone | AllNumbers) { if (id === 0 || id === 'none') { this.renderable = void 0; return; diff --git a/packages-user/client-modules/src/render/ui/main.tsx b/packages-user/client-modules/src/render/ui/main.tsx index 0d16b86..055ba98 100644 --- a/packages-user/client-modules/src/render/ui/main.tsx +++ b/packages-user/client-modules/src/render/ui/main.tsx @@ -83,7 +83,7 @@ const MainScene = defineComponent(() => { font: new Font('normal'), titleFont: new Font('normal', 20, 'px', 700), winskin: 'winskin2.png', - interval: 100, + interval: 30, lineHeight: 4, width: MAP_WIDTH }; diff --git a/packages/render-elements/src/misc.ts b/packages/render-elements/src/misc.ts index 89d518f..a6e596e 100644 --- a/packages/render-elements/src/misc.ts +++ b/packages/render-elements/src/misc.ts @@ -52,6 +52,7 @@ export class Text extends RenderItem { ctx.strokeStyle = this.strokeStyle ?? 'transparent'; ctx.font = this.font.string(); ctx.lineWidth = this.strokeWidth; + ctx.lineJoin = 'round'; if (this.strokeStyle) { ctx.strokeText(this.text, stroke, this.descent + stroke + SAFE_PAD); diff --git a/public/_server/MotaAction.g4 b/public/_server/MotaAction.g4 index 24d9735..17c0243 100644 --- a/public/_server/MotaAction.g4 +++ b/public/_server/MotaAction.g4 @@ -1044,7 +1044,7 @@ setText_s tooltip : setText:设置文本的属性,颜色为RGB三元组或RGBA四元组,打字间隔为剧情文字添加的时间间隔,为整数或不填,字符间距为字符之间的距离,为整数或不填。 helpUrl : /_docs/#/instruction 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_1 = Bool_1 ? (', "keepLast": '+Bool_1) : ''; 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_4 = IntString_4 ? (', "fontSize": '+IntString_4) : ''; 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_8 = IntString_8? (', "strokeWidth": ' + IntString_8) : ''; IntString_9 = IntString_9? (', "padding": ' + IntString_9) : ''; diff --git a/public/libs/events.js b/public/libs/events.js index 1b77244..3f4052b 100644 --- a/public/libs/events.js +++ b/public/libs/events.js @@ -1579,7 +1579,15 @@ events.prototype._action_text = function (data) { loc[3] ??= 200; const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data; 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); core.events.nowTextbox = textbox; }; @@ -1600,7 +1608,15 @@ events.prototype._action_autoText = function (data) { loc[3] ??= 200; const { x = loc[0], y = loc[1], width = loc[2], height = loc[3] } = data; 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); setTimeout(() => { @@ -1623,10 +1639,11 @@ events.prototype._action__label = function (data, x, y, prefix) { }; 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 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); if (!store) { core.doAction(); @@ -1651,6 +1668,10 @@ events.prototype._action_setText = function (data) { }); // config const config = { + x, + y, + width, + height, loc: newLoc, font: newFont, keepLast: data.keepLast, @@ -1664,15 +1685,36 @@ events.prototype._action_setText = function (data) { backColor: data.backColor, winskin: data.winskin, padding: data.padding, - titleFill: isNil(data.titleFill), - titleStroke: !!data.titleStroke, + titleFill: isNil(data.titleFill) ? 'gold' : 'transparent', + titleStroke: data.titleStroke ? 'black' : 'transparent', titlePadding: data.titlePadding, - textAlign: data.textAlign, - wordBreak: data.wordBreak, ignoreLineStart: data.ignoreLineStart, ignoreLineEnd: data.ignoreLineEnd, 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); core.doAction(); diff --git a/src/types/declaration/source.d.ts b/src/types/declaration/source.d.ts index 60f71a2..a7ea8dc 100644 --- a/src/types/declaration/source.d.ts +++ b/src/types/declaration/source.d.ts @@ -25,7 +25,9 @@ type ItemCls = 'tools' | 'items' | 'equips' | 'constants'; /** * 所有的道具id */ -type AllIds = keyof IdToNumber | 'none'; +type AllIds = keyof IdToNumber; + +type AllIdsWithNone = AllIds | 'none'; /** * 所有的道具数字