From 06abef25952d3e6013c0843f1087d92fbe2be634 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sat, 22 Feb 2025 20:46:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B5=E7=A0=81=20&=20chore:=20?= =?UTF-8?q?=E6=9A=82=E6=97=B6=E7=A7=BB=E9=99=A4=20cursor=20=E5=B1=9E?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/render/item.ts | 8 - src/core/render/preset/graphics.ts | 113 ++++++++---- src/core/render/preset/misc.ts | 7 +- src/core/render/render.ts | 4 +- src/core/render/renderer/props.ts | 33 +--- src/module/render/components/page.tsx | 255 ++++++++++++++++++++++++++ src/module/render/ui/statusBar.tsx | 25 ++- 7 files changed, 357 insertions(+), 88 deletions(-) create mode 100644 src/module/render/components/page.tsx diff --git a/src/core/render/item.ts b/src/core/render/item.ts index 434f617..f1b341c 100644 --- a/src/core/render/item.ts +++ b/src/core/render/item.ts @@ -271,9 +271,6 @@ export abstract class RenderItem /** 不透明度 */ alpha: number = 1; - /** 鼠标覆盖在此元素上时的光标样式 */ - cursor: string = 'auto'; - get x() { return this._transform.x; } @@ -1152,11 +1149,6 @@ export abstract class RenderItem this.setAnchor(nextValue[0] as number, nextValue[1] as number); return; } - case 'cursor': { - if (!this.assertType(nextValue, 'string', key)) return; - this.cursor = nextValue; - return; - } case 'scale': { if (!this.assertType(nextValue, Array, key)) return; this._transform.setScale( diff --git a/src/core/render/preset/graphics.ts b/src/core/render/preset/graphics.ts index c64e5b3..3418ab2 100644 --- a/src/core/render/preset/graphics.ts +++ b/src/core/render/preset/graphics.ts @@ -5,6 +5,57 @@ import { ElementNamespace, ComponentInternalInstance } from 'vue'; import { clamp, isNil } from 'lodash-es'; import { logger } from '@/core/common/logger'; +export type CircleParams = [ + cx?: number, + cy?: number, + radius?: number, + start?: number, + end?: number +]; +export type EllipseParams = [ + cx?: number, + cy?: number, + radiusX?: number, + radiusY?: number, + start?: number, + end?: number +]; +export type LineParams = [x1: number, y1: number, x2: number, y2: number]; +export type BezierParams = [ + sx: number, + sy: number, + cp1x: number, + cp1y: number, + cp2x: number, + cp2y: number, + ex: number, + ey: number +]; +export type QuadParams = [ + sx: number, + sy: number, + cpx: number, + cpy: number, + ex: number, + ey: number +]; +export type RectRCircleParams = [ + r1: number, + r2?: number, + r3?: number, + r4?: number +]; +export type RectREllipseParams = [ + rx1: number, + ry1: number, + rx2?: number, + ry2?: number, + rx3?: number, + ry3?: number, + rx4?: number, + ry4?: number +]; + export interface ILineProperty { /** 线宽 */ lineWidth: number; @@ -376,18 +427,21 @@ export class Circle extends GraphicItemBase { if (!this.assertType(nextValue, 'number', key)) return; this.setAngle(this.start, nextValue); return; - case 'circle': - if (!this.assertType(nextValue, Array, key)) return; - if (!isNil(nextValue[0])) { - this.setRadius(nextValue[0] as number); + case 'circle': { + const value = nextValue as CircleParams; + if (!this.assertType(value, Array, key)) return; + const [cx, cy, radius, start, end] = value; + if (!isNil(cx) && !isNil(cy)) { + this.pos(cx, cy); } - if (!isNil(nextValue[1]) && !isNil(nextValue[2])) { - this.setAngle( - nextValue[1] as number, - nextValue[2] as number - ); + if (!isNil(radius)) { + this.setRadius(radius); + } + if (!isNil(start) && !isNil(end)) { + this.setAngle(start, end); } return; + } } super.patchProp(key, prevValue, nextValue, namespace, parentComponent); } @@ -464,21 +518,21 @@ export class Ellipse extends GraphicItemBase { if (!this.assertType(nextValue, 'number', key)) return; this.setAngle(this.start, nextValue); return; - case 'ellipse': - if (!this.assertType(nextValue, Array, key)) return; - if (!isNil(nextValue[0]) && !isNil(nextValue[1])) { - this.setRadius( - nextValue[0] as number, - nextValue[1] as number - ); + case 'ellipse': { + const value = nextValue as EllipseParams; + if (!this.assertType(value, Array, key)) return; + const [cx, cy, radiusX, radiusY, start, end] = value; + if (!isNil(cx) && !isNil(cy)) { + this.pos(cx, cy); } - if (!isNil(nextValue[2]) && !isNil(nextValue[3])) { - this.setAngle( - nextValue[2] as number, - nextValue[3] as number - ); + if (!isNil(radiusX) && !isNil(radiusY)) { + this.setRadius(radiusX, radiusY); + } + if (!isNil(start) && !isNil(end)) { + this.setAngle(start, end); } return; + } } super.patchProp(key, prevValue, nextValue, namespace, parentComponent); } @@ -858,23 +912,6 @@ export const enum RectRCorner { BottomLeft } -export type RectRCircleParams = [ - r1: number, - r2?: number, - r3?: number, - r4?: number -]; -export type RectREllipseParams = [ - rx1: number, - ry1: number, - rx2?: number, - ry2?: number, - rx3?: number, - ry3?: number, - rx4?: number, - ry4?: number -]; - export class RectR extends GraphicItemBase { /** 圆角属性,四元素数组,每个元素是一个二元素数组,表示这个角的半径,顺序为 左上,右上,右下,左下 */ readonly corner: [radiusX: number, radiusY: number][] = [ diff --git a/src/core/render/preset/misc.ts b/src/core/render/preset/misc.ts index 38714ca..9478f64 100644 --- a/src/core/render/preset/misc.ts +++ b/src/core/render/preset/misc.ts @@ -107,10 +107,11 @@ export class Text extends RenderItem { * 计算字体所占空间,从而确定这个元素的大小 */ calBox() { - const { width, fontBoundingBoxAscent } = this.measure(); + const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } = + this.measure(); this.length = width; - this.descent = fontBoundingBoxAscent; - this.size(width, fontBoundingBoxAscent); + this.descent = actualBoundingBoxAscent; + this.size(width, actualBoundingBoxAscent + actualBoundingBoxDescent); } patchProp( diff --git a/src/core/render/render.ts b/src/core/render/render.ts index f979229..5da9e78 100644 --- a/src/core/render/render.ts +++ b/src/core/render/render.ts @@ -450,9 +450,7 @@ export class MotaRenderer extends Container implements IRenderTreeRoot { return this.target.canvas; } - hoverElement(element: RenderItem): void { - this.target.canvas.style.cursor = element.cursor; - } + hoverElement(_element: RenderItem): void {} destroy() { super.destroy(); diff --git a/src/core/render/renderer/props.ts b/src/core/render/renderer/props.ts index 76090b4..6fa05cb 100644 --- a/src/core/render/renderer/props.ts +++ b/src/core/render/renderer/props.ts @@ -7,7 +7,12 @@ import { } from '../preset/layer'; import type { EnemyCollection } from '@/game/enemy/damage'; import { + BezierParams, + CircleParams, + EllipseParams, ILineProperty, + LineParams, + QuadParams, RectRCircleParams, RectREllipseParams } from '../preset/graphics'; @@ -40,7 +45,6 @@ export interface BaseProps { id?: string; alpha?: number; composite?: GlobalCompositeOperation; - cursor?: string; /** * 定位属性,可以填 `[横坐标,纵坐标,宽度,高度,x锚点,y锚点]`, * 对于横坐标与纵坐标、宽度与高度、x锚点与y锚点,两两一组要么都填,要么都不填 @@ -132,33 +136,6 @@ export interface GraphicPropsBase extends BaseProps, Partial { strokeStyle?: CanvasStyle; } -export type CircleParams = [radius?: number, start?: number, end?: number]; -export type EllipseParams = [ - radiusX?: number, - radiusY?: number, - start?: number, - end?: number -]; -export type LineParams = [x1: number, y1: number, x2: number, y2: number]; -export type BezierParams = [ - sx: number, - sy: number, - cp1x: number, - cp1y: number, - cp2x: number, - cp2y: number, - ex: number, - ey: number -]; -export type QuadParams = [ - sx: number, - sy: number, - cpx: number, - cpy: number, - ex: number, - ey: number -]; - export interface RectProps extends GraphicPropsBase {} export interface CirclesProps extends GraphicPropsBase { diff --git a/src/module/render/components/page.tsx b/src/module/render/components/page.tsx new file mode 100644 index 0000000..a8cb664 --- /dev/null +++ b/src/module/render/components/page.tsx @@ -0,0 +1,255 @@ +import { + computed, + defineComponent, + nextTick, + onMounted, + ref, + SlotsType, + VNode, + watch +} from 'vue'; +import { SetupComponentOptions } from './types'; +import { clamp } from 'lodash-es'; +import { ElementLocator } from '@/core/render'; + +/** 圆角矩形页码距离容器的边框大小,与 pageSize 相乘 */ +const RECT_PAD = 0.1; + +export interface PageProps { + /** 共有多少页 */ + pages: number; + /** 页码组件的定位 */ + loc: ElementLocator; + /** 页码的字体大小,默认为 14 */ + pageSize?: number; +} + +export interface PageExpose { + /** + * 切换页码 + * @param page 要切换至的页码数,1 表示第一页 + */ + changePage(page: number): void; +} + +type PageSlots = SlotsType<{ + default: (page: number) => VNode | VNode[]; +}>; + +const pageProps = { + props: ['pages', 'loc', 'pageSize'] +} satisfies SetupComponentOptions; + +export const Page = defineComponent( + (props, { slots, expose }) => { + const nowPage = ref(1); + + // 五个元素的位置 + const leftLoc = ref([]); + const leftPageLoc = ref([]); + const nowPageLoc = ref([]); + const rightPageLoc = ref([]); + const rightLoc = ref([]); + /** 内容的位置 */ + const contentLoc = ref([]); + /** 页码容器的位置 */ + const pageLoc = ref([]); + /** 页码的矩形框的位置 */ + const rectLoc = ref([0, 0, 0, 0]); + /** 页面文字的位置 */ + const textLoc = ref([0, 0, 0, 0]); + + // 两个监听的参数 + const leftArrow = ref(new Path2D()); + const rightArrow = ref(new Path2D()); + + const isFirst = computed(() => nowPage.value === 1); + const isLast = computed(() => nowPage.value === props.pages); + const pageSize = computed(() => props.pageSize ?? 14); + const width = computed(() => props.loc[2] ?? 200); + const height = computed(() => props.loc[3] ?? 200); + const round = computed(() => pageSize.value / 4); + const pageFont = computed(() => `${pageSize.value}px normal`); + + // 左右箭头的颜色 + 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 = pageSize.value + 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 = pageSize.value * 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 = pageSize.value * 1.5; + const size = pageSize.value; + 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 = pageSize.value * 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(pageSize, () => { + updatePagePos(); + updateArrowPath(); + updateRectAndText(); + }); + watch( + () => props.loc, + () => { + updatePagePos(); + updateRectAndText(); + } + ); + + /** + * 切换页码 + */ + const changePage = (page: number) => { + const target = clamp(page, 1, props.pages); + nowPage.value = target; + }; + + const lastPage = () => { + changePage(nowPage.value - 1); + }; + + const nextPage = () => { + changePage(nowPage.value + 1); + }; + + onMounted(() => { + updatePagePos(); + updateArrowPath(); + updateRectAndText(); + }); + + expose({ changePage }); + + return () => { + return ( + + + {slots.default?.(nowPage.value)} + + + + + + + {!isFirst.value && ( + + + + + )} + + + + + {!isLast.value && ( + + + + + )} + + + + + + + ); + }; + }, + pageProps +); diff --git a/src/module/render/ui/statusBar.tsx b/src/module/render/ui/statusBar.tsx index 2b08506..df25300 100644 --- a/src/module/render/ui/statusBar.tsx +++ b/src/module/render/ui/statusBar.tsx @@ -3,6 +3,7 @@ import { defineComponent } from 'vue'; import { SetupComponentOptions } from '../components'; import { ElementLocator } from '@/core/render'; import { Scroll } from '../components/scroll'; +import { Page } from '../components/page'; export interface ILeftHeroStatus { hp: number; @@ -91,7 +92,6 @@ export const LeftStatusBar = defineComponent>( text={floorName} loc={central(24)} font={font1} - cursor="pointer" > @@ -144,17 +144,11 @@ export const LeftStatusBar = defineComponent>( font={font2} fillStyle="#f88" > - + ); @@ -170,6 +164,21 @@ export const RightStatusBar = defineComponent>( + + {(page: number) => { + switch (page) { + case 1: { + return ; + } + case 2: { + return ; + } + case 3: { + return ; + } + } + }} + ); };