From f1940bbbe21a971592664a6f30f75b3dc44c25f4 Mon Sep 17 00:00:00 2001 From: zhouxinyu Date: Wed, 21 Aug 2024 17:02:48 +0800 Subject: [PATCH] feat: support image shape edit controller --- packages/vstory/demo/src/demos/StoryEdit.tsx | 94 +++++++++++++------ packages/vstory/src/edit/edit-action.ts | 3 + .../edit/edit-component/image-selection.ts | 11 ++- .../vstory/src/edit/edit-component/index.ts | 10 +- .../src/edit/edit-component/rect-selection.ts | 8 +- .../richtext-selection-common.ts | 4 +- .../edit/edit-component/shape-selection.ts | 50 ++++++++++ .../component-group-graphic.ts | 2 +- .../component/characters/character-image.ts | 10 ++ .../component/characters/character-shape.ts | 26 +++++ .../character/component/graphic/symbol.ts | 31 ++++-- 11 files changed, 200 insertions(+), 49 deletions(-) create mode 100644 packages/vstory/src/edit/edit-component/shape-selection.ts diff --git a/packages/vstory/demo/src/demos/StoryEdit.tsx b/packages/vstory/demo/src/demos/StoryEdit.tsx index a5cd2d40..d20aba5e 100644 --- a/packages/vstory/demo/src/demos/StoryEdit.tsx +++ b/packages/vstory/demo/src/demos/StoryEdit.tsx @@ -4,17 +4,11 @@ import { Story } from '../../../src/story/story'; import { Edit } from '../../../src/edit/edit'; import '../../../src/story/index'; import { cloneDeep } from '@visactor/vutils'; -import { CommonEditComponent } from '../../../src/edit/edit-component/common'; -import { ChartSelection } from '../../../src/edit/edit-component/chart-selection'; -import { BoxSelection } from '../../../src/edit/edit-component/box-selection'; -import { TextSelection } from '../../../src/edit/edit-component/text-selection'; -import { RichTextSelection } from '../../../src/edit/edit-component/richtext-selection'; +import Scene3ChartImage2 from '../assets/scene3/chart-2.png'; +import { loadAllSelection } from '../../../src/edit/edit-component'; // Edit.registerEditComponent('common', CommonEditComponent); -Edit.registerEditComponent('chart', ChartSelection); -Edit.registerEditComponent('text', TextSelection); -Edit.registerEditComponent('richtext', RichTextSelection); -Edit.registerEditComponent('box-selection', BoxSelection); +loadAllSelection(); const chartSpec = { type: 'bar', @@ -43,7 +37,7 @@ export const StoryEdit = () => { characters: [ { type: 'Rect', - id: 'test-graphics-0', + id: 'rect0', zIndex: 10, position: { top: 40, @@ -65,7 +59,7 @@ export const StoryEdit = () => { }, { type: 'Rect', - id: 'test-graphics-1', + id: 'rect1', zIndex: 0, position: { top: 40, @@ -86,6 +80,66 @@ export const StoryEdit = () => { shapePoints: [] } }, + { + type: 'Image', + id: 'image0', + zIndex: 0, + position: { + top: 140, + left: 250, + width: 200, + height: 100 + }, + options: { + graphic: { + image: Scene3ChartImage2 + }, + text: { + text: 'Image', + fill: 'black' + }, + angle: 0, + shapePoints: [] + } + }, + // { + // type: 'Text', + // id: 'text0', + // zIndex: 0, + // position: { + // top: 140, + // left: 150, + // width: 200, + // height: 100 + // }, + // options: { + // graphic: { + // fill: 'pink', + // text: 'hahaha' + // }, + // angle: 0, + // shapePoints: [] + // } + // }, + { + type: 'Shape', + id: 'shape0', + zIndex: 0, + position: { + top: 240, + left: 250, + width: 200, + height: 100 + }, + options: { + graphic: { + fill: 'green', + symbolType: 'star' + }, + angle: 0, + shapePoints: [] + } + }, { type: 'VChart', id: 'test-chart-0', @@ -109,23 +163,7 @@ export const StoryEdit = () => { id: 'scene0', actions: [ { - characterId: 'test-graphics-0', - characterActions: [ - { - startTime: 0, - action: 'appear', - payload: { - animation: { - duration: 100, - easing: 'linear', - effect: 'fadeIn' - } as any - } - } - ] - }, - { - characterId: 'test-graphics-1', + characterId: ['rect0', 'rect1', 'image0', 'shape0'], characterActions: [ { startTime: 0, diff --git a/packages/vstory/src/edit/edit-action.ts b/packages/vstory/src/edit/edit-action.ts index 8ffe095b..e0f183cd 100644 --- a/packages/vstory/src/edit/edit-action.ts +++ b/packages/vstory/src/edit/edit-action.ts @@ -86,6 +86,9 @@ export class EditAction { // 取消选中 type = EditActionEnum.unSelection; } + // if (type === EditActionEnum.singleSelection) { + // debugger; + // } this.dispatchAction({ type: type, characterId: character?.id, diff --git a/packages/vstory/src/edit/edit-component/image-selection.ts b/packages/vstory/src/edit/edit-component/image-selection.ts index 492b481b..5e4880f3 100644 --- a/packages/vstory/src/edit/edit-component/image-selection.ts +++ b/packages/vstory/src/edit/edit-component/image-selection.ts @@ -4,13 +4,14 @@ import { type IEditComponent } from '../interface'; import { BaseSelection } from './base-selection'; import type { ITransformControl, TransformAttributes } from './edit-control/transform-control'; import { TransformControl } from './edit-control/transform-control'; +import { RectSelection } from './rect-selection'; -export class ImageSelection extends BaseSelection implements IEditComponent { +export class ImageSelection extends RectSelection implements IEditComponent { readonly level = 3; readonly type: string = 'image'; - editCharacterType = StoryComponentType.IMAGE; + readonly editCharacterType: string = StoryComponentType.IMAGE; - protected _createLayoutComponent(attributes: Partial): ITransformControl { - return new TransformControl(this, attributes); - } + // protected _createLayoutComponent(attributes: Partial): ITransformControl { + // return new TransformControl(this, attributes); + // } } diff --git a/packages/vstory/src/edit/edit-component/index.ts b/packages/vstory/src/edit/edit-component/index.ts index 72c180eb..76e015d9 100644 --- a/packages/vstory/src/edit/edit-component/index.ts +++ b/packages/vstory/src/edit/edit-component/index.ts @@ -5,12 +5,16 @@ import { TextSelection } from './text-selection'; import { RichTextSelection } from './richtext-selection'; import { RectSelection } from './rect-selection'; import { Edit } from '../edit'; +import { ChartSelection } from './chart-selection'; +import { ShapeSelection } from './shape-selection'; export function loadAllSelection() { // Edit.registerEditComponent('common', CommonEditComponent); - Edit.registerEditComponent('text', TextSelection); - Edit.registerEditComponent('richtext', RichTextSelection); + // Edit.registerEditComponent('text', TextSelection); + // Edit.registerEditComponent('richtext', RichTextSelection); Edit.registerEditComponent('rect', RectSelection); + Edit.registerEditComponent('image', ImageSelection); + Edit.registerEditComponent('shape', ShapeSelection); + Edit.registerEditComponent('chart', ChartSelection); Edit.registerEditComponent('box-selection', BoxSelection); - Edit.registerEditComponent('image-selection', ImageSelection); } diff --git a/packages/vstory/src/edit/edit-component/rect-selection.ts b/packages/vstory/src/edit/edit-component/rect-selection.ts index 3d7beb8d..2a617c34 100644 --- a/packages/vstory/src/edit/edit-component/rect-selection.ts +++ b/packages/vstory/src/edit/edit-component/rect-selection.ts @@ -8,22 +8,22 @@ import { RichTextSelectionCommon } from './richtext-selection-common'; export class RectSelection extends RichTextSelectionCommon implements IEditComponent { readonly level = 3; readonly type: string = 'rect'; - readonly editCharacterType = StoryComponentType.RECT; + readonly editCharacterType: string = StoryComponentType.RECT; startEdit(actionInfo: IEditActionInfo) { super.startEdit(actionInfo); // @ts-ignore; const character = this._actionInfo.character; - character.graphic.graphic.addEventListener('pointerdown', this.handlerRectClick); + character.graphic.graphic.addEventListener('pointerdown', this.handlerContentClick); } editEnd() { // @ts-ignore; const character = this._actionInfo.character; - character.graphic.graphic.removeEventListener('pointerdown', this.handlerRectClick); + character.graphic.graphic.removeEventListener('pointerdown', this.handlerContentClick); super.editEnd(); } - handlerRectClick = (e: any) => { + handlerContentClick = (e: any) => { this._layoutComponent.handleDragMouseDown(e); this.endRichTextEdit(); }; diff --git a/packages/vstory/src/edit/edit-component/richtext-selection-common.ts b/packages/vstory/src/edit/edit-component/richtext-selection-common.ts index 6173c580..9146570d 100644 --- a/packages/vstory/src/edit/edit-component/richtext-selection-common.ts +++ b/packages/vstory/src/edit/edit-component/richtext-selection-common.ts @@ -11,8 +11,8 @@ export interface RichTextSelectionCommon export class RichTextSelectionCommon extends BaseSelection implements IEditComponent { readonly level = 3; - readonly type: string = 'rect'; - readonly editCharacterType = 'Rect'; + readonly type: string; + readonly editCharacterType: string; protected _getRichText() { // @ts-ignore diff --git a/packages/vstory/src/edit/edit-component/shape-selection.ts b/packages/vstory/src/edit/edit-component/shape-selection.ts new file mode 100644 index 00000000..c6821c2c --- /dev/null +++ b/packages/vstory/src/edit/edit-component/shape-selection.ts @@ -0,0 +1,50 @@ +// import { StoryGraphicType } from '../../dsl/constant'; +import { StoryComponentType } from '../../constants/character'; +import type { VRenderPointerEvent } from '../../interface/type'; +import type { IEditSelectionInfo } from '../interface'; +import { type IEditComponent } from '../interface'; +import { BaseSelection } from './base-selection'; +import type { ITransformControl, IUpdateParams, TransformAttributes } from './edit-control/transform-control'; +import { TransformControl } from './edit-control/transform-control'; +import { RectSelection } from './rect-selection'; + +export class ShapeSelection extends RectSelection implements IEditComponent { + readonly level = 3; + readonly type: string = 'shape'; + readonly editCharacterType: string = StoryComponentType.SHAPE; + + // protected _createLayoutComponent(attributes: Partial): ITransformControl { + // return new TransformControl(this, attributes); + // } + + updateComponent() { + const actionInfo = this._actionInfo as IEditSelectionInfo; + if (!(actionInfo && actionInfo.character)) { + return; + } + const symbol = actionInfo.character.graphic.graphic; + const { width, height } = symbol.attribute; + const group = actionInfo.character.getGraphicParent(); + const { angle, x, y } = group.attribute; + this._layoutComponent.updateBoundsAndAngle( + { + x1: x, + y1: y, + x2: x + width, + y2: y + height + }, + angle + ); + } + + protected handlerTransformChange(data: IUpdateParams, event?: VRenderPointerEvent): void { + if (this._activeCharacter) { + const { x, y, width, height } = data; + this._activeCharacter.setAttributes({ + ...data, + x: x + width / 2, + y: y + height / 2 + }); + } + } +} diff --git a/packages/vstory/src/story/character/component/character-group/component-group-graphic.ts b/packages/vstory/src/story/character/component/character-group/component-group-graphic.ts index 7cea8d6f..863cf38e 100644 --- a/packages/vstory/src/story/character/component/character-group/component-group-graphic.ts +++ b/packages/vstory/src/story/character/component/character-group/component-group-graphic.ts @@ -31,6 +31,6 @@ export class ComponentGroup extends Group implements IVisactorGraphic { constructor(attrs: IGroupGraphicAttribute) { // vstory-component-group没有主题,必须都初始化,否则动画会找不到属性 - super({ scaleX: 1, scaleY: 1, x: 0, y: 0, angle: 0, ...attrs }); + super({ scaleX: 1, scaleY: 1, x: 0, y: 0, angle: 0, ...attrs, pickable: false }); } } diff --git a/packages/vstory/src/story/character/component/characters/character-image.ts b/packages/vstory/src/story/character/component/characters/character-image.ts index d7b89ff3..fe49a623 100644 --- a/packages/vstory/src/story/character/component/characters/character-image.ts +++ b/packages/vstory/src/story/character/component/characters/character-image.ts @@ -2,10 +2,20 @@ import type { Graphic } from '../graphic/graphic'; import { CharacterComponent } from '../character'; import { StoryComponentType } from '../../../../constants/character'; import { GraphicImage } from '../graphic/image'; +import type { StoryEvent } from '../../../interface'; +import type { ICharacterPickInfo } from '../../runtime-interface'; export class CharacterComponentImage extends CharacterComponent { readonly graphicType: string = 'image'; protected _createGraphic(): Graphic { return new GraphicImage(StoryComponentType.IMAGE, this as any); } + + checkEvent(event: StoryEvent): false | ICharacterPickInfo { + const info = super.checkEvent(event); + if (info && event.path[event.path.length - 1] === this._group) { + return false; + } + return info; + } } diff --git a/packages/vstory/src/story/character/component/characters/character-shape.ts b/packages/vstory/src/story/character/component/characters/character-shape.ts index 386a5373..870c6247 100644 --- a/packages/vstory/src/story/character/component/characters/character-shape.ts +++ b/packages/vstory/src/story/character/component/characters/character-shape.ts @@ -2,10 +2,36 @@ import type { Graphic } from '../graphic/graphic'; import { CharacterComponent } from '../character'; import { StoryComponentType } from '../../../../constants/character'; import { GraphicSymbol } from '../graphic/symbol'; +import type { StoryEvent } from '../../../interface'; +import type { ICharacterPickInfo } from '../../runtime-interface'; export class CharacterComponentShape extends CharacterComponent { readonly graphicType: string = 'shape'; protected _createGraphic(): Graphic { return new GraphicSymbol(StoryComponentType.SHAPE, this); } + + setAttributes(attr: Record): void { + this.group.setAttributes({ + ...attr, + x: attr.x - attr.width / 2, + y: attr.y - attr.height / 2 + }); + this._graphic.setAttributes({ + ...attr, + x: attr.width / 2, + y: attr.height / 2, + angle: 0, + size: Math.min(attr.width, attr.height) + }); + this._text.updateAttribute({}); + } + + checkEvent(event: StoryEvent): false | ICharacterPickInfo { + const info = super.checkEvent(event); + if (info && event.path[event.path.length - 1] === this._group) { + return false; + } + return info; + } } diff --git a/packages/vstory/src/story/character/component/graphic/symbol.ts b/packages/vstory/src/story/character/component/graphic/symbol.ts index b630282a..93c3c932 100644 --- a/packages/vstory/src/story/character/component/graphic/symbol.ts +++ b/packages/vstory/src/story/character/component/graphic/symbol.ts @@ -2,6 +2,8 @@ import type { ISymbol } from '@visactor/vrender'; import { createSymbol } from '@visactor/vrender'; import type { IPointLike } from '@visactor/vutils'; import { Graphic } from './graphic'; +import type { IWidgetData } from '../../dsl-interface'; +import { getLayoutFromWidget } from '../../../utils/layout'; export class GraphicSymbol extends Graphic { protected _graphic: ISymbol; @@ -23,14 +25,31 @@ export class GraphicSymbol extends Graphic { init() { if (!this._graphic) { - this._graphic = createSymbol( - this._transformAttributes({ - ...this.getInitialAttributes(), - ...(this._character.spec.options?.graphic ?? {}) - }) - ); + const attributes = this._transformAttributes({ + ...this.getInitialAttributes(), + ...(this._character.spec.options?.graphic ?? {}) + }); + this._graphic = createSymbol(attributes); this._graphic.name = `graphic-symbol-${this._character.id}`; this._character.getGraphicParent().add(this._graphic); } } + + applyLayoutData(layoutData: Partial): void { + const attributes = this._transformAttributes({ + ...getLayoutFromWidget(layoutData), + shapePoints: this._character.spec.options.shapePoints + }); + attributes.size = Math.min(attributes.width, attributes.height); + + this._graphic.setAttributes(attributes); + } + + protected _transformAttributes(attributes: any): any { + const data = super._transformAttributes(attributes); + const { width, height } = attributes; + data.x = width / 2; + data.y = height / 2; + return data; + } }