diff --git a/README.md b/README.md index 27156d74..99439bf2 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,6 @@ npm run serve # 📅 后续规划 - 组合元素重构:能够支持组合元素进行旋转、缩放、整体执行动画等; -- 添加强化版基础形状:支持调整圆角矩形弧度、调整三角形顶点位置等操作; -- 移动端简单编辑支持; - 导入本地PPTX文件; - 将 Vue CLI 更换到 Vite 生态; diff --git a/src/configs/shapes.ts b/src/configs/shapes.ts index fab59606..b75f4128 100644 --- a/src/configs/shapes.ts +++ b/src/configs/shapes.ts @@ -14,56 +14,196 @@ interface ShapeListItem { } export const SHAPE_PATH_FORMULAS = { - [ShapePathFormulasKeys.ROUND_RECT]: (width: number, height: number) => { - const radius = Math.min(width, height) / 8 - return `M ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius} Q ${width} ${height} ${width - radius} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 ${radius} Q 0 0 ${radius} 0 Z` + [ShapePathFormulasKeys.ROUND_RECT]: { + editable: true, + defaultValue: 0.125, + range: [0, 0.5], + relative: 'left', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius} Q ${width} ${height} ${width - radius} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 ${radius} Q 0 0 ${radius} 0 Z` + } }, - [ShapePathFormulasKeys.CUT_RECT_DIAGONAL]: (width: number, height: number) => { - const radius = Math.min(width, height) / 5 - return `M 0 ${height - radius} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L ${radius} ${height} Z` + [ShapePathFormulasKeys.CUT_RECT_DIAGONAL]: { + editable: true, + defaultValue: 0.2, + range: [0, 0.9], + relative: 'right', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 ${height - radius} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L ${radius} ${height} Z` + } }, - [ShapePathFormulasKeys.CUT_RECT_SINGLE]: (width: number, height: number) => { - const radius = Math.min(width, height) / 5 - return `M 0 ${height} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} Z` + [ShapePathFormulasKeys.CUT_RECT_SINGLE]: { + editable: true, + defaultValue: 0.2, + range: [0, 0.9], + relative: 'right', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 ${height} L 0 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} Z` + } }, - [ShapePathFormulasKeys.CUT_RECT_SAMESIDE]: (width: number, height: number) => { - const radius = Math.min(width, height) / 5 - return `M 0 ${radius} L ${radius} 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + [ShapePathFormulasKeys.CUT_RECT_SAMESIDE]: { + editable: true, + defaultValue: 0.2, + range: [0, 0.5], + relative: 'left', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 ${radius} L ${radius} 0 L ${width - radius} 0 L ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + } }, - [ShapePathFormulasKeys.ROUND_RECT_DIAGONAL]: (width: number, height: number) => { - const radius = Math.min(width, height) / 8 - return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 0 Z` + [ShapePathFormulasKeys.ROUND_RECT_DIAGONAL]: { + editable: true, + defaultValue: 0.125, + range: [0, 1], + relative: 'right', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L ${radius} ${height} Q 0 ${height} 0 ${height - radius} L 0 0 Z` + } }, - [ShapePathFormulasKeys.ROUND_RECT_SINGLE]: (width: number, height: number) => { - const radius = Math.min(width, height) / 8 - return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} L 0 0 Z` + [ShapePathFormulasKeys.ROUND_RECT_SINGLE]: { + editable: true, + defaultValue: 0.125, + range: [0, 1], + relative: 'right', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} L 0 0 Z` + } }, - [ShapePathFormulasKeys.ROUND_RECT_SAMESIDE]: (width: number, height: number) => { - const radius = Math.min(width, height) / 8 - return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + [ShapePathFormulasKeys.ROUND_RECT_SAMESIDE]: { + editable: true, + defaultValue: 0.125, + range: [0, 0.5], + relative: 'left', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const radius = Math.min(width, height) * value + return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height} L 0 ${height} Z` + } }, - [ShapePathFormulasKeys.MESSAGE]: (width: number, height: number) => { - const arrowWidth = width / 5 - const arrowheight = height / 5 - return `M 0 0 L ${width} 0 L ${width} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L 0 ${height - arrowheight} Z` + [ShapePathFormulasKeys.MESSAGE]: { + formula: (width: number, height: number) => { + const arrowWidth = width * 0.2 + const arrowheight = height * 0.2 + return `M 0 0 L ${width} 0 L ${width} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L 0 ${height - arrowheight} Z` + } }, - [ShapePathFormulasKeys.ROUND_MESSAGE]: (width: number, height: number) => { - const radius = Math.min(width, height) / 8 - const arrowWidth = width / 5 - const arrowheight = height / 5 - return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius - arrowheight} Q ${width} ${height - arrowheight} ${width - radius} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L ${radius} ${height - arrowheight} Q 0 ${height - arrowheight} 0 ${height - radius - arrowheight} L 0 ${radius} Z` + [ShapePathFormulasKeys.ROUND_MESSAGE]: { + formula: (width: number, height: number) => { + const radius = Math.min(width, height) * 0.125 + const arrowWidth = width * 0.2 + const arrowheight = height * 0.2 + return `M 0 ${radius} Q 0 0 ${radius} 0 L ${width - radius} 0 Q ${width} 0 ${width} ${radius} L ${width} ${height - radius - arrowheight} Q ${width} ${height - arrowheight} ${width - radius} ${height - arrowheight} L ${width / 2} ${height - arrowheight} L ${width / 2 - arrowWidth} ${height} L ${width / 2 - arrowWidth} ${height - arrowheight} L ${radius} ${height - arrowheight} Q 0 ${height - arrowheight} 0 ${height - radius - arrowheight} L 0 ${radius} Z` + } }, - [ShapePathFormulasKeys.L]: (width: number, height: number) => { - const lineWidth = Math.min(width, height) / 4 - return `M 0 0 L 0 ${height} L ${width} ${height} L ${width} ${height - lineWidth} L ${lineWidth} ${height - lineWidth} L ${lineWidth} 0 Z` + [ShapePathFormulasKeys.L]: { + editable: true, + defaultValue: 0.25, + range: [0.1, 0.9], + relative: 'left', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const lineWidth = Math.min(width, height) * value + return `M 0 0 L 0 ${height} L ${width} ${height} L ${width} ${height - lineWidth} L ${lineWidth} ${height - lineWidth} L ${lineWidth} 0 Z` + } }, - [ShapePathFormulasKeys.RING_RECT]: (width: number, height: number) => { - const lineWidth = Math.min(width, height) / 4 - return `M 0 0 ${width} 0 ${width} ${height} L 0 ${height} L 0 0 Z M ${lineWidth} ${lineWidth} L ${lineWidth} ${height - lineWidth} L ${width - lineWidth} ${height - lineWidth} L ${width - lineWidth} ${lineWidth} Z` + [ShapePathFormulasKeys.RING_RECT]: { + editable: true, + defaultValue: 0.25, + range: [0.1, 0.45], + relative: 'left', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const lineWidth = Math.min(width, height) * value + return `M 0 0 ${width} 0 ${width} ${height} L 0 ${height} L 0 0 Z M ${lineWidth} ${lineWidth} L ${lineWidth} ${height - lineWidth} L ${width - lineWidth} ${height - lineWidth} L ${width - lineWidth} ${lineWidth} Z` + } }, - [ShapePathFormulasKeys.PLUS]: (width: number, height: number) => { - const lineWidth = Math.min(width, height) / 4 - return `M ${width / 2 - lineWidth / 2} 0 L ${width / 2 - lineWidth / 2} ${height / 2 - lineWidth / 2} L 0 ${height / 2 - lineWidth / 2} L 0 ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} 0 Z` + [ShapePathFormulasKeys.PLUS]: { + editable: true, + defaultValue: 0.25, + range: [0.1, 0.9], + relative: 'center', + getBaseSize: (width: number, height: number) => Math.min(width, height), + formula: (width: number, height: number, value: number) => { + const lineWidth = Math.min(width, height) * value + return `M ${width / 2 - lineWidth / 2} 0 L ${width / 2 - lineWidth / 2} ${height / 2 - lineWidth / 2} L 0 ${height / 2 - lineWidth / 2} L 0 ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width / 2 - lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height} L ${width / 2 + lineWidth / 2} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 + lineWidth / 2} L ${width} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} ${height / 2 - lineWidth / 2} L ${width / 2 + lineWidth / 2} 0 Z` + } + }, + [ShapePathFormulasKeys.TRIANGLE]: { + editable: true, + defaultValue: 0.5, + range: [0, 1], + relative: 'left', + getBaseSize: (width: number, height: number) => width, + formula: (width: number, height: number, value: number) => { + const vertex = width * value + return `M ${vertex} 0 L 0 ${height} L ${width} ${height} Z` + } + }, + [ShapePathFormulasKeys.PARALLELOGRAM_LEFT]: { + editable: true, + defaultValue: 0.25, + range: [0, 0.9], + relative: 'left', + getBaseSize: (width: number, height: number) => width, + formula: (width: number, height: number, value: number) => { + const point = width * value + return `M ${point} 0 L ${width} 0 L ${width - point} ${height} L 0 ${height} Z` + } + }, + [ShapePathFormulasKeys.PARALLELOGRAM_RIGHT]: { + editable: true, + defaultValue: 0.25, + range: [0, 0.9], + relative: 'right', + getBaseSize: (width: number, height: number) => width, + formula: (width: number, height: number, value: number) => { + const point = width * value + return `M 0 0 L ${width - point} 0 L ${width} ${height} L ${point} ${height} Z` + } + }, + [ShapePathFormulasKeys.TRAPEZOID]: { + editable: true, + defaultValue: 0.25, + range: [0, 0.5], + relative: 'left', + getBaseSize: (width: number, height: number) => width, + formula: (width: number, height: number, value: number) => { + const point = width * value + return `M ${point} 0 L ${width - point} 0 L ${width} ${height} L 0 ${height} Z` + } + }, + [ShapePathFormulasKeys.BULLET]: { + editable: true, + defaultValue: 0.2, + range: [0, 1], + relative: 'top', + getBaseSize: (width: number, height: number) => height, + formula: (width: number, height: number, value: number) => { + const point = height * value + return `M ${width / 2} 0 L 0 ${point} L 0 ${height} L ${width} ${height} L ${width} ${point} Z` + } + }, + [ShapePathFormulasKeys.INDICATOR]: { + editable: true, + defaultValue: 0.2, + range: [0, 0.9], + relative: 'right', + getBaseSize: (width: number, height: number) => width, + formula: (width: number, height: number, value: number) => { + const point = width * value + return `M ${width} ${height / 2} L ${width - point} 0 L 0 0 L ${point} ${height / 2} L 0 ${height} L ${width - point} ${height} Z` + } }, } @@ -120,6 +260,44 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 100 0 A 50 50 0 1 1 100 200 A 50 50 0 1 1 100 0 Z' }, + { + viewBox: [200, 200], + path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z', + pathFormula: ShapePathFormulasKeys.TRIANGLE, + }, + { + viewBox: [200, 200], + path: 'M 0 0 L 0 200 L 200 200 Z' + }, + { + viewBox: [200, 200], + path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z', + pathFormula: ShapePathFormulasKeys.PARALLELOGRAM_LEFT, + }, + { + viewBox: [200, 200], + path: 'M 0 0 L 150 0 L 200 200 L 50 200 L 0 0 Z', + pathFormula: ShapePathFormulasKeys.PARALLELOGRAM_RIGHT, + }, + { + viewBox: [200, 200], + path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z', + pathFormula: ShapePathFormulasKeys.TRAPEZOID, + }, + { + viewBox: [200, 200], + path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z' + }, + { + viewBox: [200, 200], + path: 'M 100 0 L 0 50 L 0 200 L 200 200 L 200 50 L 100 0 Z', + pathFormula: ShapePathFormulasKeys.BULLET, + }, + { + viewBox: [200, 200], + path: 'M 200 100 L 150 0 L 0 0 L 50 100 L 0 200 L 150 200 L 200 100 Z', + pathFormula: ShapePathFormulasKeys.INDICATOR, + }, { viewBox: [200, 200], path: 'M 0 200 A 50 100 0 1 1 200 200 L 0 200 Z', @@ -148,30 +326,6 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 0 0 L 200 0 Q 200 200 0 200 L 0 0 Z' }, - { - viewBox: [200, 200], - path: 'M 100 0 L 0 200 L 200 200 L 100 0 Z' - }, - { - viewBox: [200, 200], - path: 'M 0 0 L 0 200 L 200 200 Z' - }, - { - viewBox: [200, 200], - path: 'M 50 0 L 200 0 L 150 200 L 0 200 L 50 0 Z' - }, - { - viewBox: [200, 200], - path: 'M 0 0 L 150 0 L 200 200 L 50 200 L 0 0 Z' - }, - { - viewBox: [200, 200], - path: 'M 50 0 L 150 0 L 200 200 L 0 200 L 50 0 Z' - }, - { - viewBox: [200, 200], - path: 'M 100 0 L 0 100 L 100 200 L 200 100 L 100 0 Z' - }, { viewBox: [200, 200], path: 'M 100 0 L 0 90 L 50 200 L 150 200 L 200 90 L 100 0 Z' @@ -188,10 +342,6 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 75 0 L 125 0 L 175 25 L 200 75 L 200 125 L 175 175 L 125 200 L 75 200 L 25 175 L 0 125 L 0 75 L 25 25 L 75 0 Z' }, - { - viewBox: [200, 200], - path: 'M 100 0 L 0 50 L 0 200 L 200 200 L 200 50 L 100 0 Z' - }, { viewBox: [200, 200], path: 'M 150 0 A 50 100 0 1 1 150 200 L 0 200 L 0 0 L 150 0 Z' @@ -204,10 +354,6 @@ export const SHAPE_LIST: ShapeListItem[] = [ viewBox: [200, 200], path: 'M 150 0 A 50 100 0 1 1 150 200 L 0 200 A 50 100 0 0 0 0 0 L 150 0 Z' }, - { - viewBox: [200, 200], - path: 'M 200 100 L 150 0 L 0 0 L 50 100 L 0 200 L 150 200 L 200 100 Z' - }, { viewBox: [200, 200], path: 'M 200 0 L 200 200 L 0 200 L 0 100 L 200 0 Z' diff --git a/src/hooks/useCreateElement.ts b/src/hooks/useCreateElement.ts index 40e51267..d05a30c6 100644 --- a/src/hooks/useCreateElement.ts +++ b/src/hooks/useCreateElement.ts @@ -218,7 +218,13 @@ export default () => { if (data.pathFormula) { newElement.pathFormula = data.pathFormula newElement.viewBox = [width, height] - newElement.path = SHAPE_PATH_FORMULAS[data.pathFormula](width, height) + + const pathFormula = SHAPE_PATH_FORMULAS[data.pathFormula] + if ('editable' in pathFormula) { + newElement.path = pathFormula.formula(width, height, pathFormula.defaultValue) + newElement.keypoint = pathFormula.defaultValue + } + else newElement.path = pathFormula.formula(width, height) } createElement(newElement) } diff --git a/src/types/slides.ts b/src/types/slides.ts index 7ba20ef4..a53277c9 100644 --- a/src/types/slides.ts +++ b/src/types/slides.ts @@ -13,6 +13,12 @@ export const enum ShapePathFormulasKeys { L = 'L', RING_RECT = 'ringRect', PLUS = 'plus', + TRIANGLE = 'triangle', + PARALLELOGRAM_LEFT = 'parallelogramLeft', + PARALLELOGRAM_RIGHT = 'parallelogramRight', + TRAPEZOID = 'trapezoid', + BULLET = 'bullet', + INDICATOR = 'indicator', } export const enum ElementTypes { @@ -304,6 +310,8 @@ export interface ShapeText { * pathFormula?: 形状路径计算公式 * 一般情况下,形状的大小变化时仅由宽高基于 viewBox 的缩放比例来调整形状,而 viewBox 本身和 path 不会变化, * 但也有一些形状希望能更精确的控制一些关键点的位置,此时就需要提供路径计算公式,通过在缩放时更新 viewBox 并重新计算 path 来重新绘制形状 + * + * keypoint?: 关键点位置百分比 */ export interface PPTShapeElement extends PPTBaseElement { type: 'shape' @@ -320,6 +328,7 @@ export interface PPTShapeElement extends PPTBaseElement { special?: boolean text?: ShapeText pathFormula?: ShapePathFormulasKeys + keypoint?: number } diff --git a/src/views/Editor/Canvas/Operate/ShapeElementOperate.vue b/src/views/Editor/Canvas/Operate/ShapeElementOperate.vue index 58787564..106e84a0 100644 --- a/src/views/Editor/Canvas/Operate/ShapeElementOperate.vue +++ b/src/views/Editor/Canvas/Operate/ShapeElementOperate.vue @@ -22,6 +22,12 @@ :style="{ left: scaleWidth / 2 + 'px' }" @mousedown.stop="rotateElement(elementInfo)" /> +
@@ -38,6 +44,7 @@ import { storeToRefs } from 'pinia' import { useMainStore } from '@/store' import { PPTShapeElement } from '@/types/slides' import { OperateResizeHandlers } from '@/types/edit' +import { SHAPE_PATH_FORMULAS } from '@/configs/shapes' import useCommonOperate from '../hooks/useCommonOperate' import RotateHandler from './RotateHandler.vue' @@ -61,6 +68,10 @@ const props = defineProps({ type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void>, required: true, }, + moveShapeKeypoint: { + type: Function as PropType<(e: MouseEvent, element: PPTShapeElement) => void>, + required: true, + }, }) const { canvasScale } = storeToRefs(useMainStore()) @@ -68,4 +79,33 @@ const { canvasScale } = storeToRefs(useMainStore()) const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value) const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value) const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight) - \ No newline at end of file + +const keypointStyle = computed(() => { + if (!props.elementInfo.pathFormula || !props.elementInfo.keypoint) return {} + + const pathFormula = SHAPE_PATH_FORMULAS[props.elementInfo.pathFormula] + if ('editable' in pathFormula) { + const keypointPos = pathFormula.getBaseSize(props.elementInfo.width, props.elementInfo.height) * props.elementInfo.keypoint + if (pathFormula.relative === 'left') return { left: keypointPos * canvasScale.value + 'px' } + if (pathFormula.relative === 'right') return { left: (props.elementInfo.width - keypointPos) * canvasScale.value + 'px' } + if (pathFormula.relative === 'center') return { left: (props.elementInfo.width - keypointPos) / 2 * canvasScale.value + 'px' } + if (pathFormula.relative === 'top') return { top: keypointPos * canvasScale.value + 'px' } + if (pathFormula.relative === 'bottom') return { top: (props.elementInfo.height - keypointPos) * canvasScale.value + 'px' } + } + return {} +}) + + + \ No newline at end of file diff --git a/src/views/Editor/Canvas/Operate/index.vue b/src/views/Editor/Canvas/Operate/index.vue index 4efc9cea..4398d7db 100644 --- a/src/views/Editor/Canvas/Operate/index.vue +++ b/src/views/Editor/Canvas/Operate/index.vue @@ -17,6 +17,7 @@ :rotateElement="rotateElement" :scaleElement="scaleElement" :dragLineElement="dragLineElement" + :moveShapeKeypoint="moveShapeKeypoint" >
void>, required: true, }, + moveShapeKeypoint: { + type: Function as PropType<(e: MouseEvent, element: PPTShapeElement) => void>, + required: true, + }, openLinkDialog: { type: Function as PropType<() => void>, required: true, diff --git a/src/views/Editor/Canvas/hooks/useMoveShapeKeypoint.ts b/src/views/Editor/Canvas/hooks/useMoveShapeKeypoint.ts new file mode 100644 index 00000000..b6251d7f --- /dev/null +++ b/src/views/Editor/Canvas/hooks/useMoveShapeKeypoint.ts @@ -0,0 +1,107 @@ +import { Ref } from 'vue' +import { useSlidesStore } from '@/store' +import { PPTElement, PPTShapeElement } from '@/types/slides' +import useHistorySnapshot from '@/hooks/useHistorySnapshot' +import { SHAPE_PATH_FORMULAS } from '@/configs/shapes' + +interface ShapePathData { + baseSize: number, + originPos: number, + min: number, + max: number, + relative: string, +} + +export default ( + elementList: Ref, + canvasScale: Ref, +) => { + const slidesStore = useSlidesStore() + + const { addHistorySnapshot } = useHistorySnapshot() + + const moveShapeKeypoint = (e: MouseEvent | TouchEvent, element: PPTShapeElement) => { + const isTouchEvent = !(e instanceof MouseEvent) + if (isTouchEvent && (!e.changedTouches || !e.changedTouches[0])) return + + let isMouseDown = true + + const startPageX = isTouchEvent ? e.changedTouches[0].pageX : e.pageX + const startPageY = isTouchEvent ? e.changedTouches[0].pageY : e.pageY + + const pathFormula = SHAPE_PATH_FORMULAS[element.pathFormula!] + let shapePathData: ShapePathData | null = null + if ('editable' in pathFormula) { + const baseSize = pathFormula.getBaseSize(element.width, element.height) + const originPos = baseSize * element.keypoint! + const [min, max] = pathFormula.range + const relative = pathFormula.relative + + shapePathData = { baseSize, originPos, min, max, relative } + } + + const handleMousemove = (e: MouseEvent | TouchEvent) => { + if (!isMouseDown) return + + const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX + const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY + const moveX = (currentPageX - startPageX) / canvasScale.value + const moveY = (currentPageY - startPageY) / canvasScale.value + + elementList.value = elementList.value.map(el => { + if (el.id === element.id && shapePathData) { + const { baseSize, originPos, min, max, relative } = shapePathData + const shapeElement = el as PPTShapeElement + + let keypoint = 0 + + if (relative === 'left') keypoint = (originPos + moveX) / baseSize + if (relative === 'right') keypoint = (originPos - moveX) / baseSize + if (relative === 'center') keypoint = (originPos - moveX * 2) / baseSize + if (relative === 'top') keypoint = (originPos + moveY) / baseSize + if (relative === 'bottom') keypoint = (originPos - moveY) / baseSize + + if (keypoint < min) keypoint = min + if (keypoint > max) keypoint = max + + return { + ...el, + keypoint, + path: pathFormula.formula(shapeElement.width, shapeElement.height, keypoint), + } + } + return el + }) + } + + const handleMouseup = (e: MouseEvent | TouchEvent) => { + isMouseDown = false + + document.ontouchmove = null + document.ontouchend = null + document.onmousemove = null + document.onmouseup = null + + const currentPageX = e instanceof MouseEvent ? e.pageX : e.changedTouches[0].pageX + const currentPageY = e instanceof MouseEvent ? e.pageY : e.changedTouches[0].pageY + + if (startPageX === currentPageX && startPageY === currentPageY) return + + slidesStore.updateSlide({ elements: elementList.value }) + addHistorySnapshot() + } + + if (isTouchEvent) { + document.ontouchmove = handleMousemove + document.ontouchend = handleMouseup + } + else { + document.onmousemove = handleMousemove + document.onmouseup = handleMouseup + } + } + + return { + moveShapeKeypoint, + } +} \ No newline at end of file diff --git a/src/views/Editor/Canvas/hooks/useScaleElement.ts b/src/views/Editor/Canvas/hooks/useScaleElement.ts index 0ca38f24..2fb9daaa 100644 --- a/src/views/Editor/Canvas/hooks/useScaleElement.ts +++ b/src/views/Editor/Canvas/hooks/useScaleElement.ts @@ -400,10 +400,16 @@ export default ( elementList.value = elementList.value.map(el => { if (element.id !== el.id) return el if (el.type === 'shape' && 'pathFormula' in el && el.pathFormula) { + const pathFormula = SHAPE_PATH_FORMULAS[el.pathFormula] + + let path = '' + if ('editable' in pathFormula) path = pathFormula.formula(width, height, el.keypoint!) + else path = pathFormula.formula(width, height) + return { ...el, left, top, width, height, viewBox: [width, height], - path: SHAPE_PATH_FORMULAS[el.pathFormula](width, height), + path, } } return { ...el, left, top, width, height } diff --git a/src/views/Editor/Canvas/index.vue b/src/views/Editor/Canvas/index.vue index a3c59cfa..6d6e1b57 100644 --- a/src/views/Editor/Canvas/index.vue +++ b/src/views/Editor/Canvas/index.vue @@ -46,6 +46,7 @@ :scaleElement="scaleElement" :openLinkDialog="openLinkDialog" :dragLineElement="dragLineElement" + :moveShapeKeypoint="moveShapeKeypoint" />
@@ -111,6 +112,7 @@ import useScaleElement from './hooks/useScaleElement' import useSelectElement from './hooks/useSelectElement' import useDragElement from './hooks/useDragElement' import useDragLineElement from './hooks/useDragLineElement' +import useMoveShapeKeypoint from './hooks/useMoveShapeKeypoint' import useInsertFromCreateSelection from './hooks/useInsertFromCreateSelection' import useDeleteElement from '@/hooks/useDeleteElement' @@ -172,6 +174,7 @@ const { dragLineElement } = useDragLineElement(elementList) const { selectElement } = useSelectElement(elementList, dragElement) const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale) const { rotateElement } = useRotateElement(elementList, viewportRef) +const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale) const { selectAllElement } = useSelectAllElement() const { deleteAllElements } = useDeleteElement() diff --git a/src/views/Editor/Toolbar/ElementPositionPanel.vue b/src/views/Editor/Toolbar/ElementPositionPanel.vue index 31b2c753..b8ce13d3 100644 --- a/src/views/Editor/Toolbar/ElementPositionPanel.vue +++ b/src/views/Editor/Toolbar/ElementPositionPanel.vue @@ -138,6 +138,7 @@ import { storeToRefs } from 'pinia' import { useMainStore, useSlidesStore } from '@/store' import { ElementAlignCommands, ElementOrderCommands } from '@/types/edit' import { MIN_SIZE } from '@/configs/element' +import { SHAPE_PATH_FORMULAS } from '@/configs/shapes' import useOrderElement from '@/hooks/useOrderElement' import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas' import useHistorySnapshot from '@/hooks/useHistorySnapshot' @@ -190,13 +191,35 @@ const updateTop = (value: number) => { } // 设置元素宽度、高度、旋转角度 +// 对形状设置宽高时,需要检查是否需要更新形状路径 +const updateShapePathData = (width: number, height: number) => { + if (handleElement.value && handleElement.value.type === 'shape' && 'pathFormula' in handleElement.value && handleElement.value.pathFormula) { + const pathFormula = SHAPE_PATH_FORMULAS[handleElement.value.pathFormula] + + let path = '' + if ('editable' in pathFormula) path = pathFormula.formula(width, height, handleElement.value.keypoint!) + else path = pathFormula.formula(width, height) + + return { + viewBox: [width, height], + path, + } + } + return null +} const updateWidth = (value: number) => { - const props = { width: value } + let props = { width: value } + const shapePathData = updateShapePathData(value, height.value) + if (shapePathData) props = { ...props, ...shapePathData } + slidesStore.updateElement({ id: handleElementId.value, props }) addHistorySnapshot() } const updateHeight = (value: number) => { - const props = { height: value } + let props = { height: value } + const shapePathData = updateShapePathData(width.value, value) + if (shapePathData) props = { ...props, ...shapePathData } + slidesStore.updateElement({ id: handleElementId.value, props }) addHistorySnapshot() }