Skip to content

Commit

Permalink
Merge pull request #219 from VisActor/feat/hover-control
Browse files Browse the repository at this point in the history
Feat/hover control
  • Loading branch information
neuqzxy authored Jan 17, 2025
2 parents 71c504e + b1cbdd4 commit 9b030ea
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 82 deletions.
59 changes: 55 additions & 4 deletions packages/vstory-editor/src/selection/base-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -29,6 +32,7 @@ export abstract class BaseSelection implements IEditSelection {
this._activeCharacter = null;
this._actionInfo = null;
this._layoutController = null;
this._hoverController = null;
this._initOverGraphic();
}

Expand All @@ -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;
Expand Down Expand Up @@ -93,6 +107,9 @@ export abstract class BaseSelection implements IEditSelection {
};

protected activeLayoutController() {
// 关闭hover的控件
this.inActiveHoverController();

if (!this._layoutController) {
this._layoutController = this.createLayoutController();
}
Expand All @@ -117,16 +134,40 @@ 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】未知错误,不应该走到这里');
}
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;
Expand Down Expand Up @@ -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<ControllerAttributes>): ITransformController {
return new TransformController(this, attributes) as any;
}
Expand Down Expand Up @@ -259,7 +311,6 @@ export abstract class BaseSelection implements IEditSelection {
this.startEdit(actionInfo);
return true;
}

return false;
}
}
57 changes: 57 additions & 0 deletions packages/vstory-editor/src/selection/edit-control/hover-control.ts
Original file line number Diff line number Diff line change
@@ -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<Required<HoverControllerAttribute>> implements IGroup {
hoverBorder: IRect;
editSelection: IEditSelection;

static defaultAttributes: Partial<HoverControllerAttribute> = {
hoverBorder: {
stroke: SHAPE_HOVER_COLOR,
strokeOpacity: 0.7,
lineWidth: 2,
lineDash: [8, 8]
}
};

constructor(editSelection: IEditSelection, attributes: Partial<HoverControllerAttribute>) {
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();
}
}
131 changes: 76 additions & 55 deletions packages/vstory-editor/src/selection/edit-control/transform-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -744,7 +746,7 @@ export class TransformController extends AbstractComponent<Required<ControllerAt
angle = normalizeAngle(angle);
for (const fixedAngle of fixedAngles) {
if (angle >= fixedAngle - maxAngleDifference && angle <= fixedAngle + maxAngleDifference) {
return fixedAngle;
return normalizeAngle(fixedAngle);
}
}
return angle;
Expand Down Expand Up @@ -912,7 +914,7 @@ export class TransformController extends AbstractComponent<Required<ControllerAt

protected _checkSnap(x: number, y: number, width: number, height: number) {
const { x: _x, y: _y, width: _width, height: _height } = this.rect.attribute;
// 如果没变化,就不做处理
// 计算出这次的diff
const diff = {
x: x - _x,
y: y - _y,
Expand All @@ -923,7 +925,17 @@ export class TransformController extends AbstractComponent<Required<ControllerAt
if (diff.x === 0 && diff.y === 0 && diff.width === 0 && diff.height === 0) {
return out;
}
// 计算出一个实际的bounds
const { activeCharacter } = this.editSelection;
if (!activeCharacter) {
return out;
}
// 如果有旋转的情况下resize宽高,那么不做处理直接返回
const { angle } = this.attribute;
if (angle && (diff.width || diff.height)) {
return out;
}
const lines = this.editSelection.edit.getLayoutLineInLayer([activeCharacter.id]);
// 计算出一个实际的bounds,_actualSnapBounds不受吸附影响,永远记录真实拖动的位置
if (!this._actualSnapBounds) {
this._actualSnapBounds = new AABBBounds().setValue(x, y, x + width, y + height);
} else {
Expand All @@ -932,28 +944,64 @@ export class TransformController extends AbstractComponent<Required<ControllerAt
this._actualSnapBounds.x2 += diff.x + diff.width;
this._actualSnapBounds.y2 += diff.y + diff.height;
}
// 否则,检测是否需要吸附
const { activeCharacter } = this.editSelection;

let actualSnapBounds = this._actualSnapBounds;
// 有旋转的话,需要计算出旋转后的bounds
if (angle) {
tempRect.setAttributes({
dx: actualSnapBounds.x1,
dy: actualSnapBounds.y1,
width,
height,
fill: 'transparent',
angle: angle,
anchor: [width / 2, height / 2]
});
actualSnapBounds = tempRect.AABBBounds.clone();
tempRect.setAttributes({
dx: out.x,
dy: out.y,
width: out.width,
height: out.height,
fill: 'transparent',
angle: angle,
anchor: [width / 2, height / 2]
});
out.x = tempRect.AABBBounds.x1;
out.y = tempRect.AABBBounds.y1;
out.width = tempRect.AABBBounds.width();
out.height = tempRect.AABBBounds.height();

const _out_backup = { ...out };
this._snapLineWithAngle(lines, actualSnapBounds, out);

const dx = _out_backup.x - out.x;
const dy = _out_backup.y - out.y;
// console.log(dy);
out.x = x - dx;
out.y = y - dy;
out.width = width;
out.height = height;
return out;
}

let _snappedX = false;
let _snappedY = false;
if (activeCharacter) {
const lines = this.editSelection.edit.getLayoutLineInLayer([activeCharacter.id]);
const lineX = lines.filter(item => 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)) {
Expand All @@ -963,14 +1011,15 @@ export class TransformController extends AbstractComponent<Required<ControllerAt
return out;
}

_snapLine(
orient: 'x' | 'y',
lines: ILayoutLine[],
bounds: IAABBBounds,
diff: IXYWH,
out: IXYWH,
resize: boolean
): boolean {
_snapLineWithAngle(lines: ILayoutLine[], bounds: IAABBBounds, out: IXYWH) {
const lineX = lines.filter(item => 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`,
Expand Down Expand Up @@ -1016,41 +1065,13 @@ export class TransformController extends AbstractComponent<Required<ControllerAt
}
(this as any)[`_snapLine${orient.toUpperCase()}${min.idx}`].setAttributes({
visible: true,
angle: -this.attribute.angle,
anchor: this.attribute.anchor,
points: [
{ [orient]: line.value, [otherOrient]: otherOrientMin },
{ [orient]: line.value, [otherOrient]: otherOrientMax }
] as any
});

// if (abs(d1) < abs(d2)) { // 要改x1/y1
// outBounds[`${orient}1`] = line.value;
// // 如果不是resize,就不改变宽度/高度
// if (!resize) {
// outBounds[`${orient}2`] = outBounds[`${orient}1`] + out[orient === 'x' ? 'width' : 'height'];
// }
// // 添加辅助线
// (this as any)[`_snapLine${orient.toUpperCase()}1`].setAttributes({
// visible: true,
// points: [
// { [orient]: line.value, [otherOrient]: otherOrientMin },
// { [orient]: line.value, [otherOrient]: otherOrientMax }
// ] as any
// });
// } else { // 要改x2/y2
// outBounds[`${orient}2`] = line.value;
// // 如果不是resize,就不改变宽度/高度
// if (!resize) {
// outBounds[`${orient}1`] = outBounds[`${orient}2`] - out[orient === 'x' ? 'width' : 'height'];
// }
// // 添加辅助线
// (this as any)[`_snapLine${orient.toUpperCase()}2`].setAttributes({
// visible: true,
// points: [
// { [orient]: line.value, [otherOrient]: otherOrientMin },
// { [orient]: line.value, [otherOrient]: otherOrientMax }
// ] as any
// });
// }
});
out.x = outBounds.x1;
out.y = outBounds.y1;
Expand Down
Loading

0 comments on commit 9b030ea

Please sign in to comment.