refactor: drawTip

This commit is contained in:
unanmed 2025-02-26 20:16:04 +08:00
parent d0dae40a5a
commit d2b7a5d430
9 changed files with 217 additions and 78 deletions

View File

@ -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];

View File

@ -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<ETextEvent> {
@ -32,7 +32,7 @@ export class Text extends RenderItem<ETextEvent> {
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<ETextEvent> {
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<ETextEvent> {
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<EIconEvent> 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');

View File

@ -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."
}

View File

@ -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();

11
src/module/fallback/ui.ts Normal file
View File

@ -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);
});
}

View File

@ -909,8 +909,7 @@ export class TextContentParser {
private getHeight(metrics: TextMetrics) {
return (
Math.abs(metrics.actualBoundingBoxAscent) +
Math.abs(metrics.actualBoundingBoxDescent)
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
);
}

View File

@ -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<TipProps>;
let id = 0;
function getNextTipId() {
return `@default-tip-${id++}`;
}
export const Tip = defineComponent<TipProps>((props, { expose }) => {
const iconNum = ref<AllNumbers>(0);
const text = ref<string>('');
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<ElementLocator>(() => {
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<ElementLocator>(() => {
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<ElementLocator>(() => {
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<ElementLocator>(() => {
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<TipExpose>(ex);
return () => (
<container
loc={containerLoc.value}
alpha={alpha.ref.value}
hidden={hidden.value}
noevent
>
<g-rectr
loc={rectLoc.value}
circle={[props.corner ?? 4]}
fill
fillStyle="rgba(40,40,40,0.8)"
/>
<icon
hidden={!showIcon.value}
icon={iconNum.value}
loc={iconLoc.value}
/>
<text
loc={textLoc.value}
text={text.value}
onSetText={onSetText}
font={font}
/>
</container>
);
}, tipProps);
export class TipStore {
static list: Map<string, TipStore> = 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;
}
}

View File

@ -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(() => {
</layer-group>
<Textbox id="main-textbox" {...mainTextboxProps}></Textbox>
<FloorChange id="floor-change" zIndex={50}></FloorChange>
<Tip
id="main-tip"
zIndex={80}
loc={[8, 8, 200, 32]}
pad={[12, 6]}
corner={16}
/>
</container>
<g-line line={[180 + 480, 0, 180 + 480, 480]} lineWidth={1} />
{loaded.value && (

View File

@ -70,7 +70,7 @@ export function onLoaded(hook: () => void) {
export interface ITransitionedController<T> {
readonly ref: Ref<T>;
readonly value: T;
set(value: T): void;
set(value: T, time?: number): void;
}
class RenderTransition implements ITransitionedController<number> {
@ -100,11 +100,8 @@ class RenderTransition implements ITransitionedController<number> {
});
}
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<string> {
});
}
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)