HumanBreak/packages-user/client-modules/src/render/components/floorSelect.tsx

215 lines
7.0 KiB
TypeScript

import { DefaultProps } from '@motajs/render-vue';
import { SetupComponentOptions } from '@motajs/system-ui';
import { clamp, isNil } from 'lodash-es';
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
import { Scroll, ScrollExpose } from './scroll';
import { Font } from '@motajs/render-style';
import { MotaOffscreenCanvas2D } from '@motajs/render-core';
export interface FloorSelectorProps extends DefaultProps {
floors: FloorIds[];
now?: number;
}
export type FloorSelectorEmits = {
/**
* 点击关闭按钮时触发
*/
close: () => void;
/**
* 当选中的楼层改变时触发
* @param floor 楼层索引
* @param floorId 楼层 id
*/
update: (floor: number, floorId: FloorIds) => void;
'update:now': (value: number) => void;
};
const floorSelectorProps = {
props: ['floors', 'now'],
emits: ['close', 'update', 'update:now']
} satisfies SetupComponentOptions<
FloorSelectorProps,
FloorSelectorEmits,
keyof FloorSelectorEmits
>;
export const FloorSelector = defineComponent<
FloorSelectorProps,
FloorSelectorEmits,
keyof FloorSelectorEmits
>((props, { emit }) => {
const listFont = new Font(Font.defaultFamily, 12);
/** 当前选中楼层,不反向 */
const now = ref(props.now ?? 0);
/** 当前鼠标选中楼层,反向 */
const selList = ref(0);
const scrollRef = ref<ScrollExpose>();
const floors = computed(() => props.floors.toReversed());
const floorId = computed(() => floors.value[now.value]);
const floorName = computed(() => core.floors[floorId.value].title);
watch(
() => props.now,
value => {
if (!isNil(value)) {
changeTo(value);
}
}
);
let gradient: CanvasGradient | null = null;
const getGradient = (ctx: CanvasRenderingContext2D) => {
if (gradient) return gradient;
gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, 'rgba(255,255,255,0)');
gradient.addColorStop(0.2, 'rgba(255,255,255,1)');
gradient.addColorStop(0.8, 'rgba(255,255,255,1)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
return gradient;
};
const renderMask = (canvas: MotaOffscreenCanvas2D) => {
const { ctx } = canvas;
const gradient = getGradient(ctx);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 144, 200);
};
const changeTo = (index: number, time: number = 500) => {
// 取反是为了符合 2.x 的操作习惯
const res = clamp(index, 0, floors.value.length - 1);
const reversed = floors.value.length - res - 1;
now.value = res;
selList.value = reversed;
const y = reversed * 24;
scrollRef.value?.scrollTo(y, time);
emit('update', now.value, floorId.value);
emit('update:now', now.value);
};
const changeFloor = (delta: number) => {
changeTo(now.value + delta);
};
const enterList = (index: number) => {
selList.value = index;
};
const close = () => {
emit('close');
};
onMounted(() => {
changeTo(now.value, 0);
});
return () => (
<container>
<text text={floorName.value} loc={[90, 24]} anc={[0.5, 0.5]} />
<g-line line={[48, 40, 132, 40]} lineWidth={1} />
<g-line line={[48, 440, 132, 440]} lineWidth={1} />
<text
text="退出"
loc={[90, 456]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={close}
/>
<text
text="「 上移十层 」"
loc={[90, 70]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(10)}
/>
<text
text="「 上移一层 」"
loc={[90, 110]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(1)}
/>
<text
text="「 下移一层 」"
loc={[90, 370]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(-1)}
/>
<text
text="「 下移十层 」"
loc={[90, 410]}
anc={[0.5, 0.5]}
cursor="pointer"
onClick={() => changeFloor(-10)}
/>
<container loc={[0, 140, 144, 200]}>
<Scroll
ref={scrollRef}
loc={[0, 0, 144, 200]}
noscroll
zIndex={10}
padEnd={88}
>
{floors.value.map((v, i, a) => {
const floor = core.floors[v];
const reversed = a.length - i - 1;
const nowFloor = a.length - now.value - 1;
const highlight = nowFloor === i || selList.value === i;
const color = highlight ? '#fff' : '#aaa';
const fill = highlight ? '#fff' : '#000';
return (
<container
nocache
loc={[0, i * 24 + 88, 144, 24]}
key={v}
>
<text
cursor="pointer"
text={floor.title}
loc={[114, 12]}
anc={[1, 0.5]}
font={listFont}
fillStyle={color}
onEnter={() => enterList(i)}
onLeave={() => enterList(nowFloor)}
onClick={() => changeTo(reversed)}
/>
<g-circle
stroke
fill
lineWidth={1}
circle={[130, 12, 3]}
strokeStyle={color}
fillStyle={nowFloor === i ? fill : '#000'}
/>
</container>
);
})}
</Scroll>
<g-line
line={[130, 0, 130, 200]}
zIndex={5}
lineWidth={1}
strokeStyle="#aaa"
/>
<sprite
zIndex={20}
loc={[0, 0, 144, 200]}
nocache
noevent
render={renderMask}
composite="destination-in"
/>
</container>
</container>
);
}, floorSelectorProps);