mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-09-25 09:21:47 +08:00
219 lines
6.4 KiB
TypeScript
219 lines
6.4 KiB
TypeScript
import { DefaultProps } from '@motajs/render-vue';
|
||
import { computed, defineComponent, ref, SlotsType, VNode } from 'vue';
|
||
import { Selection } from './misc';
|
||
import { ElementLocator } from '@motajs/render-core';
|
||
import { Font } from '@motajs/render-style';
|
||
import { SetupComponentOptions } from '@motajs/system-ui';
|
||
import { Scroll } from './scroll';
|
||
|
||
export interface ListProps extends DefaultProps {
|
||
/** 列表内容,第一项表示 id,第二项表示显示的内容 */
|
||
list: [string, string][];
|
||
/** 当前选中的项 */
|
||
selected: string;
|
||
/** 定位 */
|
||
loc: ElementLocator;
|
||
/** 每行的高度,默认 18 */
|
||
lineHeight?: number;
|
||
/** 字体 */
|
||
font?: Font;
|
||
/** 使用 winskin 作为光标 */
|
||
winskin?: ImageIds;
|
||
/** 使用指定样式作为光标背景 */
|
||
color?: CanvasStyle;
|
||
/** 使用指定样式作为光标边框 */
|
||
border?: CanvasStyle;
|
||
/** 选择图标的不透明度范围 */
|
||
alphaRange?: [number, number];
|
||
}
|
||
|
||
export type ListEmits = {
|
||
/**
|
||
* 当用户选中某一项时触发
|
||
* @param key 选中的项的 id
|
||
*/
|
||
update: (key: string) => void;
|
||
|
||
'update:selected': (value: string) => void;
|
||
};
|
||
|
||
const listProps = {
|
||
props: [
|
||
'list',
|
||
'selected',
|
||
'loc',
|
||
'lineHeight',
|
||
'font',
|
||
'winskin',
|
||
'color',
|
||
'border',
|
||
'alphaRange'
|
||
],
|
||
emits: ['update', 'update:selected']
|
||
} satisfies SetupComponentOptions<ListProps, ListEmits, keyof ListEmits>;
|
||
|
||
export const List = defineComponent<ListProps, ListEmits, keyof ListEmits>(
|
||
(props, { emit }) => {
|
||
const selected = ref(props.list[0][0]);
|
||
const lineHeight = computed(() => props.lineHeight ?? 18);
|
||
|
||
const select = (value: string) => {
|
||
selected.value = value;
|
||
emit('update', value);
|
||
emit('update:selected', value);
|
||
};
|
||
|
||
return () => (
|
||
<Scroll loc={props.loc}>
|
||
{props.list.map((v, i) => {
|
||
const [key, value] = v;
|
||
const loc: ElementLocator = [
|
||
0,
|
||
lineHeight.value * i,
|
||
props.loc[2] ?? 200,
|
||
lineHeight.value
|
||
];
|
||
const selectionLoc: ElementLocator = [
|
||
0,
|
||
0,
|
||
(props.loc[2] ?? 200) - 10,
|
||
lineHeight.value
|
||
];
|
||
const textLoc: ElementLocator = [
|
||
10,
|
||
lineHeight.value / 2,
|
||
void 0,
|
||
void 0,
|
||
0,
|
||
0.5
|
||
];
|
||
return (
|
||
<container loc={loc} onClick={() => select(key)}>
|
||
{selected.value === key && (
|
||
<Selection
|
||
loc={selectionLoc}
|
||
color={props.color}
|
||
border={props.border}
|
||
winskin={props.winskin}
|
||
alphaRange={props.alphaRange}
|
||
/>
|
||
)}
|
||
<text
|
||
loc={textLoc}
|
||
text={value}
|
||
font={props.font}
|
||
/>
|
||
</container>
|
||
);
|
||
})}
|
||
</Scroll>
|
||
);
|
||
},
|
||
listProps
|
||
);
|
||
|
||
export interface ListPageProps extends ListProps {
|
||
/** 组件定位 */
|
||
loc: ElementLocator;
|
||
/** 列表所占比例 */
|
||
basis?: number;
|
||
/** 列表是否排列在右侧 */
|
||
right?: boolean;
|
||
/** 是否显示关闭按钮 */
|
||
close?: boolean;
|
||
/** 关闭按钮的位置,相对于组件定位 */
|
||
closeLoc?: ElementLocator;
|
||
}
|
||
|
||
export type ListPageEmits = {
|
||
close: () => void;
|
||
} & ListEmits;
|
||
|
||
export type ListPageSlots = SlotsType<{
|
||
default: (key: string) => VNode | VNode[];
|
||
|
||
[x: string]: (key: string) => VNode | VNode[];
|
||
}>;
|
||
|
||
const listPageProps = {
|
||
props: [
|
||
'basis',
|
||
'right',
|
||
'list',
|
||
'selected',
|
||
'loc',
|
||
'lineHeight',
|
||
'font',
|
||
'winskin',
|
||
'color',
|
||
'border',
|
||
'alphaRange',
|
||
'close',
|
||
'closeLoc'
|
||
],
|
||
emits: ['update', 'update:selected', 'close']
|
||
} satisfies SetupComponentOptions<
|
||
ListPageProps,
|
||
ListPageEmits,
|
||
keyof ListPageEmits,
|
||
ListPageSlots
|
||
>;
|
||
|
||
export const ListPage = defineComponent<
|
||
ListPageProps,
|
||
ListPageEmits,
|
||
keyof ListPageEmits,
|
||
ListPageSlots
|
||
>((props, { emit, slots }) => {
|
||
const selected = ref(props.selected);
|
||
|
||
const basis = computed(() => props.basis ?? 0.3);
|
||
const width = computed(() => props.loc[2] ?? 200);
|
||
const height = computed(() => props.loc[3] ?? 200);
|
||
const listLoc = computed<ElementLocator>(() => {
|
||
const listWidth = width.value * basis.value;
|
||
if (props.right) {
|
||
return [width.value - listWidth, 0, listWidth, height.value];
|
||
} else {
|
||
return [0, 0, listWidth, height.value];
|
||
}
|
||
});
|
||
const contentLoc = computed<ElementLocator>(() => {
|
||
const contentWidth = width.value * (1 - basis.value);
|
||
if (props.right) {
|
||
return [0, 0, contentWidth, height.value];
|
||
} else {
|
||
return [width.value - contentWidth, 0, contentWidth, height.value];
|
||
}
|
||
});
|
||
|
||
const update = (key: string) => {
|
||
emit('update', key);
|
||
emit('update:selected', key);
|
||
};
|
||
|
||
return () => (
|
||
<container loc={props.loc}>
|
||
<List
|
||
{...props}
|
||
loc={listLoc.value}
|
||
list={props.list}
|
||
v-model:selected={selected.value}
|
||
onUpdate={update}
|
||
></List>
|
||
<container loc={contentLoc.value}>
|
||
{slots[selected.value]?.(selected.value) ??
|
||
slots.default?.(selected.value)}
|
||
</container>
|
||
{props.close && (
|
||
<text
|
||
loc={props.closeLoc}
|
||
text="关闭"
|
||
cursor="pointer"
|
||
font={props.font}
|
||
></text>
|
||
)}
|
||
</container>
|
||
);
|
||
}, listPageProps);
|