diff --git a/public/libs/ui.js b/public/libs/ui.js index 4b731a7..ba4c14e 100644 --- a/public/libs/ui.js +++ b/public/libs/ui.js @@ -944,60 +944,11 @@ ui.prototype.clearUI = function () { ////// 左上角绘制一段提示 ////// ui.prototype.drawTip = function (text, id, frame) { - text = core.replaceText(text) || ''; - var realText = this._getRealContent(text); - var one = { - text: text, - textX: 21, - width: 26 + core.calWidth('data', realText, '16px Arial'), - opacity: 0.1, - stage: 1, - frame: frame || 0, - time: 0 - }; - if (id != null) { - var info = core.getBlockInfo(id); - if (info == null || !info.image || info.bigImage) { - // 检查状态栏图标 - if (core.statusBar.icons[id] instanceof Image) { - info = { - image: core.statusBar.icons[id], - posX: 0, - posY: 0, - height: 32 - }; - } else info = null; - } - if (info != null) { - one.image = info.image; - one.posX = info.posX; - one.posY = info.posY; - one.height = info.height; - one.textX += 24; - one.width += 24; - } - } - core.animateFrame.tip = one; + // Deprecated. Fallback in modules/fallback/ui.ts }; ui.prototype._drawTip_drawOne = function (tip) { - core.setAlpha('data', tip.opacity); - core.fillRect('data', 5, 5, tip.width, 42, '#000000'); - if (tip.image) - core.drawImage( - 'data', - tip.image, - (tip.posX + tip.frame) * 32, - tip.posY * tip.height, - 32, - 32, - 10, - 10, - 32, - 32 - ); - core.fillText('data', tip.text, tip.textX, 33, '#FFF', '16px normal'); - core.setAlpha('data', 1); + // Deprecated. Fallback in modules/fallback/ui.ts }; ////// 地图中间绘制一段文字 ////// @@ -1137,8 +1088,8 @@ ui.prototype._getPosition = function (content) { py == null ? 'center' : py > core._HALF_HEIGHT_ - ? 'up' - : 'down'; + ? 'up' + : 'down'; } return ''; } @@ -2274,7 +2225,8 @@ ui.prototype._drawTextBox_getHorizontalPosition = function ( paddingRight = 12; if ((posInfo.px != null && posInfo.py != null) || posInfo.pos) paddingLeft = 20; - if (titleInfo.icon != null) paddingLeft = 62; // 15 + 32 + 15 + if (titleInfo.icon != null) + paddingLeft = 62; // 15 + 32 + 15 else if (titleInfo.image) paddingLeft = 90; // 10 + 70 + 10 var left = 7 + 3 * (core._HALF_WIDTH_ - 6), right = core._PX_ - left, @@ -3044,8 +2996,8 @@ ui.prototype._drawSwitchs_display = function () { (core.flags.extraDamageType == 2 ? '[最简]' : core.flags.extraDamageType == 1 - ? '[半透明]' - : '[完整]'), + ? '[半透明]' + : '[完整]'), '自动放缩: ' + (core.getLocalStorage('autoScale') ? '[ON]' : '[OFF]'), '返回上一级' ]; @@ -3710,8 +3662,8 @@ ui.prototype._drawSLPanel_drawRecords = function (n) { core.status.event.id == 'save' ? '存档' : core.status.event.id == 'load' - ? '读档' - : '回放'; + ? '读档' + : '回放'; for (var i = 0; i < (n || 6); i++) { var data = core.status.event.ui[i]; diff --git a/src/core/render/preset/misc.ts b/src/core/render/preset/misc.ts index b233a50..6503a92 100644 --- a/src/core/render/preset/misc.ts +++ b/src/core/render/preset/misc.ts @@ -10,7 +10,7 @@ import { IAnimateFrame, renderEmits } from '../frame'; type CanvasStyle = string | CanvasGradient | CanvasPattern; export interface ETextEvent extends ERenderItemEvent { - setText: [text: string]; + setText: [text: string, width: number, height: number]; } export class Text extends RenderItem { @@ -32,7 +32,7 @@ export class Text extends RenderItem { this.text = text; if (text.length > 0) { this.calBox(); - this.emit('setText', text); + this.emit('setText', text, this.width, this.height); } } @@ -74,7 +74,7 @@ export class Text extends RenderItem { this.text = text; this.calBox(); this.update(this); - this.emit('setText', text); + this.emit('setText', text, this.width, this.height); } /** @@ -113,11 +113,7 @@ export class Text extends RenderItem { this.measure(); this.length = width; this.descent = actualBoundingBoxAscent; - this.size( - width, - Math.abs(actualBoundingBoxAscent) + - Math.abs(actualBoundingBoxDescent) - ); + this.size(width, actualBoundingBoxAscent + actualBoundingBoxDescent); } protected handleProps( @@ -277,6 +273,10 @@ export class Icon extends RenderItem implements IAnimateFrame { * @param id 图标id */ setIcon(id: AllIds | AllNumbers) { + if (id === 0) { + this.renderable = void 0; + return; + } const num = typeof id === 'number' ? id : texture.idNumberMap[id]; const loading = Mota.require('var', 'loading'); diff --git a/src/data/logger.json b/src/data/logger.json index 21d18f8..2dff98f 100644 --- a/src/data/logger.json +++ b/src/data/logger.json @@ -91,6 +91,7 @@ "57": "Repeated UI controller on item '$1', new controller will not work.", "58": "Fail to set ellipse round rect, since length of 'ellipse' property should only be 2, 4, 6 or 8. delivered: $1", "59": "Unknown icon '$1' in parsing text content.", + "60": "Repeated Tip id: '$1'.", "1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.", "1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance." } diff --git a/src/module/fallback/index.ts b/src/module/fallback/index.ts index cc0dd32..a4b2277 100644 --- a/src/module/fallback/index.ts +++ b/src/module/fallback/index.ts @@ -1,10 +1,12 @@ import { Patch } from '@/common/patch'; import { patchAudio } from './audio'; import { patchWeather } from './weather'; +import { patchUI } from './ui'; export function patchAll() { patchAudio(); patchWeather(); + patchUI(); const loading = Mota.require('var', 'loading'); loading.once('coreInit', () => { Patch.patchAll(); diff --git a/src/module/fallback/ui.ts b/src/module/fallback/ui.ts new file mode 100644 index 0000000..6b88af5 --- /dev/null +++ b/src/module/fallback/ui.ts @@ -0,0 +1,11 @@ +import { Patch, PatchClass } from '@/common/patch'; +import { TipStore } from '../render/components/tip'; + +export function patchUI() { + const patch = new Patch(PatchClass.UI); + + patch.add('drawTip', function (text, id) { + const tip = TipStore.get('main-tip'); + tip?.drawTip(text, id); + }); +} diff --git a/src/module/render/components/textboxTyper.ts b/src/module/render/components/textboxTyper.ts index c8ede72..362922f 100644 --- a/src/module/render/components/textboxTyper.ts +++ b/src/module/render/components/textboxTyper.ts @@ -909,8 +909,7 @@ export class TextContentParser { private getHeight(metrics: TextMetrics) { return ( - Math.abs(metrics.actualBoundingBoxAscent) + - Math.abs(metrics.actualBoundingBoxDescent) + metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent ); } diff --git a/src/module/render/components/tip.tsx b/src/module/render/components/tip.tsx new file mode 100644 index 0000000..87b149e --- /dev/null +++ b/src/module/render/components/tip.tsx @@ -0,0 +1,169 @@ +import { DefaultProps, ElementLocator, texture } from '@/core/render'; +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'; +import { logger } from '@/core/common/logger'; + +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 = '16px 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; + } +} diff --git a/src/module/render/ui/main.tsx b/src/module/render/ui/main.tsx index 9a71dd9..4fc8368 100644 --- a/src/module/render/ui/main.tsx +++ b/src/module/render/ui/main.tsx @@ -34,6 +34,7 @@ import { } from './statusBar'; import { onLoaded } from '../use'; import { ReplayingStatus } from './toolbar'; +import { Tip } from '../components/tip'; const MainScene = defineComponent(() => { const layerGroupExtends: ILayerGroupRenderExtends[] = [ @@ -187,6 +188,13 @@ const MainScene = defineComponent(() => { + {loaded.value && ( diff --git a/src/module/render/use.ts b/src/module/render/use.ts index 4533689..0e75432 100644 --- a/src/module/render/use.ts +++ b/src/module/render/use.ts @@ -70,7 +70,7 @@ export function onLoaded(hook: () => void) { export interface ITransitionedController { readonly ref: Ref; readonly value: T; - set(value: T): void; + set(value: T, time?: number): void; } class RenderTransition implements ITransitionedController { @@ -100,11 +100,8 @@ class RenderTransition implements ITransitionedController { }); } - set(value: number): void { - this.transition - .time(this.time) - .mode(this.curve) - .transition(this.key, value); + set(value: number, time: number = this.time): void { + this.transition.time(time).mode(this.curve).transition(this.key, value); } } @@ -144,14 +141,14 @@ class RenderColorTransition implements ITransitionedController { }); } - set(value: string): void { - this.transitionColor(this.decodeColor(value)); + set(value: string, time: number = this.time): void { + this.transitionColor(this.decodeColor(value), time); } - private transitionColor([r, g, b, a]: ColorRGBA) { + private transitionColor([r, g, b, a]: ColorRGBA, time: number) { this.transition .mode(this.curve) - .time(this.time) + .time(time) .transition(this.keyR, r) .transition(this.keyG, g) .transition(this.keyB, b)