mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
feat: Choice 选项框
This commit is contained in:
parent
ee5b962743
commit
109e77d6a2
@ -126,7 +126,8 @@ export class Text extends RenderItem<ETextEvent> {
|
||||
this.length = width;
|
||||
this.descent = actualBoundingBoxAscent;
|
||||
const height = actualBoundingBoxAscent + actualBoundingBoxDescent;
|
||||
this.size(width, height + this.strokeWidth * 2 + SAFE_PAD * 2);
|
||||
const stroke = this.strokeWidth * 2;
|
||||
this.size(width + stroke, height + stroke + SAFE_PAD * 2);
|
||||
}
|
||||
|
||||
protected handleProps(
|
||||
|
@ -562,13 +562,17 @@ export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
}
|
||||
|
||||
private toTagString(item: RenderItem, space: number, deep: number): string {
|
||||
if (item.isComment) return '';
|
||||
const name = item.constructor.name;
|
||||
if (item.children.size === 0) {
|
||||
return `${' '.repeat(deep * space)}<${name} id="${item.id}" uid="${item.uid}" type="${item.type}"${item.hidden ? ' hidden' : ''}></${name}>\n`;
|
||||
return `${' '.repeat(deep * space)}<${name} ${item.id ? `id="${item.id}" ` : ''}uid="${item.uid}"${item.hidden ? ' hidden' : ''} />\n`;
|
||||
} else {
|
||||
return (
|
||||
`${' '.repeat(deep * space)}<${name} id="${item.id}" uid="${item.uid}" type="${item.type}" ${item.hidden ? 'hidden' : ''}>\n` +
|
||||
`${[...item.children].map(v => this.toTagString(v, space, deep + 1)).join('')}` +
|
||||
`${' '.repeat(deep * space)}<${name} ${item.id ? `${item.id} ` : ''}uid="${item.uid}" ${item.hidden ? 'hidden' : ''}>\n` +
|
||||
`${[...item.children]
|
||||
.filter(v => !v.isComment)
|
||||
.map(v => this.toTagString(v, space, deep + 1))
|
||||
.join('')}` +
|
||||
`${' '.repeat(deep * space)}</${name}>\n`
|
||||
);
|
||||
}
|
||||
|
@ -33,16 +33,22 @@ type _FontStretch =
|
||||
type _FontVariant = 'normal' | 'small-caps';
|
||||
|
||||
export class Font implements IFontConfig {
|
||||
static defaultFamily: string = 'Verdana';
|
||||
static defaultSize: number = 16;
|
||||
static defaultSizeUnit: string = 'px';
|
||||
static defaultWeight: number = 400;
|
||||
static defaultItalic: boolean = false;
|
||||
|
||||
private readonly fallbacks: Font[] = [];
|
||||
|
||||
private fontString: string = '';
|
||||
|
||||
constructor(
|
||||
public readonly family: string = 'Verdana',
|
||||
public readonly size: number = 16,
|
||||
public readonly sizeUnit: string = 'px',
|
||||
public readonly weight: number = 400,
|
||||
public readonly italic: boolean = false
|
||||
public readonly family: string = Font.defaultFamily,
|
||||
public readonly size: number = Font.defaultSize,
|
||||
public readonly sizeUnit: string = Font.defaultSizeUnit,
|
||||
public readonly weight: number = Font.defaultWeight,
|
||||
public readonly italic: boolean = Font.defaultItalic
|
||||
) {
|
||||
this.fontString = this.getFont();
|
||||
}
|
||||
@ -93,11 +99,11 @@ export class Font implements IFontConfig {
|
||||
|
||||
private static parseOne(str: string) {
|
||||
if (!str) return new Font();
|
||||
let italic = false;
|
||||
let weight = 400;
|
||||
let size = 16;
|
||||
let unit = 'px';
|
||||
let family = 'Verdana';
|
||||
let italic = this.defaultItalic;
|
||||
let weight = this.defaultWeight;
|
||||
let size = this.defaultSize;
|
||||
let unit = this.defaultSizeUnit;
|
||||
let family = this.defaultFamily;
|
||||
const tokens = str.split(/\s+/);
|
||||
tokens.forEach(v => {
|
||||
// font-italic
|
||||
@ -136,6 +142,24 @@ export class Font implements IFontConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认字体
|
||||
*/
|
||||
static setDefaults(font: Font) {
|
||||
this.defaultFamily = font.family;
|
||||
this.defaultItalic = font.italic;
|
||||
this.defaultSize = font.size;
|
||||
this.defaultSizeUnit = font.sizeUnit;
|
||||
this.defaultWeight = font.weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认字体
|
||||
*/
|
||||
static defaults() {
|
||||
return new Font();
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制一个字体,同时修改字体的一部分属性
|
||||
* @param font 要复制的字体
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { DefaultProps, ElementLocator, useKey } from '@/core/render';
|
||||
import { computed, defineComponent, ref } from 'vue';
|
||||
import { DefaultProps, ElementLocator, Font, useKey } from '@/core/render';
|
||||
import { computed, defineComponent, reactive, ref } from 'vue';
|
||||
import { Background, Selection } from './misc';
|
||||
import { TextContent, TextContentExpose, TextContentProps } from './textbox';
|
||||
import { SetupComponentOptions } from './types';
|
||||
import { TextAlign } from './textboxTyper';
|
||||
import { Page, PageExpose } from './page';
|
||||
|
||||
export interface ConfirmBoxProps extends DefaultProps, TextContentProps {
|
||||
text: string;
|
||||
width: number;
|
||||
loc: ElementLocator;
|
||||
selFont?: string;
|
||||
selFont?: Font;
|
||||
selFill?: CanvasStyle;
|
||||
pad?: number;
|
||||
yesText?: string;
|
||||
@ -47,6 +48,34 @@ const confirmBoxProps = {
|
||||
keyof ConfirmBoxEmits
|
||||
>;
|
||||
|
||||
/**
|
||||
* 确认框组件,与 2.x 的 drawConfirm 类似,可以键盘操作,
|
||||
* 参数参考 {@link ConfirmBoxProps},事件参考 {@link ConfirmBoxEmits},用例如下:
|
||||
* ```tsx
|
||||
* const onYes = () => console.log('yes');
|
||||
* const onNo = () => console.log('no');
|
||||
*
|
||||
* <ConfirmBox
|
||||
* text="是否要返回标题界面"
|
||||
* width={240}
|
||||
* // 确认框会自动计算宽度和高度,因此不需要手动指定,即使手动指定也无效
|
||||
* loc={[240, 240, void 0, void 0, 0.5, 0.5]}
|
||||
* // 使用 winskin 图片作为背景
|
||||
* winskin="winskin.png"
|
||||
* // 使用颜色作为背景和边框,如果设置了 winskin,那么此参数无效
|
||||
* color="#333"
|
||||
* border="gold"
|
||||
* // 设置选项的字体
|
||||
* selFont="16px Verdana"
|
||||
* // 设置选项的文本颜色
|
||||
* selFill="#d48"
|
||||
* // 完全继承 TextContent 的参数,因此可以填写 fontFamily 参数指定文本字体
|
||||
* fontFamily="Arial"
|
||||
* onYes={onYes}
|
||||
* onNo={onNo}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export const ConfirmBox = defineComponent<
|
||||
ConfirmBoxProps,
|
||||
ConfirmBoxEmits,
|
||||
@ -132,7 +161,6 @@ export const ConfirmBox = defineComponent<
|
||||
<Selection
|
||||
loc={selectLoc.value}
|
||||
winskin={props.winskin}
|
||||
color={props.color}
|
||||
border={props.border}
|
||||
noevent
|
||||
zIndex={10}
|
||||
@ -162,3 +190,305 @@ export const ConfirmBox = defineComponent<
|
||||
</container>
|
||||
);
|
||||
}, confirmBoxProps);
|
||||
|
||||
export interface ChoicesProps extends DefaultProps, TextContentProps {
|
||||
choices: [key: string | number | symbol, text: string][];
|
||||
loc: ElementLocator;
|
||||
width: number;
|
||||
maxHeight?: number;
|
||||
text?: string;
|
||||
title?: string;
|
||||
winskin?: ImageIds;
|
||||
color?: CanvasStyle;
|
||||
border?: CanvasStyle;
|
||||
selFont?: Font;
|
||||
selFill?: CanvasStyle;
|
||||
titleFont?: Font;
|
||||
titleFill?: CanvasStyle;
|
||||
pad?: number;
|
||||
defaultChoice?: string | number | symbol;
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export type ChoicesEmits = {
|
||||
choice: (key: string | number | symbol) => void;
|
||||
};
|
||||
|
||||
const choicesProps = {
|
||||
props: [
|
||||
'choices',
|
||||
'loc',
|
||||
'width',
|
||||
'maxHeight',
|
||||
'text',
|
||||
'title',
|
||||
'winskin',
|
||||
'color',
|
||||
'border',
|
||||
'selFont',
|
||||
'selFill',
|
||||
'titleFont',
|
||||
'titleFill',
|
||||
'pad',
|
||||
'defaultChoice',
|
||||
'interval'
|
||||
],
|
||||
emits: ['choice']
|
||||
} satisfies SetupComponentOptions<
|
||||
ChoicesProps,
|
||||
ChoicesEmits,
|
||||
keyof ChoicesEmits
|
||||
>;
|
||||
|
||||
export const Choices = defineComponent<
|
||||
ChoicesProps,
|
||||
ChoicesEmits,
|
||||
keyof ChoicesEmits
|
||||
>((props, { emit, attrs }) => {
|
||||
const titleHeight = ref(0);
|
||||
const contentHeight = ref(0);
|
||||
const selected = ref(0);
|
||||
const pageCom = ref<PageExpose>();
|
||||
const choiceSize = reactive<[number, number][]>([]);
|
||||
|
||||
const selFont = computed(() => props.selFont ?? new Font());
|
||||
const maxHeight = computed(() => props.maxHeight ?? 360);
|
||||
const pad = computed(() => props.pad ?? 28);
|
||||
const choiceInterval = computed(() => props.interval ?? 16);
|
||||
const hasText = computed(() => !!props.text);
|
||||
const hasTitle = computed(() => !!props.title);
|
||||
const contentWidth = computed(() => props.width - pad.value * 2);
|
||||
const choiceHeight = computed(
|
||||
() => selFont.value.size + 8 + choiceInterval.value
|
||||
);
|
||||
const contentY = computed(() => {
|
||||
if (hasTitle.value) {
|
||||
return pad.value * 2 + titleHeight.value;
|
||||
} else {
|
||||
return pad.value;
|
||||
}
|
||||
});
|
||||
const choicesY = computed(() => {
|
||||
const padding = pad.value;
|
||||
const text = hasText.value;
|
||||
let y = padding;
|
||||
if (hasTitle.value) {
|
||||
y += titleHeight.value;
|
||||
if (text) {
|
||||
y += padding / 2;
|
||||
} else {
|
||||
y += padding;
|
||||
}
|
||||
}
|
||||
if (text) {
|
||||
y += contentHeight.value;
|
||||
y += padding / 2;
|
||||
}
|
||||
return y;
|
||||
});
|
||||
const choicesMaxHeight = computed(
|
||||
() =>
|
||||
maxHeight.value -
|
||||
choicesY.value -
|
||||
pad.value * 2 -
|
||||
selFont.value.size -
|
||||
8
|
||||
);
|
||||
const choiceCountPerPage = computed(() =>
|
||||
Math.max(Math.floor(choicesMaxHeight.value / choiceHeight.value), 1)
|
||||
);
|
||||
const pages = computed(() =>
|
||||
Math.ceil(props.choices.length / choiceCountPerPage.value)
|
||||
);
|
||||
const choicesHeight = computed(() => {
|
||||
const padBottom = pages.value > 1 ? pad.value + selFont.value.size : 0;
|
||||
if (props.choices.length > choiceCountPerPage.value) {
|
||||
return choiceCountPerPage.value * choiceHeight.value + padBottom;
|
||||
} else {
|
||||
return props.choices.length * choiceHeight.value + padBottom;
|
||||
}
|
||||
});
|
||||
const boxHeight = computed(() => {
|
||||
if (props.choices.length > choiceCountPerPage.value) {
|
||||
return (
|
||||
choicesHeight.value +
|
||||
choicesY.value +
|
||||
// 不乘2是因为 choiceY 已经算上了顶部填充
|
||||
pad.value
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
choicesHeight.value +
|
||||
choicesY.value +
|
||||
// 不乘2是因为 choiceY 已经算上了顶部填充
|
||||
pad.value
|
||||
);
|
||||
}
|
||||
});
|
||||
const boxLoc = computed<ElementLocator>(() => {
|
||||
const [x = 0, y = 0, , , ax = 0, ay = 0] = props.loc;
|
||||
return [x, y, props.width, boxHeight.value, ax, ay];
|
||||
});
|
||||
const titleLoc = computed<ElementLocator>(() => {
|
||||
return [props.width / 2, pad.value, void 0, void 0, 0.5, 0];
|
||||
});
|
||||
const contentLoc = computed<ElementLocator>(() => {
|
||||
return [
|
||||
props.width / 2,
|
||||
contentY.value,
|
||||
contentWidth.value,
|
||||
void 0,
|
||||
0.5,
|
||||
0
|
||||
];
|
||||
});
|
||||
const choiceLoc = computed<ElementLocator>(() => {
|
||||
return [
|
||||
props.width / 2,
|
||||
choicesY.value,
|
||||
contentWidth.value,
|
||||
choicesHeight.value,
|
||||
0.5,
|
||||
0
|
||||
];
|
||||
});
|
||||
const selectionLoc = computed<ElementLocator>(() => {
|
||||
const [width = 200, height = 200] = choiceSize[selected.value] ?? [];
|
||||
return [
|
||||
props.width / 2 - pad.value,
|
||||
(selected.value + 0.5) * choiceHeight.value,
|
||||
width + 8,
|
||||
height + 8,
|
||||
0.5,
|
||||
0.5
|
||||
];
|
||||
});
|
||||
|
||||
const getPageContent = (page: number) => {
|
||||
const count = choiceCountPerPage.value;
|
||||
return props.choices.slice(page * count, (page + 1) * count);
|
||||
};
|
||||
|
||||
const getChoiceLoc = (index: number): ElementLocator => {
|
||||
return [
|
||||
props.width / 2 - pad.value,
|
||||
choiceHeight.value * (index + 0.5),
|
||||
void 0,
|
||||
void 0,
|
||||
0.5,
|
||||
0.5
|
||||
];
|
||||
};
|
||||
|
||||
const updateContentHeight = (height: number) => {
|
||||
contentHeight.value = height;
|
||||
};
|
||||
|
||||
const updateTitleHeight = (_0: string, _1: number, height: number) => {
|
||||
titleHeight.value = height;
|
||||
};
|
||||
|
||||
const updateChoiceSize = (index: number, width: number, height: number) => {
|
||||
choiceSize[index] = [width, height];
|
||||
};
|
||||
|
||||
const onPageChange = () => {
|
||||
selected.value = 0;
|
||||
};
|
||||
|
||||
const [key] = useKey();
|
||||
key.realize('moveUp', () => {
|
||||
if (selected.value === 0) {
|
||||
if (pageCom.value?.now() !== 0) {
|
||||
pageCom.value?.movePage(-1);
|
||||
selected.value = choiceCountPerPage.value - 1;
|
||||
}
|
||||
} else {
|
||||
selected.value--;
|
||||
}
|
||||
});
|
||||
key.realize('moveDown', () => {
|
||||
if (selected.value === choiceCountPerPage.value - 1) {
|
||||
pageCom.value?.movePage(1);
|
||||
selected.value = 0;
|
||||
} else {
|
||||
const page = pageCom.value?.now() ?? 1;
|
||||
const index = page * choiceCountPerPage.value + selected.value;
|
||||
if (index < props.choices.length - 1) {
|
||||
selected.value++;
|
||||
}
|
||||
}
|
||||
});
|
||||
key.realize('moveLeft', () => pageCom.value?.movePage(-1));
|
||||
key.realize('moveRight', () => pageCom.value?.movePage(1));
|
||||
key.realize('confirm', () => {
|
||||
const page = pageCom.value?.now() ?? 1;
|
||||
const index = page * choiceCountPerPage.value + selected.value;
|
||||
emit('choice', props.choices[index][0]);
|
||||
});
|
||||
|
||||
return () => (
|
||||
<container loc={boxLoc.value}>
|
||||
<Background
|
||||
loc={[0, 0, props.width, boxHeight.value]}
|
||||
winskin={props.winskin}
|
||||
color={props.color}
|
||||
border={props.border}
|
||||
/>
|
||||
{hasTitle.value && (
|
||||
<text
|
||||
loc={titleLoc.value}
|
||||
text={props.title}
|
||||
font={props.titleFont ?? new Font(void 0, 18)}
|
||||
fillStyle={props.titleFill ?? 'gold'}
|
||||
zIndex={5}
|
||||
onSetText={updateTitleHeight}
|
||||
/>
|
||||
)}
|
||||
{hasText.value && (
|
||||
<TextContent
|
||||
{...attrs}
|
||||
text={props.text}
|
||||
loc={contentLoc.value}
|
||||
width={contentWidth.value}
|
||||
zIndex={5}
|
||||
autoHeight
|
||||
onUpdateHeight={updateContentHeight}
|
||||
/>
|
||||
)}
|
||||
<Page
|
||||
ref={pageCom}
|
||||
loc={choiceLoc.value}
|
||||
pages={pages.value}
|
||||
font={props.selFont}
|
||||
hideIfSingle
|
||||
onPageChange={onPageChange}
|
||||
>
|
||||
{(page: number) => [
|
||||
<Selection
|
||||
loc={selectionLoc.value}
|
||||
winskin={props.winskin}
|
||||
color={props.color}
|
||||
border={props.border}
|
||||
/>,
|
||||
...getPageContent(page).map((v, i) => {
|
||||
return (
|
||||
<text
|
||||
text={v[1]}
|
||||
loc={getChoiceLoc(i)}
|
||||
font={props.selFont}
|
||||
cursor="pointer"
|
||||
zIndex={5}
|
||||
onClick={() => emit('choice', v[0])}
|
||||
onSetText={(_, width, height) =>
|
||||
updateChoiceSize(i, width, height)
|
||||
}
|
||||
onEnter={() => (selected.value = i)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
]}
|
||||
</Page>
|
||||
</container>
|
||||
);
|
||||
}, choicesProps);
|
||||
|
@ -26,12 +26,27 @@ export interface PageProps extends DefaultProps {
|
||||
hideIfSingle?: boolean;
|
||||
}
|
||||
|
||||
export type PageEmits = {
|
||||
pageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
export interface PageExpose {
|
||||
/**
|
||||
* 切换页码
|
||||
* @param page 要切换至的页码数,1 表示第一页
|
||||
*/
|
||||
changePage(page: number): void;
|
||||
|
||||
/**
|
||||
* 切换到传入的页码数加上当前页码数的页码
|
||||
* @param delta 页码数增量
|
||||
*/
|
||||
movePage(delta: number): void;
|
||||
|
||||
/**
|
||||
* 获取当前在第几页
|
||||
*/
|
||||
now(): number;
|
||||
}
|
||||
|
||||
type PageSlots = SlotsType<{
|
||||
@ -39,251 +54,267 @@ type PageSlots = SlotsType<{
|
||||
}>;
|
||||
|
||||
const pageProps = {
|
||||
props: ['pages', 'loc', 'font', 'hideIfSingle']
|
||||
} satisfies SetupComponentOptions<PageProps, {}, string, PageSlots>;
|
||||
props: ['pages', 'loc', 'font', 'hideIfSingle'],
|
||||
emits: ['pageChange']
|
||||
} satisfies SetupComponentOptions<
|
||||
PageProps,
|
||||
PageEmits,
|
||||
keyof PageEmits,
|
||||
PageSlots
|
||||
>;
|
||||
|
||||
/**
|
||||
* 分页组件,用于多页切换,例如存档界面等。参数参考 {@link PageProps},函数接口参考 {@link PageExpose}
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* 用例如下,是一个在每页显示文字的用例,其中 page 表示第几页:
|
||||
* 用例如下,是一个在每页显示文字的用例,其中 page 表示页码索引,第一页就是 0,第二页就是 1,以此类推:
|
||||
* ```tsx
|
||||
* <Page maxPage={5}>
|
||||
* {
|
||||
* (page: number) => {
|
||||
* // 页码从第一页开始,因此这里索引要减一
|
||||
* return items[page - 1].map(v => <text text={v.text} />)
|
||||
* return items[page].map(v => <text text={v.text} />)
|
||||
* }
|
||||
* }
|
||||
* </Page>
|
||||
* ```
|
||||
*/
|
||||
export const Page = defineComponent<PageProps, {}, string, PageSlots>(
|
||||
(props, { slots, expose }) => {
|
||||
const nowPage = ref(1);
|
||||
export const Page = defineComponent<
|
||||
PageProps,
|
||||
PageEmits,
|
||||
keyof PageEmits,
|
||||
PageSlots
|
||||
>((props, { slots, expose, emit }) => {
|
||||
const nowPage = ref(0);
|
||||
|
||||
// 五个元素的位置
|
||||
const leftLoc = ref<ElementLocator>([]);
|
||||
const leftPageLoc = ref<ElementLocator>([]);
|
||||
const nowPageLoc = ref<ElementLocator>([]);
|
||||
const rightPageLoc = ref<ElementLocator>([]);
|
||||
const rightLoc = ref<ElementLocator>([]);
|
||||
/** 内容的位置 */
|
||||
const contentLoc = ref<ElementLocator>([]);
|
||||
/** 页码容器的位置 */
|
||||
const pageLoc = ref<ElementLocator>([]);
|
||||
/** 页码的矩形框的位置 */
|
||||
const rectLoc = ref<ElementLocator>([0, 0, 0, 0]);
|
||||
/** 页面文字的位置 */
|
||||
const textLoc = ref<ElementLocator>([0, 0, 0, 0]);
|
||||
// 五个元素的位置
|
||||
const leftLoc = ref<ElementLocator>([]);
|
||||
const leftPageLoc = ref<ElementLocator>([]);
|
||||
const nowPageLoc = ref<ElementLocator>([]);
|
||||
const rightPageLoc = ref<ElementLocator>([]);
|
||||
const rightLoc = ref<ElementLocator>([]);
|
||||
/** 内容的位置 */
|
||||
const contentLoc = ref<ElementLocator>([]);
|
||||
/** 页码容器的位置 */
|
||||
const pageLoc = ref<ElementLocator>([]);
|
||||
/** 页码的矩形框的位置 */
|
||||
const rectLoc = ref<ElementLocator>([0, 0, 0, 0]);
|
||||
/** 页面文字的位置 */
|
||||
const textLoc = ref<ElementLocator>([0, 0, 0, 0]);
|
||||
|
||||
// 两个监听的参数
|
||||
const leftArrow = ref<Path2D>();
|
||||
const rightArrow = ref<Path2D>();
|
||||
// 两个监听的参数
|
||||
const leftArrow = ref<Path2D>();
|
||||
const rightArrow = ref<Path2D>();
|
||||
|
||||
const font = computed(() => props.font ?? new Font());
|
||||
const isFirst = computed(() => nowPage.value === 1);
|
||||
const isLast = computed(() => nowPage.value === props.pages);
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const round = computed(() => font.value.size / 4);
|
||||
const nowPageFont = computed(() =>
|
||||
Font.clone(font.value, { weight: 700 })
|
||||
);
|
||||
const hide = computed(() => props.hideIfSingle && props.pages === 1);
|
||||
const font = computed(() => props.font ?? new Font());
|
||||
const isFirst = computed(() => nowPage.value === 0);
|
||||
const isLast = computed(() => nowPage.value === props.pages - 1);
|
||||
const width = computed(() => props.loc[2] ?? 200);
|
||||
const height = computed(() => props.loc[3] ?? 200);
|
||||
const round = computed(() => font.value.size / 4);
|
||||
const nowPageFont = computed(() => Font.clone(font.value, { weight: 700 }));
|
||||
|
||||
// 左右箭头的颜色
|
||||
const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd'));
|
||||
const rightColor = computed(() => (isLast.value ? '#666' : '#ddd'));
|
||||
// 左右箭头的颜色
|
||||
const leftColor = computed(() => (isFirst.value ? '#666' : '#ddd'));
|
||||
const rightColor = computed(() => (isLast.value ? '#666' : '#ddd'));
|
||||
|
||||
let updating = false;
|
||||
const updatePagePos = () => {
|
||||
if (updating) return;
|
||||
updating = true;
|
||||
nextTick(() => {
|
||||
updating = false;
|
||||
});
|
||||
const pageH = font.value.size + 8;
|
||||
contentLoc.value = [0, 0, width.value, height.value - pageH];
|
||||
pageLoc.value = [0, height.value - pageH, width.value, pageH];
|
||||
const center = width.value / 2;
|
||||
const size = font.value.size * 1.5;
|
||||
nowPageLoc.value = [center, 0, size, size, 0.5, 0];
|
||||
leftPageLoc.value = [center - size * 1.5, 0, size, size, 0.5, 0];
|
||||
leftLoc.value = [center - size * 3, 0, size, size, 0.5, 0];
|
||||
rightPageLoc.value = [center + size * 1.5, 0, size, size, 0.5, 0];
|
||||
rightLoc.value = [center + size * 3, 0, size, size, 0.5, 0];
|
||||
};
|
||||
|
||||
const updateArrowPath = () => {
|
||||
const rectSize = font.value.size * 1.5;
|
||||
const size = font.value.size;
|
||||
const pad = rectSize - size;
|
||||
const left = new Path2D();
|
||||
left.moveTo(size, pad);
|
||||
left.lineTo(pad, rectSize / 2);
|
||||
left.lineTo(size, rectSize - pad);
|
||||
const right = new Path2D();
|
||||
right.moveTo(pad, pad);
|
||||
right.lineTo(size, rectSize / 2);
|
||||
right.lineTo(pad, rectSize - pad);
|
||||
leftArrow.value = left;
|
||||
rightArrow.value = right;
|
||||
};
|
||||
|
||||
const updateRectAndText = () => {
|
||||
const size = font.value.size * 1.5;
|
||||
const pad = RECT_PAD * size;
|
||||
rectLoc.value = [pad, pad, size - pad * 2, size - pad * 2];
|
||||
textLoc.value = [size / 2, size / 2, void 0, void 0, 0.5, 0.5];
|
||||
};
|
||||
|
||||
watch(font, () => {
|
||||
updatePagePos();
|
||||
updateArrowPath();
|
||||
updateRectAndText();
|
||||
let updating = false;
|
||||
const updatePagePos = () => {
|
||||
if (updating) return;
|
||||
updating = true;
|
||||
nextTick(() => {
|
||||
updating = false;
|
||||
});
|
||||
watch(
|
||||
() => props.loc,
|
||||
() => {
|
||||
updatePagePos();
|
||||
updateRectAndText();
|
||||
}
|
||||
);
|
||||
const pageH = hide.value ? 0 : font.value.size + 8;
|
||||
contentLoc.value = [0, 0, width.value, height.value - pageH];
|
||||
pageLoc.value = [0, height.value - pageH, width.value, pageH];
|
||||
const center = width.value / 2;
|
||||
const size = font.value.size * 1.5;
|
||||
nowPageLoc.value = [center, 0, size, size, 0.5, 0];
|
||||
leftPageLoc.value = [center - size * 1.5, 0, size, size, 0.5, 0];
|
||||
leftLoc.value = [center - size * 3, 0, size, size, 0.5, 0];
|
||||
rightPageLoc.value = [center + size * 1.5, 0, size, size, 0.5, 0];
|
||||
rightLoc.value = [center + size * 3, 0, size, size, 0.5, 0];
|
||||
};
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
const changePage = (page: number) => {
|
||||
const target = clamp(page, 1, props.pages);
|
||||
const updateArrowPath = () => {
|
||||
const rectSize = font.value.size * 1.5;
|
||||
const size = font.value.size;
|
||||
const pad = rectSize - size;
|
||||
const left = new Path2D();
|
||||
left.moveTo(size, pad);
|
||||
left.lineTo(pad, rectSize / 2);
|
||||
left.lineTo(size, rectSize - pad);
|
||||
const right = new Path2D();
|
||||
right.moveTo(pad, pad);
|
||||
right.lineTo(size, rectSize / 2);
|
||||
right.lineTo(pad, rectSize - pad);
|
||||
leftArrow.value = left;
|
||||
rightArrow.value = right;
|
||||
};
|
||||
|
||||
const updateRectAndText = () => {
|
||||
const size = font.value.size * 1.5;
|
||||
const pad = RECT_PAD * size;
|
||||
rectLoc.value = [pad, pad, size - pad * 2, size - pad * 2];
|
||||
textLoc.value = [size / 2, size / 2, void 0, void 0, 0.5, 0.5];
|
||||
};
|
||||
|
||||
watch(font, () => {
|
||||
updatePagePos();
|
||||
updateArrowPath();
|
||||
updateRectAndText();
|
||||
});
|
||||
watch(
|
||||
() => props.loc,
|
||||
() => {
|
||||
updatePagePos();
|
||||
updateRectAndText();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
*/
|
||||
const changePage = (page: number) => {
|
||||
const target = clamp(page, 0, props.pages - 1);
|
||||
if (nowPage.value !== target) {
|
||||
nowPage.value = target;
|
||||
};
|
||||
emit('pageChange', target);
|
||||
}
|
||||
};
|
||||
|
||||
const lastPage = () => {
|
||||
changePage(nowPage.value - 1);
|
||||
};
|
||||
const movePage = (delta: number) => {
|
||||
changePage(nowPage.value + delta);
|
||||
};
|
||||
|
||||
const nextPage = () => {
|
||||
changePage(nowPage.value + 1);
|
||||
};
|
||||
const now = () => nowPage.value;
|
||||
|
||||
onMounted(() => {
|
||||
updatePagePos();
|
||||
updateArrowPath();
|
||||
updateRectAndText();
|
||||
});
|
||||
const lastPage = () => {
|
||||
changePage(nowPage.value - 1);
|
||||
};
|
||||
|
||||
expose({ changePage });
|
||||
const nextPage = () => {
|
||||
changePage(nowPage.value + 1);
|
||||
};
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<container loc={props.loc}>
|
||||
<container loc={contentLoc.value}>
|
||||
{slots.default?.(nowPage.value)}
|
||||
</container>
|
||||
onMounted(() => {
|
||||
updatePagePos();
|
||||
updateArrowPath();
|
||||
updateRectAndText();
|
||||
});
|
||||
|
||||
expose<PageExpose>({ changePage, movePage, now });
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<container loc={props.loc}>
|
||||
<container loc={contentLoc.value}>
|
||||
{slots.default?.(nowPage.value)}
|
||||
</container>
|
||||
<container loc={pageLoc.value} hidden={hide.value}>
|
||||
<container
|
||||
loc={pageLoc.value}
|
||||
hidden={props.hideIfSingle && props.pages === 1}
|
||||
key={1}
|
||||
loc={leftLoc.value}
|
||||
onClick={lastPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle={leftColor.value}
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<g-path
|
||||
path={leftArrow.value}
|
||||
stroke
|
||||
strokeStyle={leftColor.value}
|
||||
lineWidth={1}
|
||||
></g-path>
|
||||
</container>
|
||||
{!isFirst.value && (
|
||||
<container
|
||||
loc={leftLoc.value}
|
||||
key={2}
|
||||
loc={leftPageLoc.value}
|
||||
onClick={lastPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle={leftColor.value}
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<g-path
|
||||
path={leftArrow.value}
|
||||
stroke
|
||||
strokeStyle={leftColor.value}
|
||||
lineWidth={1}
|
||||
></g-path>
|
||||
</container>
|
||||
{!isFirst.value && (
|
||||
<container
|
||||
loc={leftPageLoc.value}
|
||||
onClick={lastPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle="#ddd"
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<text
|
||||
loc={textLoc.value}
|
||||
text={(nowPage.value - 1).toString()}
|
||||
font={font.value}
|
||||
></text>
|
||||
</container>
|
||||
)}
|
||||
<container loc={nowPageLoc.value}>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle="#ddd"
|
||||
fillStyle="#ddd"
|
||||
lineWidth={1}
|
||||
fill
|
||||
stroke
|
||||
></g-rectr>
|
||||
<text
|
||||
loc={textLoc.value}
|
||||
text={nowPage.value.toString()}
|
||||
fillStyle="#222"
|
||||
font={nowPageFont.value}
|
||||
font={font.value}
|
||||
></text>
|
||||
</container>
|
||||
{!isLast.value && (
|
||||
<container
|
||||
loc={rightPageLoc.value}
|
||||
onClick={nextPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle="#ddd"
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<text
|
||||
loc={textLoc.value}
|
||||
text={(nowPage.value + 1).toString()}
|
||||
font={font.value}
|
||||
></text>
|
||||
</container>
|
||||
)}
|
||||
)}
|
||||
<container loc={nowPageLoc.value} key={3}>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle="#ddd"
|
||||
fillStyle="#ddd"
|
||||
lineWidth={1}
|
||||
fill
|
||||
stroke
|
||||
></g-rectr>
|
||||
<text
|
||||
loc={textLoc.value}
|
||||
text={(nowPage.value + 1).toString()}
|
||||
fillStyle="#222"
|
||||
font={nowPageFont.value}
|
||||
></text>
|
||||
</container>
|
||||
{!isLast.value && (
|
||||
<container
|
||||
loc={rightLoc.value}
|
||||
key={4}
|
||||
loc={rightPageLoc.value}
|
||||
onClick={nextPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle={rightColor.value}
|
||||
strokeStyle="#ddd"
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<g-path
|
||||
path={rightArrow.value}
|
||||
stroke
|
||||
strokeStyle={rightColor.value}
|
||||
lineWidth={1}
|
||||
></g-path>
|
||||
<text
|
||||
loc={textLoc.value}
|
||||
text={(nowPage.value + 2).toString()}
|
||||
font={font.value}
|
||||
></text>
|
||||
</container>
|
||||
)}
|
||||
<container
|
||||
key={5}
|
||||
loc={rightLoc.value}
|
||||
onClick={nextPage}
|
||||
cursor="pointer"
|
||||
>
|
||||
<g-rectr
|
||||
loc={rectLoc.value}
|
||||
circle={[round.value]}
|
||||
strokeStyle={rightColor.value}
|
||||
lineWidth={1}
|
||||
stroke
|
||||
></g-rectr>
|
||||
<g-path
|
||||
path={rightArrow.value}
|
||||
stroke
|
||||
strokeStyle={rightColor.value}
|
||||
lineWidth={1}
|
||||
></g-path>
|
||||
</container>
|
||||
</container>
|
||||
);
|
||||
};
|
||||
},
|
||||
pageProps
|
||||
);
|
||||
</container>
|
||||
);
|
||||
};
|
||||
}, pageProps);
|
||||
|
@ -19,7 +19,7 @@ import { FloorItemDetail } from '@/plugin/fx/itemDetail';
|
||||
import { PopText } from '@/plugin/fx/pop';
|
||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
||||
import { defineComponent, onMounted, reactive, ref } from 'vue';
|
||||
import { Textbox } from '../components';
|
||||
import { Textbox, Tip } from '../components';
|
||||
import { GameUI, UIController } from '@/core/system';
|
||||
import {
|
||||
MAIN_HEIGHT,
|
||||
@ -35,7 +35,6 @@ import {
|
||||
} from './statusBar';
|
||||
import { onLoaded } from '../use';
|
||||
import { ReplayingStatus } from './toolbar';
|
||||
import { Tip } from '../components/tip';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
|
Loading…
Reference in New Issue
Block a user