diff --git a/packages/vstory-editor/src/selection/base-selection.ts b/packages/vstory-editor/src/selection/base-selection.ts index 9eaf912b..5aa5c0bc 100644 --- a/packages/vstory-editor/src/selection/base-selection.ts +++ b/packages/vstory-editor/src/selection/base-selection.ts @@ -6,6 +6,8 @@ import { EditActionEnum, EditEditingState } from '../const'; import type { Edit } from '../edit'; import type { ITransformController, ControllerAttributes, IUpdateParams } from './edit-control/transform-control'; import { TransformController } from './edit-control/transform-control'; +import type { IHoverController } from './edit-control/hover-control'; +import { HoverController } from './edit-control/hover-control'; export abstract class BaseSelection implements IEditSelection { declare readonly level: number; @@ -16,6 +18,7 @@ export abstract class BaseSelection implements IEditSelection { protected _actionInfo: IEditActionInfo | null; protected _activeCharacter: ICharacter | null; protected _layoutController: ITransformController | null; + protected _hoverController: IHoverController | null; isEditing: boolean = false; declare clickCount: number; @@ -29,6 +32,7 @@ export abstract class BaseSelection implements IEditSelection { this._activeCharacter = null; this._actionInfo = null; this._layoutController = null; + this._hoverController = null; this._initOverGraphic(); } @@ -38,6 +42,16 @@ export abstract class BaseSelection implements IEditSelection { } return this.checkActionWhileNoEditing(actionInfo); } + checkOver(actionInfo: IEditActionInfo | IEditSelectionInfo) { + if (!this.isActionInfoSupported(actionInfo)) { + return; + } + if (actionInfo.type === EditActionEnum.pointerOverCharacter) { + this.activeHoverController(actionInfo.character); + } else if (actionInfo.type === EditActionEnum.pointerOutCharacter) { + this.inActiveHoverController(); + } + } startEdit(actionInfo: IEditActionInfo, emitEvent: boolean = false) { if (this.isEditing) { return; @@ -93,6 +107,9 @@ export abstract class BaseSelection implements IEditSelection { }; protected activeLayoutController() { + // 关闭hover的控件 + this.inActiveHoverController(); + if (!this._layoutController) { this._layoutController = this.createLayoutController(); } @@ -117,7 +134,25 @@ export abstract class BaseSelection implements IEditSelection { vglobal.removeEventListener('keyup', this.keyUp); } - protected attachController(layoutController: ITransformController) { + protected activeHoverController(character: ICharacter) { + if (this.isEditing) { + return; + } + if (!this._hoverController) { + this._hoverController = this.createHoverController(character); + } + this.attachController(this._hoverController); + } + + protected inActiveHoverController() { + if (!this._hoverController) { + return; + } + + this.detachController(); + } + + protected attachController(layoutController: IGroup) { const g = this.edit.getEditGroup(); if (layoutController.parent === g) { throw new Error('【attachController】未知错误,不应该走到这里'); @@ -125,8 +160,14 @@ export abstract class BaseSelection implements IEditSelection { g.appendChild(layoutController); } protected detachController() { - this._layoutController.release(); - this._layoutController = null; + if (this._layoutController) { + this._layoutController.release(); + this._layoutController = null; + } + if (this._hoverController) { + this._hoverController.release(); + this._hoverController = null; + } } protected updateController(): void { const actionInfo = this._actionInfo as IEditSelectionInfo; @@ -172,6 +213,17 @@ export abstract class BaseSelection implements IEditSelection { return controller; } + protected createHoverController(character: ICharacter): IHoverController | undefined { + const bounds = character.graphic.AABBBounds; + const controller = new HoverController(this, { + x: bounds.x1, + y: bounds.y1, + width: bounds.width(), + height: bounds.height() + }) as any; + return controller; + } + protected _createLayoutController(attributes: Partial): ITransformController { return new TransformController(this, attributes) as any; } @@ -259,7 +311,6 @@ export abstract class BaseSelection implements IEditSelection { this.startEdit(actionInfo); return true; } - return false; } } diff --git a/packages/vstory-editor/src/selection/edit-control/hover-control.ts b/packages/vstory-editor/src/selection/edit-control/hover-control.ts new file mode 100644 index 00000000..ee9f43fb --- /dev/null +++ b/packages/vstory-editor/src/selection/edit-control/hover-control.ts @@ -0,0 +1,57 @@ +import { AbstractComponent } from '@visactor/vrender-components'; +import type { IGroup, IGroupGraphicAttribute, IRect, IRectGraphicAttribute } from '@visactor/vrender-core'; +import { createRect, IGraphic } from '@visactor/vrender-core'; +import { IAABBBoundsLike, merge } from '@visactor/vutils'; +import { SHAPE_HOVER_COLOR } from './constants'; +import type { IEditSelection } from '../../interface'; +import { VRenderPointerEvent } from '../../interface'; + +interface HoverControllerAttribute extends IGroupGraphicAttribute { + hoverBorder: IRectGraphicAttribute; +} + +export type IHoverController = IGroup; + +// @ts-ignore +export class HoverController extends AbstractComponent> implements IGroup { + hoverBorder: IRect; + editSelection: IEditSelection; + + static defaultAttributes: Partial = { + hoverBorder: { + stroke: SHAPE_HOVER_COLOR, + strokeOpacity: 0.7, + lineWidth: 2, + lineDash: [8, 8] + } + }; + + constructor(editSelection: IEditSelection, attributes: Partial) { + super(merge(HoverController.defaultAttributes, attributes)); + this.editSelection = editSelection; + this.hoverBorder = createRect({ + visible: false + }); + this.add(this.hoverBorder); + } + + protected render(): void { + const { width, height, hoverBorder } = this.attribute; + this.hoverBorder.setAttributes({ + visible: true, + x: 0, + y: 0, + width, + height, + ...hoverBorder + }); + } + + release(): void { + this.parent.removeChild(this); + this.removeAllChild(); + this.hoverBorder.release(); + this.hoverBorder = null; + super.release(); + } +} diff --git a/packages/vstory-editor/src/selection/edit-control/transform-control.ts b/packages/vstory-editor/src/selection/edit-control/transform-control.ts index 5205e06e..0e84b9e7 100644 --- a/packages/vstory-editor/src/selection/edit-control/transform-control.ts +++ b/packages/vstory-editor/src/selection/edit-control/transform-control.ts @@ -14,7 +14,7 @@ import type { } from '@visactor/vrender'; import { createLine, createRect } from '@visactor/vrender'; import type { IAABBBounds, IAABBBoundsLike, IPointLike } from '@visactor/vutils'; -import { AABBBounds, abs, merge, normalizeAngle, normalizePadding, pi } from '@visactor/vutils'; +import { AABBBounds, abs, Matrix, merge, normalizeAngle, normalizePadding, pi } from '@visactor/vutils'; import { AbstractComponent } from '@visactor/vrender-components'; import { DRAG_ANCHOR_COLOR, SHAPE_SELECT_COLOR, MinSize } from './constants'; import { DragComponent } from './transform-drag'; @@ -23,6 +23,8 @@ import type { IEditSelection, VRenderPointerEvent } from '../../interface'; import { min } from '@visactor/vchart/esm/util'; import type { ILayoutLine } from '@visactor/vstory-core'; +const i = 0; + const tempRect = createRect({}); type AnchorDirection = 'top' | 'bottom' | 'left-top' | 'left-bottom' | 'right' | 'left' | 'right-top' | 'right-bottom'; @@ -744,7 +746,7 @@ export class TransformController extends AbstractComponent= fixedAngle - maxAngleDifference && angle <= fixedAngle + maxAngleDifference) { - return fixedAngle; + return normalizeAngle(fixedAngle); } } return angle; @@ -912,7 +914,7 @@ export class TransformController extends AbstractComponent item.orient === 'x'); - const lineY = lines.filter(item => item.orient === 'y'); - _snappedX = this._snapLine('x', lineX, this._actualSnapBounds, diff, out, diff.width !== 0); - _snappedY = this._snapLine('y', lineY, this._actualSnapBounds, diff, out, diff.height !== 0); - } + const lineX = lines.filter(item => item.orient === 'x'); + const lineY = lines.filter(item => item.orient === 'y'); + _snappedX = this._snapLine('x', lineX, actualSnapBounds, out, diff.width !== 0); + _snappedY = this._snapLine('y', lineY, actualSnapBounds, out, diff.height !== 0); // 从吸附到未吸附,将实际的bounds重置回去 // TODO x和y都分两边,如果有一边已经吸附,那就不生效 if (!_snappedX) { - out.x = this._actualSnapBounds.x1; - out.width = this._actualSnapBounds.width(); + out.x = actualSnapBounds.x1; + out.width = actualSnapBounds.width(); } // 从吸附到未吸附,将实际的bounds重置回去 if (!_snappedY) { - out.y = this._actualSnapBounds.y1; - out.height = this._actualSnapBounds.height(); + out.y = actualSnapBounds.y1; + out.height = actualSnapBounds.height(); } // 如果没有吸附,就重置回去 if (!(_snappedX || _snappedY)) { @@ -963,14 +1011,15 @@ export class TransformController extends AbstractComponent item.orient === 'x'); + const lineY = lines.filter(item => item.orient === 'y'); + this._snapLine('x', lineX, bounds, out, false); + this._snapLine('y', lineY, bounds, out, false); + return out; + } + + _snapLine(orient: 'x' | 'y', lines: ILayoutLine[], bounds: IAABBBounds, out: IXYWH, resize: boolean): boolean { // 重置snapLine [ `_snapLine${orient.toUpperCase()}1`, @@ -1016,41 +1065,13 @@ export class TransformController extends AbstractComponent { const player = new Player(story); story.init(player); - story.addCharacterWithAppear({ - type: 'Shape', - id: 'star', - zIndex: 10, - position: { - top: 100, - left: 100, - width: 80, - height: 60 - }, - options: { - graphic: { - stroke: false, - fill: 'pink', - symbolType: 'star' - // size: 100 - } - } - }); + // story.addCharacterWithAppear({ + // type: 'Shape', + // id: 'star', + // zIndex: 10, + // position: { + // top: 100, + // left: 100, + // width: 80, + // height: 60 + // }, + // options: { + // graphic: { + // stroke: false, + // fill: 'pink', + // symbolType: 'star' + // // size: 100 + // } + // } + // }); story.addCharacterWithAppear({ type: 'Image', id: 'image', zIndex: 10, position: { - top: 100, - left: 300, + top: 300, + left: 200, width: 80, height: 60 }, @@ -70,8 +70,8 @@ export const ComponentsEdit = () => { position: { top: 100, left: 100, - width: 200, - height: 200 + width: 60, + height: 60 }, options: { graphic: {