Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/hover control #219

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading