mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-25 08:33:26 +08:00
Compare commits
No commits in common. "e232a469c84d5c056603d58682b4186547905009" and "c505efeb6644f685752acb1deb241a4cc553ab30" have entirely different histories.
e232a469c8
...
c505efeb66
@ -64,24 +64,22 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
logger.warn(33);
|
logger.warn(33);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const w = Math.max(width, 1);
|
|
||||||
const h = Math.max(height, 1);
|
|
||||||
let ratio = this.highResolution ? devicePixelRatio : 1;
|
let ratio = this.highResolution ? devicePixelRatio : 1;
|
||||||
const scale = core.domStyle.scale;
|
const scale = core.domStyle.scale;
|
||||||
if (this.autoScale) {
|
if (this.autoScale) {
|
||||||
ratio *= scale;
|
ratio *= scale;
|
||||||
}
|
}
|
||||||
this.scale = ratio;
|
this.scale = ratio;
|
||||||
this.canvas.width = w * ratio;
|
this.canvas.width = width * ratio;
|
||||||
this.canvas.height = h * ratio;
|
this.canvas.height = height * ratio;
|
||||||
this.width = w;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
this.ctx.scale(ratio, ratio);
|
this.ctx.scale(ratio, ratio);
|
||||||
this.ctx.imageSmoothingEnabled = this.antiAliasing;
|
this.ctx.imageSmoothingEnabled = this.antiAliasing;
|
||||||
if (this.canvas.isConnected) {
|
if (this.canvas.isConnected) {
|
||||||
this.canvas.style.width = `${w * scale}px`;
|
this.canvas.style.width = `${width * scale}px`;
|
||||||
this.canvas.style.height = `${h * scale}px`;
|
this.canvas.style.height = `${height * scale}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { DefaultProps } from '@/core/render';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export interface ConfirmBoxProps extends DefaultProps {
|
|
||||||
text: string;
|
|
||||||
yesText?: string;
|
|
||||||
noText?: string;
|
|
||||||
winskin?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ConfirmBoxEmits {
|
|
||||||
onYes: () => void;
|
|
||||||
onNo: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ConfirmBox = defineComponent(() => {
|
|
||||||
return () => <container></container>;
|
|
||||||
});
|
|
@ -1,17 +1,7 @@
|
|||||||
import {
|
import { DefaultProps, ElementLocator, PathProps, Sprite } from '@/core/render';
|
||||||
DefaultProps,
|
|
||||||
ElementLocator,
|
|
||||||
onTick,
|
|
||||||
PathProps,
|
|
||||||
Sprite
|
|
||||||
} from '@/core/render';
|
|
||||||
import { computed, defineComponent, ref, watch } from 'vue';
|
import { computed, defineComponent, ref, watch } from 'vue';
|
||||||
import { SetupComponentOptions } from './types';
|
import { SetupComponentOptions } from './types';
|
||||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||||
import { TextboxProps, TextContent } from './textbox';
|
|
||||||
import { Scroll, ScrollExpose, ScrollProps } from './scroll';
|
|
||||||
import { transitioned } from '../use';
|
|
||||||
import { hyper } from 'mutate-animate';
|
|
||||||
|
|
||||||
interface ProgressProps extends DefaultProps {
|
interface ProgressProps extends DefaultProps {
|
||||||
/** 进度条的位置 */
|
/** 进度条的位置 */
|
||||||
@ -30,16 +20,6 @@ const progressProps = {
|
|||||||
props: ['loc', 'progress', 'success', 'background']
|
props: ['loc', 'progress', 'success', 'background']
|
||||||
} satisfies SetupComponentOptions<ProgressProps>;
|
} satisfies SetupComponentOptions<ProgressProps>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 进度条组件,参数参考 {@link ProgressProps},用例如下:
|
|
||||||
* ```tsx
|
|
||||||
* // 定义进度
|
|
||||||
* const progress = ref(0);
|
|
||||||
*
|
|
||||||
* // 显示进度条
|
|
||||||
* <Progress loc={[12, 12, 120, 8]} progress={progress.value} />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const Progress = defineComponent<ProgressProps>(props => {
|
export const Progress = defineComponent<ProgressProps>(props => {
|
||||||
const element = ref<Sprite>();
|
const element = ref<Sprite>();
|
||||||
|
|
||||||
@ -75,7 +55,7 @@ export const Progress = defineComponent<ProgressProps>(props => {
|
|||||||
}, progressProps);
|
}, progressProps);
|
||||||
|
|
||||||
export interface ArrowProps extends PathProps {
|
export interface ArrowProps extends PathProps {
|
||||||
/** 箭头的起始和终点坐标,前两个是起始坐标,后两个是终点坐标 */
|
/** 箭头的四个坐标 */
|
||||||
arrow: [number, number, number, number];
|
arrow: [number, number, number, number];
|
||||||
/** 箭头的头部大小 */
|
/** 箭头的头部大小 */
|
||||||
head?: number;
|
head?: number;
|
||||||
@ -87,13 +67,6 @@ const arrowProps = {
|
|||||||
props: ['arrow', 'head', 'color']
|
props: ['arrow', 'head', 'color']
|
||||||
} satisfies SetupComponentOptions<ArrowProps>;
|
} satisfies SetupComponentOptions<ArrowProps>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 箭头组件,显示一个箭头,参数参考 {@link ArrowProps},用例如下:
|
|
||||||
* ```tsx
|
|
||||||
* // 从 (12, 12) 到 (48, 48) 的箭头
|
|
||||||
* <Arrow arrow={[12, 12, 48, 48]} />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const Arrow = defineComponent<ArrowProps>(props => {
|
export const Arrow = defineComponent<ArrowProps>(props => {
|
||||||
const loc = computed<ElementLocator>(() => {
|
const loc = computed<ElementLocator>(() => {
|
||||||
const [x1, y1, x2, y2] = props.arrow;
|
const [x1, y1, x2, y2] = props.arrow;
|
||||||
@ -134,261 +107,3 @@ export const Arrow = defineComponent<ArrowProps>(props => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, arrowProps);
|
}, arrowProps);
|
||||||
|
|
||||||
export interface ScrollTextProps extends TextboxProps, ScrollProps {
|
|
||||||
/** 自动滚动的速度,每秒多少像素 */
|
|
||||||
speed: number;
|
|
||||||
/** 文字的最大宽度 */
|
|
||||||
width: number;
|
|
||||||
/** 自动滚动组件的定位 */
|
|
||||||
loc: ElementLocator;
|
|
||||||
/** 文字滚动入元素之前要先滚动多少像素,默认16像素 */
|
|
||||||
pad?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ScrollTextEmits = {
|
|
||||||
/**
|
|
||||||
* 当滚动完毕时触发
|
|
||||||
*/
|
|
||||||
scrollEnd: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ScrollTextExpose {
|
|
||||||
/**
|
|
||||||
* 暂停滚动
|
|
||||||
*/
|
|
||||||
pause(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 继续滚动
|
|
||||||
*/
|
|
||||||
resume(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置滚动速度
|
|
||||||
*/
|
|
||||||
setSpeed(speed: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 立刻重新滚动
|
|
||||||
*/
|
|
||||||
rescroll(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollProps = {
|
|
||||||
props: ['speed', 'loc', 'pad', 'width'],
|
|
||||||
emits: ['scrollEnd']
|
|
||||||
} satisfies SetupComponentOptions<
|
|
||||||
ScrollTextProps,
|
|
||||||
ScrollTextEmits,
|
|
||||||
keyof ScrollTextEmits
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 滚动文字,可以用于展示长剧情或者 staff 表,参数参考 {@link ScrollTextProps},
|
|
||||||
* 事件参考 {@link ScrollTextEmits},函数接口参考 {@link ScrollTextExpose},用例如下:
|
|
||||||
* ```tsx
|
|
||||||
* // 用于接受函数接口
|
|
||||||
* const scroll = ref<ScrollTextExpose>();
|
|
||||||
* // 显示的文字
|
|
||||||
* const text = '滚动文字'.repeat(100);
|
|
||||||
*
|
|
||||||
* onMounted(() => {
|
|
||||||
* // 设置为每秒滚动 100 像素
|
|
||||||
* scroll.value?.setSpeed(100);
|
|
||||||
* // 暂停滚动
|
|
||||||
* scroll.value?.pause();
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // 显示滚动文字,每秒滚动 50 像素
|
|
||||||
* <ScrollText ref={scroll} text={text} speed={50} loc={[0, 0, 180, 100]} width={180} />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export const ScrollText = defineComponent<
|
|
||||||
ScrollTextProps,
|
|
||||||
ScrollTextEmits,
|
|
||||||
keyof ScrollTextEmits
|
|
||||||
>((props, { emit, expose, attrs }) => {
|
|
||||||
const scroll = ref<ScrollExpose>();
|
|
||||||
const speed = ref(props.speed);
|
|
||||||
|
|
||||||
const eleHeight = computed(() => props.loc[3] ?? props.height ?? 200);
|
|
||||||
const pad = computed(() => props.pad ?? 16);
|
|
||||||
|
|
||||||
let lastFixedTime = Date.now();
|
|
||||||
let lastFixedPos = 0;
|
|
||||||
let paused = false;
|
|
||||||
let nowScroll = 0;
|
|
||||||
|
|
||||||
onTick(() => {
|
|
||||||
if (paused || !scroll.value) return;
|
|
||||||
const now = Date.now();
|
|
||||||
const dt = now - lastFixedTime;
|
|
||||||
nowScroll = (dt / 1000) * speed.value + lastFixedPos;
|
|
||||||
scroll.value.scrollTo(nowScroll, 0);
|
|
||||||
if (nowScroll >= scroll.value.getScrollLength()) {
|
|
||||||
emit('scrollEnd');
|
|
||||||
paused = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const pause = () => {
|
|
||||||
paused = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resume = () => {
|
|
||||||
paused = false;
|
|
||||||
lastFixedPos = nowScroll;
|
|
||||||
lastFixedTime = Date.now();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSpeed = (value: number) => {
|
|
||||||
lastFixedPos = nowScroll;
|
|
||||||
lastFixedTime = Date.now();
|
|
||||||
speed.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const rescroll = () => {
|
|
||||||
nowScroll = 0;
|
|
||||||
lastFixedTime = Date.now();
|
|
||||||
lastFixedPos = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
expose<ScrollTextExpose>({ pause, resume, setSpeed, rescroll });
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<Scroll
|
|
||||||
ref={scroll}
|
|
||||||
loc={props.loc}
|
|
||||||
padEnd={eleHeight.value + pad.value}
|
|
||||||
noscroll
|
|
||||||
>
|
|
||||||
<TextContent
|
|
||||||
{...attrs}
|
|
||||||
width={props.width - 16}
|
|
||||||
loc={[8, eleHeight.value + pad.value]}
|
|
||||||
autoHeight
|
|
||||||
/>
|
|
||||||
</Scroll>
|
|
||||||
);
|
|
||||||
}, scrollProps);
|
|
||||||
|
|
||||||
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>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示一个选择光标,与 2.x 的 drawUIEventSelector 效果一致,参数参考 {@link SelectionProps},用例如下:
|
|
||||||
* ```tsx
|
|
||||||
* // 使用 winskin.png 作为选择光标,光标动画的不透明度范围是 [0.3, 0.8]
|
|
||||||
* <Selection loc={[24, 24, 80, 16]} winskin="winskin.png" alphaRange={[0.3, 0.8]} />
|
|
||||||
* // 使用指定的填充和边框颜色作为选择光标
|
|
||||||
* <Selection loc={[24, 24, 80, 16]} color="#ddd" border="gold" />
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
export interface BackgroundProps extends DefaultProps {
|
|
||||||
loc: ElementLocator;
|
|
||||||
winskin?: ImageIds;
|
|
||||||
color?: CanvasStyle;
|
|
||||||
border?: CanvasStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backgroundProps = {
|
|
||||||
props: ['loc', 'winskin', 'color', 'border']
|
|
||||||
} satisfies SetupComponentOptions<BackgroundProps>;
|
|
||||||
|
|
||||||
export const Background = defineComponent<BackgroundProps>(props => {
|
|
||||||
const isWinskin = computed(() => !!props.winskin);
|
|
||||||
const fixedLoc = computed<ElementLocator>(() => {
|
|
||||||
const [x = 0, y = 0, width = 200, height = 200] = props.loc;
|
|
||||||
return [x + 2, y + 2, width - 4, height - 4];
|
|
||||||
});
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
isWinskin.value ? (
|
|
||||||
<winskin image={props.winskin!} loc={props.loc} noanti />
|
|
||||||
) : (
|
|
||||||
<g-rectr
|
|
||||||
loc={fixedLoc.value}
|
|
||||||
fillStyle={props.color}
|
|
||||||
strokeStyle={props.border}
|
|
||||||
fill
|
|
||||||
stroke
|
|
||||||
lineWidth={2}
|
|
||||||
circle={[4]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}, backgroundProps);
|
|
||||||
|
@ -37,11 +37,6 @@ export interface ScrollExpose {
|
|||||||
* @param time 滚动的动画时长,默认为无动画
|
* @param time 滚动的动画时长,默认为无动画
|
||||||
*/
|
*/
|
||||||
scrollTo(y: number, time?: number): void;
|
scrollTo(y: number, time?: number): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取这个滚动条组件最多可以滚动多长
|
|
||||||
*/
|
|
||||||
getScrollLength(): number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrollProps extends DefaultProps {
|
export interface ScrollProps extends DefaultProps {
|
||||||
@ -52,7 +47,7 @@ export interface ScrollProps extends DefaultProps {
|
|||||||
* 滚动到最下方(最右方)时的填充大小,如果默认的高度计算方式有误,
|
* 滚动到最下方(最右方)时的填充大小,如果默认的高度计算方式有误,
|
||||||
* 那么可以调整此参数来修复错误
|
* 那么可以调整此参数来修复错误
|
||||||
*/
|
*/
|
||||||
padEnd?: number;
|
pad?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScrollSlots = SlotsType<{
|
type ScrollSlots = SlotsType<{
|
||||||
@ -60,7 +55,7 @@ type ScrollSlots = SlotsType<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
const scrollProps = {
|
const scrollProps = {
|
||||||
props: ['hor', 'noscroll', 'loc', 'padEnd']
|
props: ['hor', 'noscroll', 'loc', 'pad']
|
||||||
} satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>;
|
} satisfies SetupComponentOptions<ScrollProps, {}, string, ScrollSlots>;
|
||||||
|
|
||||||
/** 滚动条图示的最短长度 */
|
/** 滚动条图示的最短长度 */
|
||||||
@ -117,7 +112,6 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
|||||||
const scrollColor = computed(
|
const scrollColor = computed(
|
||||||
() => `rgba(${SCROLL_COLOR},${scrollAlpha.ref.value ?? 0.5})`
|
() => `rgba(${SCROLL_COLOR},${scrollAlpha.ref.value ?? 0.5})`
|
||||||
);
|
);
|
||||||
const padEnd = computed(() => props.padEnd ?? 0);
|
|
||||||
|
|
||||||
watch(scrollColor, () => {
|
watch(scrollColor, () => {
|
||||||
scroll.value?.update();
|
scroll.value?.update();
|
||||||
@ -151,6 +145,7 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
|||||||
if (contentPos !== contentTarget) {
|
if (contentPos !== contentTarget) {
|
||||||
contentPos = transition.value.showScroll;
|
contentPos = transition.value.showScroll;
|
||||||
checkAllItem();
|
checkAllItem();
|
||||||
|
updatePosition();
|
||||||
content.value?.update();
|
content.value?.update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -216,7 +211,7 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
|||||||
*/
|
*/
|
||||||
const onTransform = (item: RenderItem) => {
|
const onTransform = (item: RenderItem) => {
|
||||||
const rect = item.getBoundingRect();
|
const rect = item.getBoundingRect();
|
||||||
const pad = props.padEnd ?? 0;
|
const pad = props.pad ?? 0;
|
||||||
if (item.parent === content.value) {
|
if (item.parent === content.value) {
|
||||||
if (direction.value === ScrollDirection.Horizontal) {
|
if (direction.value === ScrollDirection.Horizontal) {
|
||||||
if (rect.right > maxLength - pad) {
|
if (rect.right > maxLength - pad) {
|
||||||
@ -296,7 +291,7 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
|||||||
}
|
}
|
||||||
checkItem(v);
|
checkItem(v);
|
||||||
});
|
});
|
||||||
maxLength = Math.max(max + padEnd.value, 10);
|
maxLength = Math.max(max + (props.pad ?? 0), 10);
|
||||||
updatePosition();
|
updatePosition();
|
||||||
scroll.value?.update();
|
scroll.value?.update();
|
||||||
};
|
};
|
||||||
@ -509,19 +504,10 @@ export const Scroll = defineComponent<ScrollProps, {}, string, ScrollSlots>(
|
|||||||
transition.ticker.destroy();
|
transition.ticker.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region expose 函数
|
|
||||||
|
|
||||||
const getScrollLength = () => {
|
|
||||||
return maxLength - height.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
expose<ScrollExpose>({
|
expose<ScrollExpose>({
|
||||||
scrollTo,
|
scrollTo
|
||||||
getScrollLength
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<container loc={props.loc} onWheel={wheel}>
|
<container loc={props.loc} onWheel={wheel}>
|
||||||
|
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);
|
@ -26,7 +26,6 @@ import {
|
|||||||
WordBreak,
|
WordBreak,
|
||||||
TextAlign
|
TextAlign
|
||||||
} from './textboxTyper';
|
} from './textboxTyper';
|
||||||
import { ElementLocator } from '@/core/render';
|
|
||||||
|
|
||||||
export interface TextContentProps
|
export interface TextContentProps
|
||||||
extends DefaultProps,
|
extends DefaultProps,
|
||||||
@ -37,10 +36,6 @@ export interface TextContentProps
|
|||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
/** 是否描边 */
|
/** 是否描边 */
|
||||||
stroke?: boolean;
|
stroke?: boolean;
|
||||||
/** 是否自适应高度 */
|
|
||||||
autoHeight?: boolean;
|
|
||||||
/** 文字的最大宽度 */
|
|
||||||
width: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TextContentEmits = {
|
export type TextContentEmits = {
|
||||||
@ -58,11 +53,6 @@ export interface TextContentExpose {
|
|||||||
* 立刻显示所有文字
|
* 立刻显示所有文字
|
||||||
*/
|
*/
|
||||||
showAll(): void;
|
showAll(): void;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取这段 TextContent 的总高度
|
|
||||||
*/
|
|
||||||
getHeight(): number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const textContentOptions = {
|
const textContentOptions = {
|
||||||
@ -86,8 +76,7 @@ const textContentOptions = {
|
|||||||
'strokeWidth',
|
'strokeWidth',
|
||||||
'stroke',
|
'stroke',
|
||||||
'loc',
|
'loc',
|
||||||
'width',
|
'width'
|
||||||
'autoHeight'
|
|
||||||
],
|
],
|
||||||
emits: ['typeEnd', 'typeStart']
|
emits: ['typeEnd', 'typeStart']
|
||||||
} satisfies SetupComponentOptions<
|
} satisfies SetupComponentOptions<
|
||||||
@ -101,11 +90,8 @@ export const TextContent = defineComponent<
|
|||||||
TextContentEmits,
|
TextContentEmits,
|
||||||
keyof TextContentEmits
|
keyof TextContentEmits
|
||||||
>((props, { emit, expose }) => {
|
>((props, { emit, expose }) => {
|
||||||
const loc = ref<ElementLocator>(
|
const width = computed(() => props.width ?? props.loc?.[2] ?? 200);
|
||||||
(props.loc?.slice() as ElementLocator) ?? []
|
if (width.value < 0) {
|
||||||
);
|
|
||||||
|
|
||||||
if (props.width < 0) {
|
|
||||||
logger.warn(41, String(props.width));
|
logger.warn(41, String(props.width));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +112,6 @@ export const TextContent = defineComponent<
|
|||||||
typer.setText(props.text ?? '');
|
typer.setText(props.text ?? '');
|
||||||
typer.type();
|
typer.type();
|
||||||
needUpdate = false;
|
needUpdate = false;
|
||||||
updateLoc();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -134,23 +119,12 @@ export const TextContent = defineComponent<
|
|||||||
typer.typeAll();
|
typer.typeAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(props, () => {
|
watch(props, value => {
|
||||||
typer.setConfig(props);
|
typer.setConfig(value);
|
||||||
retype();
|
retype();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getHeight = () => {
|
expose({ retype, showAll });
|
||||||
return typer.getHeight();
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateLoc = () => {
|
|
||||||
if (props.autoHeight) {
|
|
||||||
const [x = 0, y = 0, width = 200, , ax = 0, ay = 0] = loc.value;
|
|
||||||
loc.value = [x, y, width, getHeight(), ax, ay];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expose<TextContentExpose>({ retype, showAll, getHeight });
|
|
||||||
|
|
||||||
const spriteElement = shallowRef<Sprite>();
|
const spriteElement = shallowRef<Sprite>();
|
||||||
const renderContent = (canvas: MotaOffscreenCanvas2D) => {
|
const renderContent = (canvas: MotaOffscreenCanvas2D) => {
|
||||||
@ -201,7 +175,7 @@ export const TextContent = defineComponent<
|
|||||||
return () => {
|
return () => {
|
||||||
return (
|
return (
|
||||||
<sprite
|
<sprite
|
||||||
loc={loc.value}
|
loc={props.loc}
|
||||||
ref={spriteElement}
|
ref={spriteElement}
|
||||||
render={renderContent}
|
render={renderContent}
|
||||||
></sprite>
|
></sprite>
|
||||||
@ -226,8 +200,6 @@ export interface TextboxProps extends TextContentProps, DefaultProps {
|
|||||||
titleStroke?: CanvasStyle;
|
titleStroke?: CanvasStyle;
|
||||||
/** 标题文字与边框间的距离,默认为4 */
|
/** 标题文字与边框间的距离,默认为4 */
|
||||||
titlePadding?: number;
|
titlePadding?: number;
|
||||||
/** 最大宽度 */
|
|
||||||
width: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextboxExpose {
|
export interface TextboxExpose {
|
||||||
@ -285,8 +257,8 @@ export const Textbox = defineComponent<
|
|||||||
keyof TextboxEmits,
|
keyof TextboxEmits,
|
||||||
TextboxSlots
|
TextboxSlots
|
||||||
>((props, { slots, expose }) => {
|
>((props, { slots, expose }) => {
|
||||||
const contentData = shallowReactive<TextContentProps>({ width: 200 });
|
const contentData = shallowReactive<TextContentProps>({});
|
||||||
const data = shallowReactive<TextboxProps>({ width: 200 });
|
const data = shallowReactive<TextboxProps>({});
|
||||||
|
|
||||||
const setContentData = () => {
|
const setContentData = () => {
|
||||||
contentData.breakChars = props.breakChars ?? '';
|
contentData.breakChars = props.breakChars ?? '';
|
||||||
|
@ -124,8 +124,6 @@ export interface TyperTextRenderable {
|
|||||||
strokeStyle: CanvasStyle;
|
strokeStyle: CanvasStyle;
|
||||||
/** 文字画到哪个索引 */
|
/** 文字画到哪个索引 */
|
||||||
pointer: number;
|
pointer: number;
|
||||||
/** 这段文字的总高度 */
|
|
||||||
height: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TyperIconRenderable {
|
export interface TyperIconRenderable {
|
||||||
@ -233,15 +231,6 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
|||||||
onTick(() => this.tick());
|
onTick(() => this.tick());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取这段文字的总高度
|
|
||||||
*/
|
|
||||||
getHeight() {
|
|
||||||
const heights = this.renderObject.lineHeights;
|
|
||||||
const lines = heights.reduce((prev, curr) => prev + curr, 0);
|
|
||||||
return lines + this.config.lineHeight * heights.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置打字机的配置属性
|
* 设置打字机的配置属性
|
||||||
* @param config 配置信息
|
* @param config 配置信息
|
||||||
@ -296,21 +285,19 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
|||||||
if (line < 0 || line > renderable.splitLines.length) {
|
if (line < 0 || line > renderable.splitLines.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const start = renderable.splitLines[line - 1] ?? 0;
|
const start = renderable.splitLines[line - 1] ?? -1;
|
||||||
const end =
|
const end =
|
||||||
renderable.splitLines[line] ?? renderable.text.length;
|
renderable.splitLines[line] ?? renderable.text.length - 1;
|
||||||
const lineHeight = this.renderObject.lineHeights[this.nowLine];
|
|
||||||
|
|
||||||
const data: TyperTextRenderable = {
|
const data: TyperTextRenderable = {
|
||||||
type: TextContentType.Text,
|
type: TextContentType.Text,
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y,
|
y: this.y,
|
||||||
text: renderable.text.slice(start, end),
|
text: renderable.text.slice(start + 1, end + 1),
|
||||||
font: renderable.font,
|
font: renderable.font,
|
||||||
fillStyle: renderable.fillStyle,
|
fillStyle: renderable.fillStyle,
|
||||||
strokeStyle: this.config.strokeStyle,
|
strokeStyle: this.config.strokeStyle,
|
||||||
pointer: 0,
|
pointer: 0
|
||||||
height: lineHeight + this.config.lineHeight
|
|
||||||
};
|
};
|
||||||
this.processingData = data;
|
this.processingData = data;
|
||||||
this.renderData.push(data);
|
this.renderData.push(data);
|
||||||
@ -939,7 +926,7 @@ export class TextContentParser {
|
|||||||
const rest = width - this.lineWidth;
|
const rest = width - this.lineWidth;
|
||||||
const guessRest = guess * (rest / width) * this.guessGain;
|
const guessRest = guess * (rest / width) * this.guessGain;
|
||||||
const length = pointer - this.lineStart + 1;
|
const length = pointer - this.lineStart + 1;
|
||||||
if (length <= guessRest) {
|
if (length < guessRest) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.guessGain = 1;
|
this.guessGain = 1;
|
||||||
@ -954,7 +941,7 @@ export class TextContentParser {
|
|||||||
if (height > this.lineHeight) {
|
if (height > this.lineHeight) {
|
||||||
this.lineHeight = height;
|
this.lineHeight = height;
|
||||||
}
|
}
|
||||||
if (metrics.width <= rest) {
|
if (metrics.width < rest) {
|
||||||
// 实际宽度小于剩余宽度时,将猜测增益乘以剩余总宽度与当前宽度的比值的若干倍
|
// 实际宽度小于剩余宽度时,将猜测增益乘以剩余总宽度与当前宽度的比值的若干倍
|
||||||
this.guessGain *= (rest / metrics.width) * (1.1 + 1 / length);
|
this.guessGain *= (rest / metrics.width) * (1.1 + 1 / length);
|
||||||
this.bsStart = breakIndex;
|
this.bsStart = breakIndex;
|
||||||
@ -969,7 +956,7 @@ export class TextContentParser {
|
|||||||
this.lineHeights.push(this.lineHeight);
|
this.lineHeights.push(this.lineHeight);
|
||||||
this.bsStart = index;
|
this.bsStart = index;
|
||||||
const text = data.text.slice(
|
const text = data.text.slice(
|
||||||
this.wordBreak[index] + 1,
|
this.wordBreak[index],
|
||||||
pointer + 1
|
pointer + 1
|
||||||
);
|
);
|
||||||
if (text.length < guessRest / 4) {
|
if (text.length < guessRest / 4) {
|
||||||
@ -1008,7 +995,7 @@ export class TextContentParser {
|
|||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
const text = data.text.slice(
|
const text = data.text.slice(
|
||||||
wordBreak[this.bsStart] + 1,
|
wordBreak[this.bsStart],
|
||||||
wordBreak[mid] + 1
|
wordBreak[mid] + 1
|
||||||
);
|
);
|
||||||
const metrics = ctx.measureText(text);
|
const metrics = ctx.measureText(text);
|
||||||
@ -1041,7 +1028,7 @@ export class TextContentParser {
|
|||||||
const wordBreak = data.wordBreak;
|
const wordBreak = data.wordBreak;
|
||||||
const lastLine = data.splitLines.at(-1);
|
const lastLine = data.splitLines.at(-1);
|
||||||
const lastIndex = isNil(lastLine) ? 0 : lastLine;
|
const lastIndex = isNil(lastLine) ? 0 : lastLine;
|
||||||
const restText = data.text.slice(lastIndex + 1);
|
const restText = data.text.slice(lastIndex);
|
||||||
const ctx = this.testCanvas.ctx;
|
const ctx = this.testCanvas.ctx;
|
||||||
ctx.font = data.font;
|
ctx.font = data.font;
|
||||||
const metrics = ctx.measureText(restText);
|
const metrics = ctx.measureText(restText);
|
||||||
@ -1065,7 +1052,7 @@ export class TextContentParser {
|
|||||||
data.splitLines.push(this.wordBreak[index]);
|
data.splitLines.push(this.wordBreak[index]);
|
||||||
this.lineHeights.push(this.lineHeight);
|
this.lineHeights.push(this.lineHeight);
|
||||||
this.bsStart = index;
|
this.bsStart = index;
|
||||||
const text = data.text.slice(this.wordBreak[index] + 1);
|
const text = data.text.slice(this.wordBreak[index]);
|
||||||
if (!isLast && text.length < guess / 4) {
|
if (!isLast && text.length < guess / 4) {
|
||||||
// 如果剩余文字很少,几乎不可能会单独成一行时,直接结束循环
|
// 如果剩余文字很少,几乎不可能会单独成一行时,直接结束循环
|
||||||
this.lastBreakIndex = index;
|
this.lastBreakIndex = index;
|
||||||
@ -1182,8 +1169,6 @@ export class TextContentParser {
|
|||||||
this.checkRestLine(width, guess, i);
|
this.checkRestLine(width, guess, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lineHeights.push(this.lineHeight);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineHeights: this.lineHeights,
|
lineHeights: this.lineHeights,
|
||||||
data: this.renderable
|
data: this.renderable
|
||||||
|
@ -66,8 +66,7 @@ const MainScene = defineComponent(() => {
|
|||||||
titleFont: '700 20px normal',
|
titleFont: '700 20px normal',
|
||||||
winskin: 'winskin2.png',
|
winskin: 'winskin2.png',
|
||||||
interval: 100,
|
interval: 100,
|
||||||
lineHeight: 4,
|
lineHeight: 4
|
||||||
width: 480
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const map = ref<LayerGroup>();
|
const map = ref<LayerGroup>();
|
||||||
|
@ -247,7 +247,7 @@ export const NumpadToolbar = defineComponent<
|
|||||||
const altAlpha = transitioned(0, 100, linear())!;
|
const altAlpha = transitioned(0, 100, linear())!;
|
||||||
|
|
||||||
const ctrlColor = computed(
|
const ctrlColor = computed(
|
||||||
() => `rgba(221,221,221,${ctrlAlpha.ref.value})`
|
() => `rgba(255,255,255,${ctrlAlpha.ref.value})`
|
||||||
);
|
);
|
||||||
const ctrlTextColor = computed(() => {
|
const ctrlTextColor = computed(() => {
|
||||||
const rgb = Math.floor(255 - ctrlAlpha.ref.value * 255);
|
const rgb = Math.floor(255 - ctrlAlpha.ref.value * 255);
|
||||||
@ -255,14 +255,14 @@ export const NumpadToolbar = defineComponent<
|
|||||||
});
|
});
|
||||||
|
|
||||||
const shiftColor = computed(
|
const shiftColor = computed(
|
||||||
() => `rgba(221,221,221,${shiftAlpha.ref.value})`
|
() => `rgba(255,255,255,${shiftAlpha.ref.value})`
|
||||||
);
|
);
|
||||||
const shiftTextColor = computed(() => {
|
const shiftTextColor = computed(() => {
|
||||||
const rgb = Math.floor(255 - shiftAlpha.ref.value * 255);
|
const rgb = Math.floor(255 - shiftAlpha.ref.value * 255);
|
||||||
return `rgba(${rgb},${rgb},${rgb},1)`;
|
return `rgba(${rgb},${rgb},${rgb},1)`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const altColor = computed(() => `rgba(221,221,221,${altAlpha.ref.value})`);
|
const altColor = computed(() => `rgba(255,255,255,${altAlpha.ref.value})`);
|
||||||
const altTextColor = computed(() => {
|
const altTextColor = computed(() => {
|
||||||
const rgb = Math.floor(255 - altAlpha.ref.value * 255);
|
const rgb = Math.floor(255 - altAlpha.ref.value * 255);
|
||||||
return `rgba(${rgb},${rgb},${rgb},1)`;
|
return `rgba(${rgb},${rgb},${rgb},1)`;
|
||||||
|
Loading…
Reference in New Issue
Block a user