菜单功能完善
直线/曲线编辑右键菜单功能完善 添加_rightclick和_leftclick事件(原始事件无法区别拖拽)
This commit is contained in:
parent
df3522e205
commit
3454739356
@ -14,7 +14,7 @@ viewport 使用的 github 开源的 pixi-viewport[pixi-viewport](https://github.
|
||||
- 绘制增加吸附功能(移动到特定位置附近吸附)(完成)
|
||||
- 图形动画抽象
|
||||
- 添加公用动画逻辑(如按指定路径位移,按角度旋转、按比例缩放、透明度控制等)
|
||||
- 菜单事件及处理
|
||||
- 菜单事件及处理,功能:菜单更新、菜单项显隐控制、菜单执行前后事件回调
|
||||
- 打包
|
||||
- 添加拖拽轨迹限制功能
|
||||
- 添加图形对象 可编辑属性 定义功能
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { fromUint8Array, toUint8Array } from 'js-base64';
|
||||
import { IPointData, Point } from 'pixi.js';
|
||||
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
|
||||
import { IscsFanDraw } from 'src/graphics/iscs-fan/IscsFanDrawAssistant';
|
||||
import { Link } from 'src/graphics/link/Link';
|
||||
import { LinkDraw } from 'src/graphics/link/LinkDrawAssistant';
|
||||
import {
|
||||
@ -10,11 +12,11 @@ import {
|
||||
JlDrawApp,
|
||||
KeyListener,
|
||||
} from 'src/jlgraphic';
|
||||
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
|
||||
import { MenuItemOptions } from 'src/jlgraphic/ui/Menu';
|
||||
import { IscsFanData } from './graphics/IscsFanInteraction';
|
||||
import { LinkData } from './graphics/LinkInteraction';
|
||||
import { graphicData } from './protos/draw_data_storage';
|
||||
import { IscsFanDraw } from 'src/graphics/iscs-fan/IscsFanDrawAssistant';
|
||||
import { IscsFanData } from './graphics/IscsFanInteraction';
|
||||
import { IscsFan } from 'src/graphics/iscs-fan/IscsFan';
|
||||
|
||||
export function fromStoragePoint(p: graphicData.Point): Point {
|
||||
return new Point(p.x, p.y);
|
||||
@ -46,6 +48,28 @@ export function toStorageTransform(
|
||||
});
|
||||
}
|
||||
|
||||
const UndoOptions: MenuItemOptions = {
|
||||
name: '撤销',
|
||||
};
|
||||
const RedoOptions: MenuItemOptions = {
|
||||
name: '重做',
|
||||
};
|
||||
const SelectAllOptions: MenuItemOptions = {
|
||||
name: '全选',
|
||||
};
|
||||
|
||||
export const DefaultCanvasMenu = new ContextMenu({
|
||||
name: '绘制-画布菜单',
|
||||
groups: [
|
||||
{
|
||||
items: [UndoOptions, RedoOptions],
|
||||
},
|
||||
{
|
||||
items: [SelectAllOptions],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export function initDrawApp(app: JlDrawApp) {
|
||||
app.setOptions({
|
||||
drawAssistants: [
|
||||
@ -58,6 +82,23 @@ export function initDrawApp(app: JlDrawApp) {
|
||||
],
|
||||
});
|
||||
|
||||
// 画布右键菜单
|
||||
app.registerMenu(DefaultCanvasMenu);
|
||||
app.canvas.on('_rightclick', (e) => {
|
||||
UndoOptions.disabled = !app.opRecord.hasUndo;
|
||||
RedoOptions.disabled = !app.opRecord.hasRedo;
|
||||
UndoOptions.handler = () => {
|
||||
app.opRecord.undo();
|
||||
};
|
||||
RedoOptions.handler = () => {
|
||||
app.opRecord.redo();
|
||||
};
|
||||
SelectAllOptions.handler = () => {
|
||||
app.selectAllGraphics();
|
||||
};
|
||||
DefaultCanvasMenu.open(e.global);
|
||||
});
|
||||
|
||||
app.addKeyboardListener(
|
||||
new KeyListener({
|
||||
value: 'KeyL',
|
||||
|
@ -29,7 +29,13 @@ import {
|
||||
BezierCurveEditPlugin,
|
||||
ILineGraphic,
|
||||
PolylineEditPlugin,
|
||||
addWayPoint,
|
||||
addWaypointConfig,
|
||||
clearWayPoint,
|
||||
clearWaypointsConfig,
|
||||
getWaypointRangeIndex,
|
||||
} from 'src/jlgraphic/plugins/GraphicEditPlugin';
|
||||
import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu';
|
||||
import { ILinkData, Link, LinkTemplate } from './Link';
|
||||
|
||||
export interface ILinkDrawOptions {
|
||||
@ -263,6 +269,15 @@ function onEditPointCreate(
|
||||
}
|
||||
}
|
||||
|
||||
const LinkEditMenu: ContextMenu = ContextMenu.init({
|
||||
name: '轨道编辑菜单',
|
||||
groups: [
|
||||
{
|
||||
items: [addWaypointConfig, clearWaypointsConfig],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* link路径编辑
|
||||
*/
|
||||
@ -270,6 +285,7 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
static Name = 'LinkPointsDrag';
|
||||
constructor(app: GraphicApp) {
|
||||
super(LinkPointsEditPlugin.Name, app);
|
||||
app.registerMenu(LinkEditMenu);
|
||||
}
|
||||
static init(app: GraphicApp): LinkPointsEditPlugin {
|
||||
return new LinkPointsEditPlugin(app);
|
||||
@ -281,14 +297,37 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
g.lineGraphic.eventMode = 'static';
|
||||
g.lineGraphic.cursor = 'pointer';
|
||||
g.lineGraphic.hitArea = new LinkGraphicHitArea(g);
|
||||
g.on('_rightclick', this.onContextMenu, this);
|
||||
g.on('selected', this.onSelected, this);
|
||||
g.on('unselected', this.onUnselected, this);
|
||||
}
|
||||
unbind(g: Link): void {
|
||||
g.off('_rightclick', this.onContextMenu, this);
|
||||
g.off('selected', this.onSelected, this);
|
||||
g.off('unselected', this.onUnselected, this);
|
||||
}
|
||||
|
||||
onContextMenu(e: FederatedMouseEvent) {
|
||||
const target = e.target as DisplayObject;
|
||||
const link = target.getGraphic() as Link;
|
||||
this.app.updateSelected(link);
|
||||
|
||||
addWaypointConfig.handler = () => {
|
||||
const linePoints = link.linePoints;
|
||||
const p = link.screenToLocalPoint(e.global);
|
||||
const { start, end } = getWaypointRangeIndex(
|
||||
linePoints,
|
||||
link.datas.curve,
|
||||
p
|
||||
);
|
||||
addWayPoint(link, link.datas.curve, start, end, p);
|
||||
};
|
||||
clearWaypointsConfig.handler = () => {
|
||||
clearWayPoint(link, link.datas.curve);
|
||||
};
|
||||
LinkEditMenu.open(e.global);
|
||||
}
|
||||
|
||||
onSelected(g: DisplayObject): void {
|
||||
const link = g as Link;
|
||||
let lep;
|
||||
|
@ -35,6 +35,8 @@ import {
|
||||
ICanvasProperties,
|
||||
JlCanvas,
|
||||
} from './JlGraphicApp';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
|
||||
|
||||
/**
|
||||
* 图形绘制助手
|
||||
@ -240,7 +242,7 @@ export class JlDrawApp extends GraphicApp {
|
||||
this.selectedData = null;
|
||||
this.initSelectedDataUpdateListen();
|
||||
// 拖拽操作记录
|
||||
this.appDragRecord();
|
||||
this.appOperationRecord();
|
||||
// 绑定通用键盘操作
|
||||
this.bindKeyboardOperation();
|
||||
}
|
||||
@ -269,7 +271,7 @@ export class JlDrawApp extends GraphicApp {
|
||||
return sda as DA;
|
||||
}
|
||||
|
||||
private appDragRecord() {
|
||||
private appOperationRecord() {
|
||||
let dragStartDatas: GraphicData[] = [];
|
||||
this.on('drag_op_start', (e: AppDragEvent) => {
|
||||
// 图形拖拽,记录初始数据
|
||||
@ -283,7 +285,7 @@ export class JlDrawApp extends GraphicApp {
|
||||
if (dragStartDatas.length > 0) {
|
||||
const newData = this.selectedGraphics.map((g) => g.saveData());
|
||||
this.opRecord.record(
|
||||
new DragOperation(
|
||||
new GraphicDataUpdateOperation(
|
||||
this,
|
||||
this.selectedGraphics,
|
||||
dragStartDatas,
|
||||
@ -293,6 +295,28 @@ export class JlDrawApp extends GraphicApp {
|
||||
dragStartDatas = [];
|
||||
}
|
||||
});
|
||||
// 菜单操作
|
||||
let preMenuHandleDatas: GraphicData[] = [];
|
||||
this.on('pre-menu-handle', (menu: MenuItemOptions) => {
|
||||
if (menu.name === '撤销' || menu.name === '重做') {
|
||||
return;
|
||||
}
|
||||
preMenuHandleDatas = this.selectedGraphics.map((g) => g.saveData());
|
||||
});
|
||||
this.on('post-menu-handle', () => {
|
||||
if (preMenuHandleDatas.length > 0) {
|
||||
const newData = this.selectedGraphics.map((g) => g.saveData());
|
||||
this.opRecord.record(
|
||||
new GraphicDataUpdateOperation(
|
||||
this,
|
||||
this.selectedGraphics,
|
||||
preMenuHandleDatas,
|
||||
newData
|
||||
)
|
||||
);
|
||||
preMenuHandleDatas = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -346,7 +370,6 @@ export class JlDrawApp extends GraphicApp {
|
||||
this.app.stage.addChild(this.scaleText);
|
||||
const bound = this.coordinates.getLocalBounds();
|
||||
this.scaleText.position.set(bound.width + 10, 0);
|
||||
this.canvas.interactive = true;
|
||||
this.canvas.on('mousemove', (e) => {
|
||||
if (e.target) {
|
||||
const { x, y } = this.toCanvasCoordinates(e.global);
|
||||
@ -517,11 +540,11 @@ export class JlDrawApp extends GraphicApp {
|
||||
const data = g.saveData();
|
||||
g.updateData(this.selectedData as GraphicData);
|
||||
this.opRecord.record(
|
||||
new UpdateDataOperation(
|
||||
new GraphicDataUpdateOperation(
|
||||
this,
|
||||
g,
|
||||
data,
|
||||
this.selectedData as GraphicData
|
||||
[g],
|
||||
[data],
|
||||
[this.selectedData as GraphicData]
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -604,7 +627,12 @@ function recordGraphicMoveOperation(app: GraphicApp) {
|
||||
) {
|
||||
const newData = app.selectedGraphics.map((g) => g.saveData());
|
||||
app.opRecord.record(
|
||||
new DragOperation(app, app.selectedGraphics, dragStartDatas, newData)
|
||||
new GraphicDataUpdateOperation(
|
||||
app,
|
||||
app.selectedGraphics,
|
||||
dragStartDatas,
|
||||
newData
|
||||
)
|
||||
);
|
||||
|
||||
handleArrowKeyMoveGraphics(app, (g) => {
|
||||
@ -695,39 +723,7 @@ export class GraphicDeleteOperation extends JlOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据操作
|
||||
*/
|
||||
export class UpdateDataOperation extends JlOperation {
|
||||
obj: JlGraphic;
|
||||
data: GraphicData; // 更新为data的protobuf数据
|
||||
old: GraphicData; // 更新前data的protobuf数据
|
||||
description = '';
|
||||
|
||||
constructor(
|
||||
app: GraphicApp,
|
||||
obj: JlGraphic,
|
||||
old: GraphicData,
|
||||
data: GraphicData
|
||||
) {
|
||||
super(app, 'update-payload');
|
||||
this.app = app;
|
||||
this.obj = obj;
|
||||
this.old = old;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
undo(): JlGraphic[] {
|
||||
this.obj.updateData(this.old);
|
||||
return [this.obj];
|
||||
}
|
||||
redo(): JlGraphic[] {
|
||||
this.obj.updateData(this.data);
|
||||
return [this.obj];
|
||||
}
|
||||
}
|
||||
|
||||
export class DragOperation extends JlOperation {
|
||||
export class GraphicDataUpdateOperation extends JlOperation {
|
||||
obj: JlGraphic[];
|
||||
oldData: GraphicData[];
|
||||
newData: GraphicData[];
|
||||
@ -746,7 +742,7 @@ export class DragOperation extends JlOperation {
|
||||
undo(): void | JlGraphic[] {
|
||||
for (let i = 0; i < this.obj.length; i++) {
|
||||
const g = this.obj[i];
|
||||
g.exitChildEdit();
|
||||
// g.exitChildEdit();
|
||||
g.updateData(this.oldData[i]);
|
||||
}
|
||||
return this.obj;
|
||||
@ -754,7 +750,7 @@ export class DragOperation extends JlOperation {
|
||||
redo(): void | JlGraphic[] {
|
||||
for (let i = 0; i < this.obj.length; i++) {
|
||||
const g = this.obj[i];
|
||||
g.exitChildEdit();
|
||||
// g.exitChildEdit();
|
||||
g.updateData(this.newData[i]);
|
||||
}
|
||||
return this.obj;
|
||||
|
@ -45,6 +45,7 @@ import {
|
||||
} from '../plugins/KeyboardPlugin';
|
||||
import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu';
|
||||
import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils';
|
||||
import { MenuItemOptions } from '../ui/Menu';
|
||||
|
||||
export const AppConsts = {
|
||||
viewportname: '__viewport',
|
||||
@ -284,6 +285,8 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents {
|
||||
drag_op_start: [event: AppDragEvent];
|
||||
drag_op_move: [event: AppDragEvent];
|
||||
drag_op_end: [event: AppDragEvent];
|
||||
'pre-menu-handle': [menu: MenuItemOptions];
|
||||
'post-menu-handle': [menu: MenuItemOptions];
|
||||
destroy: [app: GraphicApp];
|
||||
}
|
||||
|
||||
|
@ -305,6 +305,16 @@ DisplayObject.prototype.localToScreenPoints = function localToScreenPoints(
|
||||
): Point[] {
|
||||
return points.map((p) => this.toGlobal(p));
|
||||
};
|
||||
DisplayObject.prototype.screenToLocalPoint = function screenToLocalPoint(
|
||||
p: IPointData
|
||||
): Point {
|
||||
return this.toLocal(p);
|
||||
}
|
||||
DisplayObject.prototype.screenToLocalPoints = function screenToLocalPoints(
|
||||
...points: IPointData[]
|
||||
): Point[] {
|
||||
return points.map((p) => this.toLocal(p));
|
||||
}
|
||||
|
||||
DisplayObject.prototype.localBoundsToCanvasPoints =
|
||||
function localBoundsToCanvasPoints() {
|
||||
|
3
src/jlgraphic/global.d.ts
vendored
3
src/jlgraphic/global.d.ts
vendored
@ -11,6 +11,7 @@ declare namespace GlobalMixins {
|
||||
type BoundsGraphic = import('./plugins').BoundsGraphic;
|
||||
type IPointDataType = import('pixi.js').IPointData;
|
||||
type PointType = import('pixi.js').Point;
|
||||
type FederatedMouseEvent = import('pixi.js').FederatedMouseEvent;
|
||||
type DisplayObjectType = import('pixi.js').DisplayObject;
|
||||
type ContainerType = import('pixi.js').Container;
|
||||
interface DisplayObjectEvents {
|
||||
@ -22,6 +23,8 @@ declare namespace GlobalMixins {
|
||||
transformstart: [e: GraphicTransformEvent];
|
||||
transforming: [e: GraphicTransformEvent];
|
||||
transformend: [e: GraphicTransformEvent];
|
||||
_rightclick: [e: FederatedMouseEvent];
|
||||
_leftclick: [e: FederatedMouseEvent];
|
||||
selected: [DisplayObjectType];
|
||||
unselected: [DisplayObjectType];
|
||||
childselected: [DisplayObjectType];
|
||||
|
@ -46,6 +46,14 @@ export class OperationRecord {
|
||||
this.maxLen = maxLen;
|
||||
}
|
||||
|
||||
public get hasUndo(): boolean {
|
||||
return this.undoStack.length > 0;
|
||||
}
|
||||
|
||||
public get hasRedo(): boolean {
|
||||
return this.redoStack.length > 0;
|
||||
}
|
||||
|
||||
setMaxLen(v: number) {
|
||||
this.maxLen = v;
|
||||
const len = this.undoStack.length;
|
||||
|
@ -6,12 +6,20 @@ import {
|
||||
Graphics,
|
||||
IDestroyOptions,
|
||||
IPointData,
|
||||
Point,
|
||||
} from 'pixi.js';
|
||||
import { JlGraphic } from '../core';
|
||||
import { DraggablePoint } from '../graphic';
|
||||
import { calculateMirrorPoint, distance2 } from '../utils';
|
||||
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
|
||||
import {
|
||||
calculateFootPointFromPointToLine,
|
||||
calculateMirrorPoint,
|
||||
assertBezierPoints,
|
||||
distance2,
|
||||
linePoint,
|
||||
pointPolygon,
|
||||
} from '../utils';
|
||||
import { GraphicTransformEvent, ShiftData } from './GraphicTransformPlugin';
|
||||
|
||||
export abstract class GraphicEditPlugin<
|
||||
@ -22,6 +30,7 @@ export abstract class GraphicEditPlugin<
|
||||
super();
|
||||
this.graphic = g;
|
||||
this.zIndex = 2;
|
||||
this.sortableChildren = true;
|
||||
this.graphic.on('transformstart', this.hideAll, this);
|
||||
this.graphic.on('transformend', this.showAll, this);
|
||||
this.graphic.on('repaint', this.showAll, this);
|
||||
@ -45,20 +54,20 @@ export abstract class GraphicEditPlugin<
|
||||
}
|
||||
}
|
||||
|
||||
const addWaypoint: MenuItemOptions = {
|
||||
export const addWaypointConfig: MenuItemOptions = {
|
||||
name: '添加路径点',
|
||||
};
|
||||
const removeWaypoint: MenuItemOptions = {
|
||||
export const removeWaypointConfig: MenuItemOptions = {
|
||||
name: '移除路径点',
|
||||
};
|
||||
const clearWaypoints: MenuItemOptions = {
|
||||
export const clearWaypointsConfig: MenuItemOptions = {
|
||||
name: '清除所有路径点',
|
||||
};
|
||||
const menuOptions: MenuOptions = {
|
||||
name: '图形编辑点菜单',
|
||||
groups: [
|
||||
{
|
||||
items: [addWaypoint, removeWaypoint, clearWaypoints],
|
||||
items: [removeWaypointConfig, clearWaypointsConfig],
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -94,6 +103,42 @@ export abstract class LineEditPlugin extends GraphicEditPlugin<ILineGraphic> {
|
||||
abstract initEditPoints(): void;
|
||||
}
|
||||
|
||||
export function getWaypointRangeIndex(
|
||||
points: IPointData[],
|
||||
curve: boolean,
|
||||
p: IPointData
|
||||
): { start: number; end: number } {
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
if (!curve) {
|
||||
// 直线
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const sp = points[i - 1];
|
||||
const ep = points[i];
|
||||
const fp = calculateFootPointFromPointToLine(sp, ep, p);
|
||||
if (linePoint(sp, ep, fp, 1, true)) {
|
||||
start = i - 1;
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 贝塞尔曲线
|
||||
assertBezierPoints(points);
|
||||
for (let i = 0; i < points.length - 3; i += 3) {
|
||||
const p1 = points[i];
|
||||
const cp1 = points[i + 1];
|
||||
const cp2 = points[i + 2];
|
||||
const p2 = points[i + 3];
|
||||
if (pointPolygon(p, [p1, cp1, cp2, p2], 1)) {
|
||||
start = i;
|
||||
end = i + 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
export type onEditPointCreate = (
|
||||
g: ILineGraphic,
|
||||
dp: DraggablePoint,
|
||||
@ -125,6 +170,19 @@ export class PolylineEditPlugin extends LineEditPlugin {
|
||||
for (let i = 0; i < cps.length; i++) {
|
||||
const p = cps[i];
|
||||
const dp = new DraggablePoint(p);
|
||||
dp.on('rightclick', (e: FederatedMouseEvent) => {
|
||||
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
|
||||
// 路径中的点
|
||||
const app = dp.getGraphicApp();
|
||||
app.registerMenu(EditPointContextMenu);
|
||||
removeWaypointConfig.handler = () => {
|
||||
removeLineWayPoint(this.graphic, i);
|
||||
};
|
||||
clearWaypointsConfig.handler = () => {
|
||||
clearWayPoint(this.graphic, false);
|
||||
};
|
||||
EditPointContextMenu.open(e.global);
|
||||
});
|
||||
dp.on('transforming', () => {
|
||||
const tlp = this.graphic.canvasToLocalPoint(dp.position);
|
||||
const cp = this.linePoints[i];
|
||||
@ -166,6 +224,133 @@ export interface ICompleteBezierCurveEditPointOptions
|
||||
smooth: boolean;
|
||||
}
|
||||
|
||||
export function addWayPoint(
|
||||
graphic: ILineGraphic,
|
||||
curve: boolean,
|
||||
start: number,
|
||||
end: number,
|
||||
p: IPointData
|
||||
) {
|
||||
if (!curve) {
|
||||
addLineWayPoint(graphic, start, end, p);
|
||||
} else {
|
||||
addBezierWayPoint(graphic, start, end, p);
|
||||
}
|
||||
}
|
||||
|
||||
export function addLineWayPoint(
|
||||
graphic: ILineGraphic,
|
||||
start: number,
|
||||
end: number,
|
||||
p: IPointData
|
||||
) {
|
||||
const linePoints = graphic.linePoints;
|
||||
const points = linePoints.slice(0, start + 1);
|
||||
points.push(new Point(p.x, p.y));
|
||||
points.push(...linePoints.slice(end));
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
|
||||
function assertBezierWayPoint(i: number) {
|
||||
const c = i % 3;
|
||||
if (c !== 0) {
|
||||
throw new Error(`i=${i}的点不是路径点`);
|
||||
}
|
||||
}
|
||||
|
||||
export function addBezierWayPoint(
|
||||
graphic: ILineGraphic,
|
||||
start: number,
|
||||
end: number,
|
||||
p: IPointData
|
||||
) {
|
||||
if (start === end) {
|
||||
throw new Error('开始结束点不能一致');
|
||||
}
|
||||
assertBezierWayPoint(start);
|
||||
assertBezierWayPoint(end);
|
||||
const linePoints = graphic.linePoints;
|
||||
const points = linePoints.slice(0, start + 2);
|
||||
const ap = new Point(p.x, p.y);
|
||||
points.push(ap.clone(), ap.clone(), ap.clone());
|
||||
points.push(...linePoints.slice(end - 1));
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
|
||||
export function removeWayPoint(
|
||||
graphic: ILineGraphic,
|
||||
curve: boolean,
|
||||
i: number
|
||||
) {
|
||||
if (!curve) {
|
||||
removeLineWayPoint(graphic, i);
|
||||
} else {
|
||||
removeBezierWayPoint(graphic, i);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeLineWayPoint(graphic: ILineGraphic, i: number) {
|
||||
const linePoints = graphic.linePoints;
|
||||
if (linePoints.length > 2) {
|
||||
const points = linePoints.slice(0, i);
|
||||
points.push(...linePoints.slice(i + 1));
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeBezierWayPoint(graphic: ILineGraphic, i: number) {
|
||||
let points;
|
||||
const linePoints = graphic.linePoints;
|
||||
const c = i % 3;
|
||||
if (c !== 0) {
|
||||
throw new Error(`i=${i}的点${linePoints[i]}不是路径点`);
|
||||
}
|
||||
if (i === 0) {
|
||||
// 第一个点
|
||||
if (linePoints.length > 4) {
|
||||
points = linePoints.slice(3);
|
||||
} else {
|
||||
console.error('不能移除:剩余点数不足');
|
||||
}
|
||||
} else if (i === linePoints.length - 1) {
|
||||
// 最后一个点
|
||||
if (linePoints.length > 4) {
|
||||
points = linePoints.slice(0, linePoints.length - 3);
|
||||
} else {
|
||||
console.error('无法移除:剩余点数不足');
|
||||
}
|
||||
} else {
|
||||
// 中间点
|
||||
points = linePoints.slice(0, i - 1);
|
||||
points.push(...linePoints.slice(i + 2));
|
||||
}
|
||||
if (points) {
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除路径点(只留端点),适用于直线和贝塞尔曲线
|
||||
* @param graphic
|
||||
* @param curve
|
||||
*/
|
||||
export function clearWayPoint(graphic: ILineGraphic, curve: boolean) {
|
||||
const linePoints = graphic.linePoints;
|
||||
if (!curve) {
|
||||
if (linePoints.length > 2) {
|
||||
const points = linePoints.slice(0, 1);
|
||||
points.push(...linePoints.slice(-1));
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
} else {
|
||||
if (linePoints.length > 4) {
|
||||
const points = linePoints.slice(0, 2);
|
||||
points.push(...linePoints.slice(-2));
|
||||
graphic.linePoints = points;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 贝塞尔曲线编辑
|
||||
*/
|
||||
@ -195,11 +380,15 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
const startOrEnd = i == 0 || i == cps.length - 1;
|
||||
const c = i % 3;
|
||||
if (c === 1) {
|
||||
// 前一路径点的控制点
|
||||
dp.zIndex = 2;
|
||||
const fp = cps[i - 1];
|
||||
const line = new Graphics();
|
||||
this.drawAuxiliaryLine(line, fp, p);
|
||||
this.auxiliaryLines.push(line);
|
||||
} else if (c === 2) {
|
||||
// 后一路径点的控制点
|
||||
dp.zIndex = 3;
|
||||
const np = cps[i + 1];
|
||||
const line = new Graphics();
|
||||
this.drawAuxiliaryLine(line, p, np);
|
||||
@ -208,34 +397,14 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
dp.on('rightclick', (e: FederatedMouseEvent) => {
|
||||
// dp.getGraphicApp().openContextMenu(EditPointContextMenu.DefaultMenu);
|
||||
if (c === 0) {
|
||||
// 路径中的点
|
||||
const app = dp.getGraphicApp();
|
||||
app.registerMenu(EditPointContextMenu);
|
||||
removeWaypoint.onClick = () => {
|
||||
let points;
|
||||
if (i === 0) {
|
||||
// 第一个点
|
||||
if (this.linePoints.length > 4) {
|
||||
points = this.linePoints.slice(3);
|
||||
} else {
|
||||
console.error('无法移除');
|
||||
}
|
||||
} else if (i === cps.length - 1) {
|
||||
// 最后一个点
|
||||
if (this.linePoints.length > 4) {
|
||||
points = this.linePoints.slice(0, this.linePoints.length - 3);
|
||||
} else {
|
||||
console.error('无法移除');
|
||||
}
|
||||
} else {
|
||||
// 中间点
|
||||
points = [];
|
||||
points.push(...this.linePoints.slice(0, i - 1));
|
||||
points.push(...this.linePoints.slice(i + 2));
|
||||
}
|
||||
if (points) {
|
||||
console.log('points', points);
|
||||
this.graphic.linePoints = points;
|
||||
}
|
||||
removeWaypointConfig.handler = () => {
|
||||
removeBezierWayPoint(this.graphic, i);
|
||||
};
|
||||
clearWaypointsConfig.handler = () => {
|
||||
clearWayPoint(this.graphic, true);
|
||||
};
|
||||
EditPointContextMenu.open(e.global);
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ export class GraphicTransformPlugin extends BaseInteractionPlugin {
|
||||
return targets;
|
||||
}
|
||||
onDragStart(e: AppDragEvent) {
|
||||
if (!e.target.isCanvas()) {
|
||||
if (!e.target.isCanvas() && e.isLeftButton) {
|
||||
const targets: DisplayObject[] = this.getDraggedTargets(e);
|
||||
if (targets.length > 0) {
|
||||
targets.forEach((target) => {
|
||||
@ -248,7 +248,7 @@ export class GraphicTransformPlugin extends BaseInteractionPlugin {
|
||||
}
|
||||
|
||||
onDragMove(e: AppDragEvent) {
|
||||
if (!e.target.isCanvas()) {
|
||||
if (!e.target.isCanvas() && e.isLeftButton) {
|
||||
const targets: DisplayObject[] = this.getDraggedTargets(e);
|
||||
if (targets.length > 0) {
|
||||
// 处理位移
|
||||
@ -291,7 +291,7 @@ export class GraphicTransformPlugin extends BaseInteractionPlugin {
|
||||
}
|
||||
|
||||
onDragEnd(e: AppDragEvent) {
|
||||
if (!e.target.isCanvas()) {
|
||||
if (!e.target.isCanvas() && e.isLeftButton) {
|
||||
const targets: DisplayObject[] = this.getDraggedTargets(e);
|
||||
targets.forEach((target) => {
|
||||
if (target.shiftStartPoint) {
|
||||
|
@ -165,9 +165,10 @@ export class AppDragEvent {
|
||||
*/
|
||||
export class DragPlugin extends BaseInteractionPlugin {
|
||||
static Name = '__drag_operation_plugin';
|
||||
threshold = 3;
|
||||
private threshold = 3;
|
||||
target: DisplayObject | null = null;
|
||||
start: Point | null = null;
|
||||
startClientPoint: Point | null = null;
|
||||
drag = false;
|
||||
constructor(app: GraphicApp) {
|
||||
super(app, DragPlugin.Name, InteractionPluginType.Other);
|
||||
@ -193,17 +194,19 @@ export class DragPlugin extends BaseInteractionPlugin {
|
||||
onPointerDown(e: FederatedPointerEvent) {
|
||||
this.target = e.target as DisplayObject;
|
||||
this.start = this.app.toCanvasCoordinates(e.global);
|
||||
this.startClientPoint = e.global.clone();
|
||||
const canvas = this.app.canvas;
|
||||
canvas.on('pointermove', this.onPointerMove, this);
|
||||
canvas.on('pointerup', this.onPointerUp, this);
|
||||
canvas.on('pointerupoutside', this.onPointerUp, this);
|
||||
}
|
||||
onPointerMove(e: FederatedPointerEvent) {
|
||||
if (this.start) {
|
||||
const current = this.app.toCanvasCoordinates(e.global);
|
||||
if (this.start && this.startClientPoint) {
|
||||
const current = e.global;
|
||||
const sgp = this.startClientPoint;
|
||||
const dragStart =
|
||||
Math.abs(current.x - this.start.x) > this.threshold ||
|
||||
Math.abs(current.y - this.start.y) > this.threshold;
|
||||
Math.abs(current.x - sgp.x) > this.threshold ||
|
||||
Math.abs(current.y - sgp.y) > this.threshold;
|
||||
if (this.target && this.start && !this.drag && dragStart) {
|
||||
this.app.emit(
|
||||
'drag_op_start',
|
||||
@ -230,6 +233,21 @@ export class DragPlugin extends BaseInteractionPlugin {
|
||||
'drag_op_end',
|
||||
new AppDragEvent(this.app, 'end', this.target, e, this.start)
|
||||
);
|
||||
} else if (this.target && this.start && !this.drag) {
|
||||
// this.target.emit('click', this.target);
|
||||
const ade = new AppDragEvent(this.app, 'end', this.target, e, this.start);
|
||||
const graphic = this.target.getGraphic();
|
||||
if (ade.isRightButton) {
|
||||
this.target.emit('_rightclick', e);
|
||||
if (graphic != null) {
|
||||
graphic.emit('_rightclick', e);
|
||||
}
|
||||
} else if (ade.isLeftButton) {
|
||||
this.target.emit('_leftclick', e);
|
||||
if (graphic != null) {
|
||||
graphic.emit('_leftclick', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
const canvas = this.app.canvas;
|
||||
canvas.off('mousemove', this.onPointerMove, this);
|
||||
@ -243,6 +261,7 @@ export class DragPlugin extends BaseInteractionPlugin {
|
||||
clearCache() {
|
||||
this.drag = false;
|
||||
this.start = null;
|
||||
this.startClientPoint = null;
|
||||
this.target = null;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,12 @@ export class ContextMenuPlugin {
|
||||
|
||||
constructor(app: GraphicApp) {
|
||||
this.app = app;
|
||||
const canvas = this.app.canvas;
|
||||
canvas.on('pointerdown', () => {
|
||||
this.contextMenuMap.forEach((menu) => {
|
||||
menu.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerMenu(menu: ContextMenu) {
|
||||
@ -262,7 +268,7 @@ export class ContextMenu extends Container {
|
||||
|
||||
this.addChild(...this.groups);
|
||||
|
||||
this.interactive = true;
|
||||
this.eventMode = 'static';
|
||||
this.on('pointerover', () => {
|
||||
this.active = true;
|
||||
});
|
||||
@ -288,6 +294,7 @@ export class ContextMenu extends Container {
|
||||
}
|
||||
|
||||
updateBg() {
|
||||
this.bg.clear();
|
||||
const options = this.menuOptions;
|
||||
let maxItemWidth = 0;
|
||||
let borderHeight = 0;
|
||||
@ -422,10 +429,7 @@ class MenuGroup extends Container {
|
||||
config.items.forEach((item) => {
|
||||
this.items.push(new ContextMenuItem(menu, item));
|
||||
});
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
const item = this.items[i];
|
||||
item.position.y = i * item.totalHeight;
|
||||
}
|
||||
|
||||
this.addChild(...this.items);
|
||||
}
|
||||
|
||||
@ -464,7 +468,21 @@ class MenuGroup extends Container {
|
||||
}
|
||||
|
||||
update() {
|
||||
this.items.forEach((item) => item.update());
|
||||
let i = 0;
|
||||
this.items.forEach((item) => {
|
||||
item.update();
|
||||
if (item.visible) {
|
||||
item.position.y = i * item.totalHeight;
|
||||
i++;
|
||||
}
|
||||
});
|
||||
// this.items.forEach()
|
||||
// for (let i = 0; i < this.items.length; i++) {
|
||||
// const item = this.items[i];
|
||||
// if (item.visible) {
|
||||
// item.position.y = i * item.totalHeight;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
updateItemBox(maxItemWidth: number) {
|
||||
@ -511,6 +529,42 @@ class ContextMenuItem extends Container {
|
||||
this.initSubMenu();
|
||||
}
|
||||
|
||||
registerEventHandler() {
|
||||
this.eventMode = 'static';
|
||||
this.cursor = 'pointer';
|
||||
this.on('pointerover', () => {
|
||||
this.active = true;
|
||||
if (this.config.disabled) {
|
||||
this.cursor = 'not-allowed';
|
||||
} else {
|
||||
this.cursor = 'pointer';
|
||||
}
|
||||
if (this.subMenu) {
|
||||
const p = this.toGlobal(new Point(this.width));
|
||||
this.subMenu.open(p);
|
||||
}
|
||||
});
|
||||
this.on('pointerout', () => {
|
||||
this.active = false;
|
||||
});
|
||||
this.on('pointertap', () => {
|
||||
if (this.config.disabled) {
|
||||
// 禁用,不处理
|
||||
return;
|
||||
}
|
||||
if (this.config.handler) {
|
||||
this.menu.plugin.app.emit('pre-menu-handle', this.config);
|
||||
this.config.handler();
|
||||
this.menu.plugin.app.emit('post-menu-handle', this.config);
|
||||
}
|
||||
if (!this.config.subMenu || this.config.subMenu.length === 0) {
|
||||
this.active = false;
|
||||
this.menu.active = false;
|
||||
this.menu.rootMenu.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get active(): boolean {
|
||||
return this._active || (this.subMenu != undefined && this.subMenu.active);
|
||||
}
|
||||
@ -543,8 +597,12 @@ class ContextMenuItem extends Container {
|
||||
}
|
||||
|
||||
public get totalHeight(): number {
|
||||
if (this.config.visible === false) {
|
||||
return 0;
|
||||
} else {
|
||||
return this.paddingTop + this.paddingBottom + this.nameGraphic.height;
|
||||
}
|
||||
}
|
||||
|
||||
public get nameBounds(): Rectangle {
|
||||
return this.nameGraphic.getLocalBounds();
|
||||
@ -683,41 +741,16 @@ class ContextMenuItem extends Container {
|
||||
}
|
||||
|
||||
// 事件监听
|
||||
this.interactive = true;
|
||||
this.hitArea = new Rectangle(0, 0, width, height);
|
||||
this.cursor = 'pointer';
|
||||
this.on('pointerover', () => {
|
||||
this.active = true;
|
||||
if (this.config.disabled) {
|
||||
this.cursor = 'not-allowed';
|
||||
} else {
|
||||
this.cursor = 'pointer';
|
||||
}
|
||||
if (this.subMenu) {
|
||||
const p = this.toGlobal(new Point(this.width));
|
||||
this.subMenu.open(p);
|
||||
}
|
||||
});
|
||||
this.on('pointerout', () => {
|
||||
this.active = false;
|
||||
});
|
||||
this.on('pointertap', () => {
|
||||
if (this.config.disabled) {
|
||||
// 禁用,不处理
|
||||
return;
|
||||
}
|
||||
if (this.config.onClick) {
|
||||
this.config.onClick();
|
||||
}
|
||||
if (!this.config.subMenu || this.config.subMenu.length === 0) {
|
||||
this.active = false;
|
||||
this.menu.active = false;
|
||||
this.menu.rootMenu.close();
|
||||
}
|
||||
});
|
||||
this.registerEventHandler();
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.config.visible === false) {
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
this.visible = true;
|
||||
this.nameText.text = this.config.name;
|
||||
this.nameText.style.fontSize = this.fontSize;
|
||||
this.nameText.style.fill = this.fontColor;
|
||||
|
@ -23,6 +23,10 @@ export interface MenuOptions {
|
||||
* 菜单分组
|
||||
*/
|
||||
export interface MenuGroupOptions {
|
||||
/**
|
||||
* 分组命名
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 菜单项
|
||||
*/
|
||||
@ -53,7 +57,7 @@ export interface MenuStyleOptions {
|
||||
*/
|
||||
borderColor?: string;
|
||||
/**
|
||||
* 包围框是否圆角,小于等于0为直角,圆角的半径
|
||||
* 包围框是否圆角,圆角的半径,0为直角
|
||||
*/
|
||||
borderRoundRadius?: number;
|
||||
/**
|
||||
@ -90,9 +94,9 @@ export interface MenuItemOptions {
|
||||
*/
|
||||
shortcutKeys?: string[];
|
||||
/**
|
||||
* 点击处理
|
||||
* 菜单逻辑处理
|
||||
*/
|
||||
onClick?: () => void;
|
||||
handler?: () => void;
|
||||
fontColor?: string;
|
||||
/**
|
||||
* 子菜单
|
||||
@ -151,7 +155,7 @@ export const DefaultWhiteStyleOptions: MenuCompletionStyleOptions = {
|
||||
borderWidth: 1,
|
||||
borderColor: '#4C4C4C',
|
||||
/**
|
||||
* 默认直角
|
||||
* 默认圆角
|
||||
*/
|
||||
borderRoundRadius: 5,
|
||||
itemStyle: {
|
||||
|
@ -2,12 +2,11 @@ import {
|
||||
Container,
|
||||
DisplayObject,
|
||||
IPointData,
|
||||
Matrix,
|
||||
Point,
|
||||
Rectangle,
|
||||
} from 'pixi.js';
|
||||
import Vector2 from '../math/Vector2';
|
||||
import { floatEquals, isZero } from '../math';
|
||||
import Vector2 from '../math/Vector2';
|
||||
|
||||
/**
|
||||
* 递归父节点执行逻辑
|
||||
@ -86,10 +85,23 @@ export interface BezierParam {
|
||||
cp2: IPointData;
|
||||
}
|
||||
|
||||
export function convertToBezierParams(points: IPointData[]): BezierParam[] {
|
||||
/**
|
||||
* 判断贝塞尔曲线数据是否正确
|
||||
* @param points
|
||||
*/
|
||||
export function assertBezierPoints(points: IPointData[]) {
|
||||
if (points.length < 4 || points.length % 3 !== 1) {
|
||||
throw new Error(`bezierCurve 数据错误: ${points}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为贝塞尔曲线参数
|
||||
* @param points
|
||||
* @returns
|
||||
*/
|
||||
export function convertToBezierParams(points: IPointData[]): BezierParam[] {
|
||||
assertBezierPoints(points);
|
||||
const bps: BezierParam[] = [];
|
||||
for (let i = 0; i < points.length - 3; i += 3) {
|
||||
const p1 = new Point(points[i].x, points[i].y);
|
||||
@ -240,22 +252,6 @@ export function deserializeTransformInto(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算变换后的点坐标
|
||||
* @param point 坐标点
|
||||
* @param transform 变换矩阵
|
||||
* @returns
|
||||
*/
|
||||
export function calculateTransformedPoint(
|
||||
point: IPointData,
|
||||
transform: Matrix
|
||||
): Point {
|
||||
const { a, b, c, d, tx, ty } = transform;
|
||||
const x = a * point.x + c * point.y + tx;
|
||||
const y = b * point.x + d * point.y + ty;
|
||||
return new Point(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将直线转换为多边形
|
||||
* @param p1
|
||||
|
Loading…
Reference in New Issue
Block a user