mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-24 16:13:24 +08:00
Compare commits
6 Commits
d0dae40a5a
...
c505efeb66
Author | SHA1 | Date | |
---|---|---|---|
c505efeb66 | |||
baec3d5429 | |||
87c7e0909b | |||
9e05bb3287 | |||
310e597c58 | |||
d2b7a5d430 |
@ -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];
|
||||
|
@ -351,6 +351,13 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region debug
|
||||
|
||||
/** 是否需要禁用更新,如果出现更新,那么发出警告并停止更新操作 */
|
||||
private forbidUpdate: boolean = false;
|
||||
|
||||
//#endregion
|
||||
|
||||
constructor(
|
||||
type: RenderItemPosition,
|
||||
enableCache: boolean = true,
|
||||
@ -392,6 +399,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
*/
|
||||
renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||
if (this.hidden) return;
|
||||
this.forbidUpdate = true;
|
||||
this.emit('beforeRender', transform);
|
||||
if (this.transformFallThrough) {
|
||||
this.fallTransform = transform;
|
||||
@ -425,6 +433,7 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
}
|
||||
ctx.restore();
|
||||
this.emit('afterRender', transform);
|
||||
this.forbidUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -476,7 +485,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
*/
|
||||
setFilter(filter: string) {
|
||||
this.filter = filter;
|
||||
this.update(this);
|
||||
// 设置滤镜时,不需要更新自身的缓存,直接调用父元素的更新即可
|
||||
this._parent?.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -485,7 +495,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
*/
|
||||
setComposite(composite: GlobalCompositeOperation) {
|
||||
this.composite = composite;
|
||||
this.update();
|
||||
// 设置混合模式时,不需要更新自身的缓存,直接调用父元素的更新即可
|
||||
this._parent?.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -494,7 +505,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
*/
|
||||
setAlpha(alpha: number) {
|
||||
this.alpha = alpha;
|
||||
this.update();
|
||||
// 设置不透明度时,不需要更新自身的缓存,直接调用父元素的更新即可
|
||||
this._parent?.update();
|
||||
}
|
||||
|
||||
setHD(hd: boolean): void {
|
||||
@ -521,7 +533,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
setAnchor(x: number, y: number): void {
|
||||
this.anchorX = x;
|
||||
this.anchorY = y;
|
||||
this.update();
|
||||
// 设置锚点时,不需要更新自身的缓存,直接调用父元素的更新即可
|
||||
this._parent?.update();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -597,6 +610,11 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
}
|
||||
|
||||
update(item: RenderItem<any> = this): void {
|
||||
if (import.meta.env.DEV) {
|
||||
if (this.forbidUpdate) {
|
||||
logger.warn(61, this.constructor.name);
|
||||
}
|
||||
}
|
||||
if (this._parent) {
|
||||
if (this.cacheDirty && this._parent.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
@ -609,7 +627,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
}
|
||||
|
||||
updateTransform() {
|
||||
this.update();
|
||||
// 更新变换矩阵时,不需要更新自身的缓存,直接调用父元素的更新即可
|
||||
this._parent?.update();
|
||||
this.emit('transform', this, this._transform);
|
||||
}
|
||||
|
||||
|
@ -313,7 +313,7 @@ export abstract class GraphicItemBase
|
||||
this.checkMode(GraphicModeProp.Stroke, nextValue);
|
||||
return true;
|
||||
case 'strokeAndFill':
|
||||
if (!this.assertType(nextValue, 'number', key)) return false;
|
||||
if (!this.assertType(nextValue, 'boolean', key)) return false;
|
||||
this.checkMode(GraphicModeProp.StrokeAndFill, nextValue);
|
||||
return true;
|
||||
case 'fillRule':
|
||||
|
@ -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');
|
||||
|
@ -91,6 +91,8 @@
|
||||
"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'.",
|
||||
"61": "Unexpected recursive call of $1.update in render function. Please ensure you must do this, if you do, ignore this warn.",
|
||||
"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."
|
||||
}
|
||||
|
@ -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
11
src/module/fallback/ui.ts
Normal 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);
|
||||
});
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { DefaultProps, ElementLocator, Sprite } from '@/core/render';
|
||||
import { defineComponent, ref, watch } from 'vue';
|
||||
import { DefaultProps, ElementLocator, PathProps, Sprite } from '@/core/render';
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import { SetupComponentOptions } from './types';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
|
||||
@ -53,3 +53,57 @@ export const Progress = defineComponent<ProgressProps>(props => {
|
||||
return <sprite ref={element} loc={props.loc} render={render}></sprite>;
|
||||
};
|
||||
}, progressProps);
|
||||
|
||||
export interface ArrowProps extends PathProps {
|
||||
/** 箭头的四个坐标 */
|
||||
arrow: [number, number, number, number];
|
||||
/** 箭头的头部大小 */
|
||||
head?: number;
|
||||
/** 箭头的颜色 */
|
||||
color?: CanvasStyle;
|
||||
}
|
||||
|
||||
const arrowProps = {
|
||||
props: ['arrow', 'head', 'color']
|
||||
} satisfies SetupComponentOptions<ArrowProps>;
|
||||
|
||||
export const Arrow = defineComponent<ArrowProps>(props => {
|
||||
const loc = computed<ElementLocator>(() => {
|
||||
const [x1, y1, x2, y2] = props.arrow;
|
||||
const left = Math.min(x1, x2);
|
||||
const right = Math.max(x1, x2);
|
||||
const top = Math.min(y1, y2);
|
||||
const bottom = Math.max(y1, y2);
|
||||
return [left, top, right - left, bottom - top];
|
||||
});
|
||||
const path = computed(() => {
|
||||
const path = new Path2D();
|
||||
const head = props.head ?? 8;
|
||||
const [x = 0, y = 0] = loc.value;
|
||||
const [x1, y1, x2, y2] = props.arrow;
|
||||
path.moveTo(x1 - x, y1 - y);
|
||||
path.lineTo(x2 - x, y2 - y);
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
path.moveTo(
|
||||
x2 - head * Math.cos(angle - Math.PI / 6),
|
||||
y2 - head * Math.sin(angle - Math.PI / 6)
|
||||
);
|
||||
path.lineTo(x2 - x, y2 - y);
|
||||
path.lineTo(
|
||||
x2 - head * Math.cos(angle + Math.PI / 6),
|
||||
y2 - head * Math.sin(angle + Math.PI / 6)
|
||||
);
|
||||
return path;
|
||||
});
|
||||
|
||||
return () => (
|
||||
<g-path
|
||||
loc={loc.value}
|
||||
path={path.value}
|
||||
stroke
|
||||
strokeStyle={props.color}
|
||||
lineCap="round"
|
||||
lineJoin="round"
|
||||
/>
|
||||
);
|
||||
}, arrowProps);
|
||||
|
83
src/module/render/components/selection.tsx
Normal file
83
src/module/render/components/selection.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { DefaultProps, ElementLocator, onTick } from '@/core/render';
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { SetupComponentOptions } from './types';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { transitioned } from '../use';
|
||||
import { hyper } from 'mutate-animate';
|
||||
|
||||
export interface SelectionProps extends DefaultProps {
|
||||
loc: ElementLocator;
|
||||
color?: CanvasStyle;
|
||||
border?: CanvasStyle;
|
||||
winskin?: ImageIds;
|
||||
/** 选择图标的不透明度范围 */
|
||||
alphaRange?: [number, number];
|
||||
}
|
||||
|
||||
const selectionProps = {
|
||||
props: ['loc', 'color', 'border', 'winskin', 'alphaRange']
|
||||
} satisfies SetupComponentOptions<SelectionProps>;
|
||||
|
||||
export const Selection = defineComponent<SelectionProps>(props => {
|
||||
const minAlpha = computed(() => props.alphaRange?.[0] ?? 0.25);
|
||||
const maxAlpha = computed(() => props.alphaRange?.[1] ?? 0.55);
|
||||
const alpha = transitioned(minAlpha.value, 2000, hyper('sin', 'in-out'))!;
|
||||
|
||||
const isWinskin = computed(() => !!props.winskin);
|
||||
const winskinImage = computed(() =>
|
||||
isWinskin.value ? core.material.images.images[props.winskin!] : null
|
||||
);
|
||||
const fixedLoc = computed<ElementLocator>(() => {
|
||||
const [x = 0, y = 0, width = 200, height = 200] = props.loc;
|
||||
return [x + 1, y + 1, width - 2, height - 2];
|
||||
});
|
||||
|
||||
const renderWinskin = (canvas: MotaOffscreenCanvas2D) => {
|
||||
const ctx = canvas.ctx;
|
||||
const image = winskinImage.value;
|
||||
if (!image) return;
|
||||
const [, , width = 200, height = 200] = props.loc;
|
||||
// 背景
|
||||
ctx.drawImage(image, 130, 66, 28, 28, 2, 2, width - 4, height - 4);
|
||||
// 四个角
|
||||
ctx.drawImage(image, 128, 64, 2, 2, 0, 0, 2, 2);
|
||||
ctx.drawImage(image, 158, 64, 2, 2, width - 2, 0, 2, 2);
|
||||
ctx.drawImage(image, 128, 94, 2, 2, 0, height - 2, 2, 2);
|
||||
ctx.drawImage(image, 158, 94, 2, 2, width - 2, height - 2, 2, 2);
|
||||
// 四条边
|
||||
ctx.drawImage(image, 130, 64, 28, 2, 2, 0, width - 4, 2);
|
||||
ctx.drawImage(image, 130, 94, 28, 2, 2, height - 2, width - 4, 2);
|
||||
ctx.drawImage(image, 128, 66, 2, 28, 0, 2, 2, height - 4);
|
||||
ctx.drawImage(image, 158, 66, 2, 28, width - 2, 2, 2, height - 4);
|
||||
};
|
||||
|
||||
onTick(() => {
|
||||
if (alpha.value === maxAlpha.value) {
|
||||
alpha.set(minAlpha.value);
|
||||
}
|
||||
if (alpha.value === minAlpha.value) {
|
||||
alpha.set(maxAlpha.value);
|
||||
}
|
||||
});
|
||||
|
||||
return () =>
|
||||
isWinskin.value ? (
|
||||
<sprite
|
||||
loc={props.loc}
|
||||
render={renderWinskin}
|
||||
alpha={alpha.ref.value}
|
||||
noanti
|
||||
/>
|
||||
) : (
|
||||
<g-rectr
|
||||
loc={fixedLoc.value}
|
||||
circle={[4]}
|
||||
alpha={alpha.ref.value}
|
||||
fill
|
||||
stroke
|
||||
fillStyle={props.color}
|
||||
strokeStyle={props.border}
|
||||
lineWidth={1}
|
||||
/>
|
||||
);
|
||||
}, selectionProps);
|
@ -909,8 +909,7 @@ export class TextContentParser {
|
||||
|
||||
private getHeight(metrics: TextMetrics) {
|
||||
return (
|
||||
Math.abs(metrics.actualBoundingBoxAscent) +
|
||||
Math.abs(metrics.actualBoundingBoxDescent)
|
||||
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent
|
||||
);
|
||||
}
|
||||
|
||||
|
169
src/module/render/components/tip.tsx
Normal file
169
src/module/render/components/tip.tsx
Normal 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;
|
||||
}
|
||||
}
|
@ -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[] = [
|
||||
@ -106,7 +107,8 @@ const MainScene = defineComponent(() => {
|
||||
springCount: 0,
|
||||
floor: 'MT0',
|
||||
replaying: false,
|
||||
replayStatus
|
||||
replayStatus,
|
||||
night: 0
|
||||
});
|
||||
|
||||
const { getHeroStatusOn } = Mota.requireAll('fn');
|
||||
@ -132,10 +134,11 @@ const MainScene = defineComponent(() => {
|
||||
leftStatus.exAtk = getHeroStatusOn('mana');
|
||||
leftStatus.magicDef = getHeroStatusOn('magicDef');
|
||||
|
||||
const { HeroSkill } = Mota.require('module', 'Mechanism');
|
||||
const { HeroSkill, NightSpecial } = Mota.require('module', 'Mechanism');
|
||||
rightStatus.autoSkill = HeroSkill.getAutoSkill();
|
||||
rightStatus.skillName = HeroSkill.getSkillName();
|
||||
rightStatus.skillDesc = HeroSkill.getSkillDesc();
|
||||
rightStatus.night = NightSpecial.getNight(floor);
|
||||
rightStatus.floor = floor;
|
||||
rightStatus.replaying = core.isReplaying();
|
||||
const { pausing, speed, toReplay, totalList } = core.status.replay;
|
||||
@ -187,6 +190,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 && (
|
||||
|
@ -195,6 +195,8 @@ export interface IRightHeroStatus {
|
||||
replaying: boolean;
|
||||
/** 录像播放状态 */
|
||||
replayStatus: ReplayingStatus;
|
||||
/** 极昼永夜 */
|
||||
night: number;
|
||||
}
|
||||
|
||||
export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
|
||||
@ -246,6 +248,15 @@ export const RightStatusBar = defineComponent<StatusBarProps<IRightHeroStatus>>(
|
||||
valueColor: '#a7ffa7'
|
||||
});
|
||||
}
|
||||
if (s.night !== 0) {
|
||||
const text = s.night.toString();
|
||||
data.push({
|
||||
name: '极昼永夜',
|
||||
nameColor: '#a3f8ff',
|
||||
value: s.night > 0 ? '+' + text : text,
|
||||
valueColor: s.night > 0 ? '#a7ffa7' : '#ffa7a7'
|
||||
});
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user