import { DefaultProps, ElementLocator, Font, texture } from '@motajs/render'; import { logger } from '@motajs/common'; import { computed, defineComponent, onUnmounted, ref } from 'vue'; import { SetupComponentOptions } from './types'; import { transitioned } from '../use'; import { hyper } from 'mutate-animate'; import { debounce } from 'lodash-es'; export interface TipProps extends DefaultProps { loc: ElementLocator; pad?: [number, number]; corner?: number; id?: string; } export interface TipExpose { /** * 显示提示文本 * @param text 提示文字 * @param icon 显示的图标,不填则不显示 */ drawTip(text: string, icon?: AllIds | AllNumbers): void; } const tipProps = { props: ['loc', 'pad', 'corner', 'id'] } satisfies SetupComponentOptions; let id = 0; function getNextTipId() { return `@default-tip-${id++}`; } export const Tip = defineComponent((props, { expose }) => { const iconNum = ref(0); const text = ref(''); const textWidth = ref(0); const font = new Font('normal'); const alpha = transitioned(0, 500, hyper('sin', 'in-out'))!; const pad = computed(() => props.pad ?? [4, 4]); const locHeight = computed(() => props.loc[3] ?? 200); const hidden = computed(() => alpha.ref.value === 0); const showIcon = computed(() => iconNum.value !== 0); const iconSize = computed<[number, number]>(() => { const renderable = texture.getRenderable(iconNum.value); if (!renderable) return [1, 1]; const [, , width, height] = renderable.render[0]; return [width, height]; }); const iconLoc = computed(() => { const [width, height] = iconSize.value; const aspect = width / height; const realHeight = locHeight.value - pad.value[1] * 2; const realWidth = realHeight * aspect; return [pad.value[0], pad.value[1], realWidth, realHeight]; }); const textLoc = computed(() => { if (showIcon.value) { const [, , width] = iconLoc.value; const x = width! + pad.value[0] + pad.value[1]; return [x, locHeight.value / 2, void 0, void 0, 0, 0.5]; } else { return [pad.value[0], locHeight.value / 2, void 0, void 0, 0, 0.5]; } }); const containerLoc = computed(() => { const [x = 0, y = 0, , height = 200] = props.loc; const iconWidth = iconLoc.value[2] ?? 32; if (showIcon.value) { const width = textWidth.value + iconWidth + pad.value[0] * 2 + pad.value[1]; return [x, y, width, height]; } else { const width = textWidth.value + pad.value[0] * 2; return [x, y, width, height]; } }); const rectLoc = computed(() => { const [, , width = 200, height = 200] = containerLoc.value; return [1, 1, width - 2, height - 2]; }); const hide = debounce(() => { alpha.set(0); }, 3000); const drawTip = (tipText: string, iconId: AllIds | AllNumbers = 0) => { if (typeof iconId === 'string') { const num = texture.idNumberMap[iconId]; iconNum.value = num; } else { iconNum.value = iconId; } text.value = core.replaceText(tipText); alpha.set(0, 0); alpha.set(1); hide(); }; const onSetText = (_: string, width: number) => { textWidth.value = width; }; const ex: TipExpose = { drawTip }; TipStore.use(props.id ?? getNextTipId(), ex); expose(ex); return () => ( ); }, tipProps); export class TipStore { static list: Map = new Map(); private constructor(private readonly data: TipExpose) {} /** * 显示提示文本 * @param text 提示文字 * @param icon 显示的图标,不填则不显示 */ drawTip(text: string, icon: AllIds | AllNumbers = 0) { this.data.drawTip(text, icon); } static get(id: string) { return TipStore.list.get(id); } static use(id: string, data: TipExpose) { const store = new TipStore(data); if (this.list.has(id)) { logger.warn(60, id); } this.list.set(id, store); onUnmounted(() => { this.list.delete(id); }); return store; } }