mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-01 04:42:58 +08:00 
			
		
		
		
	feat: ConfirmBox 组件
This commit is contained in:
		
							parent
							
								
									e232a469c8
								
							
						
					
					
						commit
						0b2db0270c
					
				| @ -7,6 +7,9 @@ import { isNil } from 'lodash-es'; | ||||
| import { logger } from '@/core/common/logger'; | ||||
| import { IAnimateFrame, renderEmits } from '../frame'; | ||||
| 
 | ||||
| /** 文字的安全填充,会填充在文字的上侧和下侧,防止削顶和削底 */ | ||||
| const SAFE_PAD = 1; | ||||
| 
 | ||||
| type CanvasStyle = string | CanvasGradient | CanvasPattern; | ||||
| 
 | ||||
| export interface ETextEvent extends ERenderItemEvent { | ||||
| @ -31,8 +34,10 @@ export class Text extends RenderItem<ETextEvent> { | ||||
| 
 | ||||
|         this.text = text; | ||||
|         if (text.length > 0) { | ||||
|             this.calBox(); | ||||
|             this.emit('setText', text, this.width, this.height); | ||||
|             this.requestBeforeFrame(() => { | ||||
|                 this.calBox(); | ||||
|                 this.emit('setText', text, this.width, this.height); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -41,6 +46,7 @@ export class Text extends RenderItem<ETextEvent> { | ||||
|         _transform: Transform | ||||
|     ): void { | ||||
|         const ctx = canvas.ctx; | ||||
|         const stroke = this.strokeWidth; | ||||
|         ctx.textBaseline = 'bottom'; | ||||
|         ctx.fillStyle = this.fillStyle ?? 'transparent'; | ||||
|         ctx.strokeStyle = this.strokeStyle ?? 'transparent'; | ||||
| @ -48,10 +54,10 @@ export class Text extends RenderItem<ETextEvent> { | ||||
|         ctx.lineWidth = this.strokeWidth; | ||||
| 
 | ||||
|         if (this.strokeStyle) { | ||||
|             ctx.strokeText(this.text, 0, this.descent); | ||||
|             ctx.strokeText(this.text, stroke, this.descent + stroke + SAFE_PAD); | ||||
|         } | ||||
|         if (this.fillStyle) { | ||||
|             ctx.fillText(this.text, 0, this.descent); | ||||
|             ctx.fillText(this.text, stroke, this.descent + stroke + SAFE_PAD); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -95,6 +101,7 @@ export class Text extends RenderItem<ETextEvent> { | ||||
|     setStyle(fill?: CanvasStyle, stroke?: CanvasStyle) { | ||||
|         this.fillStyle = fill; | ||||
|         this.strokeStyle = stroke; | ||||
|         this.update(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -102,7 +109,11 @@ export class Text extends RenderItem<ETextEvent> { | ||||
|      * @param width 宽度 | ||||
|      */ | ||||
|     setStrokeWidth(width: number) { | ||||
|         const before = this.strokeWidth; | ||||
|         this.strokeWidth = width; | ||||
|         const dw = width - before; | ||||
|         this.size(this.width + dw * 2, this.height + dw * 2); | ||||
|         this.update(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -113,7 +124,8 @@ export class Text extends RenderItem<ETextEvent> { | ||||
|             this.measure(); | ||||
|         this.length = width; | ||||
|         this.descent = actualBoundingBoxAscent; | ||||
|         this.size(width, actualBoundingBoxAscent + actualBoundingBoxDescent); | ||||
|         const height = actualBoundingBoxAscent + actualBoundingBoxDescent; | ||||
|         this.size(width, height + this.strokeWidth * 2 + SAFE_PAD * 2); | ||||
|     } | ||||
| 
 | ||||
|     protected handleProps( | ||||
|  | ||||
| @ -564,10 +564,10 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { | ||||
|     private toTagString(item: RenderItem, space: number, deep: number): string { | ||||
|         const name = item.constructor.name; | ||||
|         if (item.children.size === 0) { | ||||
|             return `${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}"${item.hidden ? ' hidden' : ''}></${name}>\n`; | ||||
|             return `${' '.repeat(deep * space)}<${name} id="${item.id}" uid="${item.uid}" type="${item.type}"${item.hidden ? ' hidden' : ''}></${name}>\n`; | ||||
|         } else { | ||||
|             return ( | ||||
|                 `${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}" ${item.hidden ? 'hidden' : ''}>\n` + | ||||
|                 `${' '.repeat(deep * space)}<${name} id="${item.id}" uid="${item.uid}" type="${item.type}" ${item.hidden ? 'hidden' : ''}>\n` + | ||||
|                 `${[...item.children].map(v => this.toTagString(v, space, deep + 1)).join('')}` + | ||||
|                 `${' '.repeat(deep * space)}</${name}>\n` | ||||
|             ); | ||||
|  | ||||
| @ -1,18 +1,164 @@ | ||||
| import { DefaultProps } from '@/core/render'; | ||||
| import { defineComponent } from 'vue'; | ||||
| import { DefaultProps, ElementLocator, useKey } from '@/core/render'; | ||||
| import { computed, defineComponent, ref } from 'vue'; | ||||
| import { Background, Selection } from './misc'; | ||||
| import { TextContent, TextContentExpose, TextContentProps } from './textbox'; | ||||
| import { SetupComponentOptions } from './types'; | ||||
| import { TextAlign } from './textboxTyper'; | ||||
| 
 | ||||
| export interface ConfirmBoxProps extends DefaultProps { | ||||
| export interface ConfirmBoxProps extends DefaultProps, TextContentProps { | ||||
|     text: string; | ||||
|     width: number; | ||||
|     loc: ElementLocator; | ||||
|     selFont?: string; | ||||
|     selFill?: CanvasStyle; | ||||
|     pad?: number; | ||||
|     yesText?: string; | ||||
|     noText?: string; | ||||
|     winskin?: string; | ||||
|     winskin?: ImageIds; | ||||
|     defaultYes?: boolean; | ||||
|     color?: CanvasStyle; | ||||
|     border?: CanvasStyle; | ||||
| } | ||||
| 
 | ||||
| export interface ConfirmBoxEmits { | ||||
|     onYes: () => void; | ||||
|     onNo: () => void; | ||||
| } | ||||
| export type ConfirmBoxEmits = { | ||||
|     yes: () => void; | ||||
|     no: () => void; | ||||
| }; | ||||
| 
 | ||||
| export const ConfirmBox = defineComponent(() => { | ||||
|     return () => <container></container>; | ||||
| }); | ||||
| const confirmBoxProps = { | ||||
|     props: [ | ||||
|         'text', | ||||
|         'width', | ||||
|         'loc', | ||||
|         'selFont', | ||||
|         'selFill', | ||||
|         'pad', | ||||
|         'yesText', | ||||
|         'noText', | ||||
|         'winskin', | ||||
|         'defaultYes', | ||||
|         'color', | ||||
|         'border' | ||||
|     ], | ||||
|     emits: ['no', 'yes'] | ||||
| } satisfies SetupComponentOptions< | ||||
|     ConfirmBoxProps, | ||||
|     ConfirmBoxEmits, | ||||
|     keyof ConfirmBoxEmits | ||||
| >; | ||||
| 
 | ||||
| export const ConfirmBox = defineComponent< | ||||
|     ConfirmBoxProps, | ||||
|     ConfirmBoxEmits, | ||||
|     keyof ConfirmBoxEmits | ||||
| >((props, { emit, attrs }) => { | ||||
|     const content = ref<TextContentExpose>(); | ||||
|     const height = ref(200); | ||||
|     const selected = ref(props.defaultYes ? true : false); | ||||
|     const yesSize = ref<[number, number]>([0, 0]); | ||||
|     const noSize = ref<[number, number]>([0, 0]); | ||||
| 
 | ||||
|     const loc = computed<ElementLocator>(() => { | ||||
|         const [x = 0, y = 0, , , ax = 0, ay = 0] = props.loc; | ||||
|         return [x, y, props.width, height.value, ax, ay]; | ||||
|     }); | ||||
|     const yesText = computed(() => props.yesText ?? '确认'); | ||||
|     const noText = computed(() => props.noText ?? '取消'); | ||||
|     const pad = computed(() => props.pad ?? 32); | ||||
|     const yesLoc = computed<ElementLocator>(() => { | ||||
|         const y = height.value - pad.value; | ||||
|         return [props.width / 3, y, void 0, void 0, 0.5, 1]; | ||||
|     }); | ||||
|     const noLoc = computed<ElementLocator>(() => { | ||||
|         const y = height.value - pad.value; | ||||
|         return [(props.width / 3) * 2, y, void 0, void 0, 0.5, 1]; | ||||
|     }); | ||||
|     const contentLoc = computed<ElementLocator>(() => { | ||||
|         const width = props.width - pad.value * 2; | ||||
|         return [props.width / 2, pad.value, width, 0, 0.5, 0]; | ||||
|     }); | ||||
|     const selectLoc = computed<ElementLocator>(() => { | ||||
|         if (selected.value) { | ||||
|             const [x = 0, y = 0] = yesLoc.value; | ||||
|             const [width, height] = yesSize.value; | ||||
|             return [x, y + 4, width + 8, height + 8, 0.5, 1]; | ||||
|         } else { | ||||
|             const [x = 0, y = 0] = noLoc.value; | ||||
|             const [width, height] = noSize.value; | ||||
|             return [x, y + 4, width + 8, height + 8, 0.5, 1]; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     const onUpdateHeight = (textHeight: number) => { | ||||
|         height.value = textHeight + pad.value * 4; | ||||
|     }; | ||||
| 
 | ||||
|     const setYes = (_: string, width: number, height: number) => { | ||||
|         yesSize.value = [width, height]; | ||||
|     }; | ||||
| 
 | ||||
|     const setNo = (_: string, width: number, height: number) => { | ||||
|         noSize.value = [width, height]; | ||||
|     }; | ||||
| 
 | ||||
|     const [key] = useKey(); | ||||
|     key.realize('confirm', () => { | ||||
|         if (selected.value) emit('yes'); | ||||
|         else emit('no'); | ||||
|     }); | ||||
|     key.realize('moveLeft', () => void (selected.value = true)); | ||||
|     key.realize('moveRight', () => void (selected.value = false)); | ||||
| 
 | ||||
|     return () => ( | ||||
|         <container loc={loc.value}> | ||||
|             <Background | ||||
|                 loc={[0, 0, props.width, height.value]} | ||||
|                 winskin={props.winskin} | ||||
|                 color={props.color} | ||||
|                 border={props.border} | ||||
|                 zIndex={0} | ||||
|             /> | ||||
|             <TextContent | ||||
|                 {...attrs} | ||||
|                 ref={content} | ||||
|                 loc={contentLoc.value} | ||||
|                 text={props.text} | ||||
|                 width={props.width - pad.value * 2} | ||||
|                 zIndex={5} | ||||
|                 textAlign={TextAlign.Center} | ||||
|                 autoHeight | ||||
|                 onUpdateHeight={onUpdateHeight} | ||||
|             /> | ||||
|             <Selection | ||||
|                 loc={selectLoc.value} | ||||
|                 winskin={props.winskin} | ||||
|                 color={props.color} | ||||
|                 border={props.border} | ||||
|                 noevent | ||||
|                 zIndex={10} | ||||
|             /> | ||||
|             <text | ||||
|                 loc={yesLoc.value} | ||||
|                 text={yesText.value} | ||||
|                 fillStyle={props.selFill} | ||||
|                 font={props.selFont} | ||||
|                 cursor="pointer" | ||||
|                 zIndex={15} | ||||
|                 onClick={() => emit('yes')} | ||||
|                 onEnter={() => (selected.value = true)} | ||||
|                 onSetText={setYes} | ||||
|             /> | ||||
|             <text | ||||
|                 loc={noLoc.value} | ||||
|                 text={noText.value} | ||||
|                 fillStyle={props.selFill} | ||||
|                 font={props.selFont} | ||||
|                 cursor="pointer" | ||||
|                 zIndex={15} | ||||
|                 onClick={() => emit('no')} | ||||
|                 onEnter={() => (selected.value = false)} | ||||
|                 onSetText={setNo} | ||||
|             /> | ||||
|         </container> | ||||
|     ); | ||||
| }, confirmBoxProps); | ||||
|  | ||||
| @ -212,7 +212,7 @@ export const ScrollText = defineComponent< | ||||
|     const scroll = ref<ScrollExpose>(); | ||||
|     const speed = ref(props.speed); | ||||
| 
 | ||||
|     const eleHeight = computed(() => props.loc[3] ?? props.height ?? 200); | ||||
|     const eleHeight = computed(() => props.loc[3] ?? props.width); | ||||
|     const pad = computed(() => props.pad ?? 16); | ||||
| 
 | ||||
|     let lastFixedTime = Date.now(); | ||||
| @ -370,6 +370,15 @@ const backgroundProps = { | ||||
|     props: ['loc', 'winskin', 'color', 'border'] | ||||
| } satisfies SetupComponentOptions<BackgroundProps>; | ||||
| 
 | ||||
| /** | ||||
|  * 背景组件,与 Selection 类似,不过绘制的是背景,而不是选择光标,参数参考 {@link BackgroundProps},用例如下: | ||||
|  * ```tsx
 | ||||
|  * // 使用 winskin2.png 作为背景
 | ||||
|  * <Background loc={[8, 8, 160, 160]} winskin="winskin2.png" /> | ||||
|  * // 使用指定填充和边框颜色作为背景
 | ||||
|  * <Background loc={[8, 8, 160, 160]} color="#333" border="gold" /> | ||||
|  * ``` | ||||
|  */ | ||||
| export const Background = defineComponent<BackgroundProps>(props => { | ||||
|     const isWinskin = computed(() => !!props.winskin); | ||||
|     const fixedLoc = computed<ElementLocator>(() => { | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { | ||||
|     computed, | ||||
|     defineComponent, | ||||
|     nextTick, | ||||
|     onMounted, | ||||
|     onUnmounted, | ||||
|     ref, | ||||
|     shallowReactive, | ||||
| @ -46,6 +47,7 @@ export interface TextContentProps | ||||
| export type TextContentEmits = { | ||||
|     typeEnd: () => void; | ||||
|     typeStart: () => void; | ||||
|     updateHeight: (height: number) => void; | ||||
| }; | ||||
| 
 | ||||
| export interface TextContentExpose { | ||||
| @ -89,7 +91,7 @@ const textContentOptions = { | ||||
|         'width', | ||||
|         'autoHeight' | ||||
|     ], | ||||
|     emits: ['typeEnd', 'typeStart'] | ||||
|     emits: ['typeEnd', 'typeStart', 'updateHeight'] | ||||
| } satisfies SetupComponentOptions< | ||||
|     TextContentProps, | ||||
|     TextContentEmits, | ||||
| @ -144,10 +146,12 @@ export const TextContent = defineComponent< | ||||
|     }; | ||||
| 
 | ||||
|     const updateLoc = () => { | ||||
|         const height = getHeight(); | ||||
|         if (props.autoHeight) { | ||||
|             const [x = 0, y = 0, width = 200, , ax = 0, ay = 0] = loc.value; | ||||
|             loc.value = [x, y, width, getHeight(), ax, ay]; | ||||
|             loc.value = [x, y, width, height, ax, ay]; | ||||
|         } | ||||
|         emit('updateHeight', height); | ||||
|     }; | ||||
| 
 | ||||
|     expose<TextContentExpose>({ retype, showAll, getHeight }); | ||||
| @ -198,6 +202,8 @@ export const TextContent = defineComponent< | ||||
|         emit('typeEnd'); | ||||
|     }); | ||||
| 
 | ||||
|     onMounted(retype); | ||||
| 
 | ||||
|     return () => { | ||||
|         return ( | ||||
|             <sprite | ||||
|  | ||||
| @ -9,6 +9,9 @@ import { | ||||
| import EventEmitter from 'eventemitter3'; | ||||
| import { isNil } from 'lodash-es'; | ||||
| 
 | ||||
| /** 文字的安全填充,会填充在文字的上侧和下侧,防止削顶和削底 */ | ||||
| const SAFE_PAD = 1; | ||||
| 
 | ||||
| export const enum WordBreak { | ||||
|     /** 不换行 */ | ||||
|     None, | ||||
| @ -110,6 +113,8 @@ export interface ITextContentRenderable { | ||||
| export interface ITextContentRenderObject { | ||||
|     /** 每一行的高度 */ | ||||
|     lineHeights: number[]; | ||||
|     /** 每一行的宽度 */ | ||||
|     lineWidths: number[]; | ||||
|     /** 渲染数据 */ | ||||
|     data: ITextContentRenderable[]; | ||||
| } | ||||
| @ -173,7 +178,8 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
|     /** 渲染信息 */ | ||||
|     private renderObject: ITextContentRenderObject = { | ||||
|         lineHeights: [], | ||||
|         data: [] | ||||
|         data: [], | ||||
|         lineWidths: [] | ||||
|     }; | ||||
|     /** 渲染信息 */ | ||||
|     private renderData: TyperRenderable[] = []; | ||||
| @ -239,7 +245,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
|     getHeight() { | ||||
|         const heights = this.renderObject.lineHeights; | ||||
|         const lines = heights.reduce((prev, curr) => prev + curr, 0); | ||||
|         return lines + this.config.lineHeight * heights.length; | ||||
|         return lines + this.config.lineHeight * heights.length + SAFE_PAD * 2; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -275,7 +281,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
|         this.typing = false; | ||||
|         this.dataLine = 0; | ||||
|         this.x = 0; | ||||
|         this.y = 0; | ||||
|         this.y = SAFE_PAD; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -288,6 +294,19 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
|         this.renderObject = this.parser.parse(text, this.config.width); | ||||
|     } | ||||
| 
 | ||||
|     private getDataX(line: number) { | ||||
|         const width = this.renderObject.lineWidths[line]; | ||||
|         if (isNil(width)) return this.x; | ||||
|         switch (this.config.textAlign) { | ||||
|             case TextAlign.Left: | ||||
|                 return this.x; | ||||
|             case TextAlign.Center: | ||||
|                 return this.x + (this.config.width - width) / 2; | ||||
|             case TextAlign.End: | ||||
|                 return this.x + this.config.width - width; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private createTyperData(index: number, line: number) { | ||||
|         const renderable = this.renderObject.data[index]; | ||||
|         if (!renderable) return false; | ||||
| @ -303,7 +322,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
| 
 | ||||
|                 const data: TyperTextRenderable = { | ||||
|                     type: TextContentType.Text, | ||||
|                     x: this.x, | ||||
|                     x: this.getDataX(line), | ||||
|                     y: this.y, | ||||
|                     text: renderable.text.slice(start, end), | ||||
|                     font: renderable.font, | ||||
| @ -331,7 +350,7 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> { | ||||
|                 } | ||||
|                 const data: TyperIconRenderable = { | ||||
|                     type: TextContentType.Icon, | ||||
|                     x: this.x, | ||||
|                     x: this.getDataX(line), | ||||
|                     y: this.y, | ||||
|                     width: iconWidth, | ||||
|                     height: iconWidth / aspect, | ||||
| @ -537,6 +556,8 @@ export class TextContentParser { | ||||
|     private lineHeight: number = 0; | ||||
|     /** 每一行的行高 */ | ||||
|     private lineHeights: number[] = []; | ||||
|     /** 每一行的宽度 */ | ||||
|     private lineWidths: number[] = []; | ||||
|     /** 当前这一行已经有多长 */ | ||||
|     private lineWidth: number = 0; | ||||
|     /** 这一行未计算部分的起始位置索引 */ | ||||
| @ -809,6 +830,7 @@ export class TextContentParser { | ||||
|         this.nowRenderable = -1; | ||||
|         this.lineHeight = 0; | ||||
|         this.lineHeights = []; | ||||
|         this.lineWidths = []; | ||||
|         this.lineWidth = 0; | ||||
|         this.lineStart = 0; | ||||
|         this.guessGain = 1; | ||||
| @ -967,6 +989,7 @@ export class TextContentParser { | ||||
|                 const index = this.bsLineWidth(maxWidth, this.nowRenderable); | ||||
|                 data.splitLines.push(this.wordBreak[index]); | ||||
|                 this.lineHeights.push(this.lineHeight); | ||||
|                 this.lineWidths.push(this.lineWidth); | ||||
|                 this.bsStart = index; | ||||
|                 const text = data.text.slice( | ||||
|                     this.wordBreak[index] + 1, | ||||
| @ -990,10 +1013,11 @@ export class TextContentParser { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private bsLineWidth(width: number, index: number) { | ||||
|     private bsLineWidth(maxWidth: number, index: number) { | ||||
|         let start = this.bsStart; | ||||
|         let end = this.bsEnd; | ||||
|         let height = 0; | ||||
|         let width = 0; | ||||
| 
 | ||||
|         const data = this.renderable[index]; | ||||
|         const { wordBreak } = data; | ||||
| @ -1005,6 +1029,7 @@ export class TextContentParser { | ||||
|                 if (height > this.lineHeight) { | ||||
|                     this.lineHeight = height; | ||||
|                 } | ||||
|                 this.lineWidth = width; | ||||
|                 return start; | ||||
|             } | ||||
|             const text = data.text.slice( | ||||
| @ -1012,10 +1037,12 @@ export class TextContentParser { | ||||
|                 wordBreak[mid] + 1 | ||||
|             ); | ||||
|             const metrics = ctx.measureText(text); | ||||
|             width = metrics.width; | ||||
|             height = this.getHeight(metrics); | ||||
|             if (metrics.width > width) { | ||||
|             if (width > maxWidth) { | ||||
|                 end = mid; | ||||
|             } else if (metrics.width === width) { | ||||
|             } else if (width === maxWidth) { | ||||
|                 this.lineWidth = width; | ||||
|                 if (height > this.lineHeight) { | ||||
|                     this.lineHeight = height; | ||||
|                 } | ||||
| @ -1064,6 +1091,7 @@ export class TextContentParser { | ||||
|                     const index = this.bsLineWidth(maxWidth, pointer); | ||||
|                     data.splitLines.push(this.wordBreak[index]); | ||||
|                     this.lineHeights.push(this.lineHeight); | ||||
|                     this.lineWidths.push(this.lineWidth); | ||||
|                     this.bsStart = index; | ||||
|                     const text = data.text.slice(this.wordBreak[index] + 1); | ||||
|                     if (!isLast && text.length < guess / 4) { | ||||
| @ -1089,9 +1117,9 @@ export class TextContentParser { | ||||
|             let iconWidth = 0; | ||||
|             if (aspect < 1) { | ||||
|                 // 这时候应该把高度限定在当前字体大小
 | ||||
|                 iconWidth = width * (this.status.fontSize / height); | ||||
|                 iconWidth = width * (data.fontSize / height); | ||||
|             } else { | ||||
|                 iconWidth = this.status.fontSize; | ||||
|                 iconWidth = data.fontSize; | ||||
|             } | ||||
|             this.lineWidth += iconWidth; | ||||
|             const iconHeight = iconWidth / aspect; | ||||
| @ -1109,13 +1137,69 @@ export class TextContentParser { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private checkLastSize() { | ||||
|         const last = this.renderable.at(-1); | ||||
|         if (!last) return; | ||||
|         const index = this.lastBreakIndex; | ||||
|         const text = last.text.slice(this.wordBreak[index] + 1); | ||||
|         const ctx = this.testCanvas.ctx; | ||||
|         ctx.font = last.font; | ||||
|         const metrics = ctx.measureText(text); | ||||
|         this.lineWidth = metrics.width; | ||||
|         const height = this.getHeight(metrics); | ||||
|         if (height > this.lineHeight) { | ||||
|             this.lineHeight = height; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private checkNoneBreakSize() { | ||||
|         const ctx = this.testCanvas.ctx; | ||||
|         this.renderable.forEach(data => { | ||||
|             switch (data.type) { | ||||
|                 case TextContentType.Text: { | ||||
|                     ctx.font = data.font; | ||||
|                     const metrics = ctx.measureText(data.text); | ||||
|                     this.lineWidth += metrics.width; | ||||
|                     const height = this.getHeight(metrics); | ||||
|                     if (height > this.lineHeight) this.lineHeight = height; | ||||
|                     break; | ||||
|                 } | ||||
|                 case TextContentType.Icon: { | ||||
|                     const renderable = texture.getRenderable(data.icon!); | ||||
|                     if (!renderable) return false; | ||||
|                     const [, , width, height] = renderable.render[0]; | ||||
|                     const aspect = width / height; | ||||
|                     let iconWidth = 0; | ||||
|                     if (aspect < 1) { | ||||
|                         // 这时候应该把高度限定在当前字体大小
 | ||||
|                         iconWidth = width * (data.fontSize / height); | ||||
|                     } else { | ||||
|                         iconWidth = data.fontSize; | ||||
|                     } | ||||
|                     this.lineWidth += iconWidth; | ||||
|                     const iconHeight = iconWidth / aspect; | ||||
|                     if (iconHeight > this.lineHeight) { | ||||
|                         this.lineHeight = iconHeight; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         this.lineHeights.push(this.lineHeight); | ||||
|         this.lineWidths.push(this.lineWidth); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 对解析出的文字分词并分行 | ||||
|      * @param width 文字的宽度,到达这么宽之后换行 | ||||
|      */ | ||||
|     private splitLines(width: number): ITextContentRenderObject { | ||||
|         if (this.wordBreakRule === WordBreak.None) { | ||||
|             return { lineHeights: [0], data: this.renderable }; | ||||
|             this.checkNoneBreakSize(); | ||||
|             return { | ||||
|                 lineHeights: this.lineHeights, | ||||
|                 data: this.renderable, | ||||
|                 lineWidths: this.lineWidths | ||||
|             }; | ||||
|         } | ||||
|         this.nowRenderable = -1; | ||||
| 
 | ||||
| @ -1182,11 +1266,14 @@ export class TextContentParser { | ||||
|             this.checkRestLine(width, guess, i); | ||||
|         } | ||||
| 
 | ||||
|         this.checkLastSize(); | ||||
|         this.lineHeights.push(this.lineHeight); | ||||
|         this.lineWidths.push(this.lineWidth); | ||||
| 
 | ||||
|         return { | ||||
|             lineHeights: this.lineHeights, | ||||
|             data: this.renderable | ||||
|             data: this.renderable, | ||||
|             lineWidths: this.lineWidths | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user