菜单使用调整
复制bug修改 编辑点相关调整 link编辑点添加右键菜单,实现移除编辑点逻辑
This commit is contained in:
parent
f649cb4a3d
commit
cd951906b0
@ -12,6 +12,8 @@ viewport 使用的 github 开源的 pixi-viewport[pixi-viewport](https://github.
|
||||
- 图形复制功能(完成)
|
||||
- 绘制应用图形外包围框及旋转缩放功能(完成)
|
||||
- 绘制增加吸附功能(移动到特定位置附近吸附)(完成)
|
||||
- 图形动画抽象
|
||||
- 添加公用动画逻辑(如按指定路径位移,按角度旋转、按比例缩放、透明度控制等)
|
||||
- 菜单事件及处理
|
||||
- 打包
|
||||
- 添加拖拽轨迹限制功能
|
||||
|
@ -27,6 +27,20 @@ export abstract class BaseGraphicData implements GraphicData {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
static defaultCommonInfo(): graphicData.CommonInfo {
|
||||
return new graphicData.CommonInfo({
|
||||
id: '',
|
||||
graphicType: '',
|
||||
transform: new graphicData.Transform({
|
||||
position: new graphicData.Point({ x: 0, y: 0 }),
|
||||
scale: new graphicData.Point({ x: 1, y: 1 }),
|
||||
rotation: 0,
|
||||
skew: new graphicData.Point({ x: 0, y: 0 }),
|
||||
}),
|
||||
childTransforms: [],
|
||||
});
|
||||
}
|
||||
|
||||
getData<D extends IProtoGraphicData>(): D {
|
||||
return this._data as D;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export class IscsFanData extends BaseGraphicData implements IIscsFanData {
|
||||
fan = data;
|
||||
} else {
|
||||
fan = new graphicData.IscsFan({
|
||||
common: new graphicData.CommonInfo(),
|
||||
common: BaseGraphicData.defaultCommonInfo(),
|
||||
});
|
||||
}
|
||||
super(fan);
|
||||
|
@ -9,7 +9,7 @@ export class LinkData extends BaseGraphicData implements ILinkData {
|
||||
let link;
|
||||
if (!data) {
|
||||
link = new graphicData.Link({
|
||||
common: new graphicData.CommonInfo(),
|
||||
common: BaseGraphicData.defaultCommonInfo(),
|
||||
});
|
||||
} else {
|
||||
link = data;
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { FederatedMouseEvent, Point } from 'pixi.js';
|
||||
import {
|
||||
GraphicDrawAssistant,
|
||||
GraphicTransform,
|
||||
JlDrawApp,
|
||||
} from 'src/jlgraphic';
|
||||
import { GraphicDrawAssistant, JlDrawApp } from 'src/jlgraphic';
|
||||
import { IIscsFanData, IscsFan, IscsFanTemplate } from './IscsFan';
|
||||
|
||||
export class IscsFanDraw extends GraphicDrawAssistant<
|
||||
@ -43,10 +39,7 @@ export class IscsFanDraw extends GraphicDrawAssistant<
|
||||
this.finish();
|
||||
}
|
||||
prepareData(data: IIscsFanData): boolean {
|
||||
// 变换设置必须新建变换赋值,不能直接修改变换数据
|
||||
const transfrom = GraphicTransform.default();
|
||||
transfrom.position = this.iscsFan.position.clone();
|
||||
data.transform = transfrom;
|
||||
data.transform = this.iscsFan.saveTransform();
|
||||
return true;
|
||||
}
|
||||
onEsc(): void {
|
||||
|
@ -3,8 +3,9 @@ import {
|
||||
GraphicData,
|
||||
JlGraphic,
|
||||
JlGraphicTemplate,
|
||||
convertToBezierPoints,
|
||||
convertToBezierParams,
|
||||
} from 'src/jlgraphic';
|
||||
import { ILineGraphic } from 'src/jlgraphic/plugins/GraphicEditPlugin';
|
||||
|
||||
export interface ILinkData extends GraphicData {
|
||||
get code(): string; // 编号
|
||||
@ -24,7 +25,7 @@ export interface ILinkData extends GraphicData {
|
||||
eq(other: ILinkData): boolean;
|
||||
}
|
||||
|
||||
export class Link extends JlGraphic {
|
||||
export class Link extends JlGraphic implements ILineGraphic {
|
||||
static Type = 'Link';
|
||||
lineGraphic: Graphics;
|
||||
constructor() {
|
||||
@ -32,6 +33,7 @@ export class Link extends JlGraphic {
|
||||
this.lineGraphic = new Graphics();
|
||||
this.addChild(this.lineGraphic);
|
||||
}
|
||||
|
||||
get datas(): ILinkData {
|
||||
return this.getDatas<ILinkData>();
|
||||
}
|
||||
@ -46,7 +48,7 @@ export class Link extends JlGraphic {
|
||||
);
|
||||
if (this.datas.curve) {
|
||||
// 曲线
|
||||
const bps = convertToBezierPoints(this.datas.points);
|
||||
const bps = convertToBezierParams(this.datas.points);
|
||||
bps.forEach((bp) => {
|
||||
this.lineGraphic.drawBezierCurve(
|
||||
bp.p1,
|
||||
@ -66,7 +68,14 @@ export class Link extends JlGraphic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get linePoints(): IPointData[] {
|
||||
return this.datas.points;
|
||||
}
|
||||
set linePoints(points: IPointData[]) {
|
||||
this.datas.points = points;
|
||||
this.repaint();
|
||||
this.emit('pointupdate', this, this.datas.points);
|
||||
}
|
||||
getStartPoint(): IPointData {
|
||||
return this.datas.points[0];
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
JlGraphic,
|
||||
KeyListener,
|
||||
calculateMirrorPoint,
|
||||
convertToBezierPoints,
|
||||
convertToBezierParams,
|
||||
linePoint,
|
||||
pointPolygon,
|
||||
} from 'src/jlgraphic';
|
||||
@ -26,6 +26,7 @@ import AbsorbablePoint, {
|
||||
} from 'src/jlgraphic/graphic/AbsorbablePosition';
|
||||
import {
|
||||
BezierCurveEditPlugin,
|
||||
ILineGraphic,
|
||||
PolylineEditPlugin,
|
||||
} from 'src/jlgraphic/plugins/GraphicEditPlugin';
|
||||
import { ILinkData, Link, LinkTemplate } from './Link';
|
||||
@ -64,7 +65,7 @@ export class LinkDraw extends GraphicDrawAssistant<LinkTemplate, ILinkData> {
|
||||
this.container.addChild(this.graphic);
|
||||
this.graphicTemplate.curve = true;
|
||||
|
||||
new LinkPointsEditPlugin(app);
|
||||
LinkPointsEditPlugin.init(app);
|
||||
}
|
||||
|
||||
bind(): void {
|
||||
@ -132,7 +133,7 @@ export class LinkDraw extends GraphicDrawAssistant<LinkTemplate, ILinkData> {
|
||||
cp.copyFrom(mp);
|
||||
}
|
||||
|
||||
const bps = convertToBezierPoints(ps);
|
||||
const bps = convertToBezierParams(ps);
|
||||
bps.forEach((bp) =>
|
||||
this.graphic.drawBezierCurve(
|
||||
bp.p1,
|
||||
@ -184,7 +185,7 @@ export class LinkGraphicHitArea implements IHitArea {
|
||||
const p = new Point(x, y);
|
||||
if (this.link.datas.curve) {
|
||||
// 曲线
|
||||
const bps = convertToBezierPoints(this.link.datas.points);
|
||||
const bps = convertToBezierParams(this.link.datas.points);
|
||||
for (let i = 0; i < bps.length; i++) {
|
||||
const bp = bps[i];
|
||||
if (
|
||||
@ -244,7 +245,7 @@ function buildAbsorbablePositions(link: Link): AbsorbablePosition[] {
|
||||
* @param index
|
||||
*/
|
||||
function onEditPointCreate(
|
||||
g: JlGraphic,
|
||||
g: ILineGraphic,
|
||||
dp: DraggablePoint,
|
||||
index: number
|
||||
): void {
|
||||
@ -267,6 +268,9 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
constructor(app: GraphicApp) {
|
||||
super(LinkPointsEditPlugin.Name, app);
|
||||
}
|
||||
static init(app: GraphicApp): LinkPointsEditPlugin {
|
||||
return new LinkPointsEditPlugin(app);
|
||||
}
|
||||
filter(...grahpics: JlGraphic[]): Link[] | undefined {
|
||||
return grahpics.filter((g) => g.type == Link.Type) as Link[];
|
||||
}
|
||||
@ -291,11 +295,9 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
BezierCurveEditPlugin.Name
|
||||
);
|
||||
if (!lep) {
|
||||
lep = new BezierCurveEditPlugin(
|
||||
link,
|
||||
link.datas.points,
|
||||
onEditPointCreate
|
||||
);
|
||||
lep = new BezierCurveEditPlugin(link, {
|
||||
onEditPointCreate,
|
||||
});
|
||||
link.addAssistantAppend(lep);
|
||||
}
|
||||
} else {
|
||||
@ -304,11 +306,7 @@ export class LinkPointsEditPlugin extends GraphicInteractionPlugin<Link> {
|
||||
PolylineEditPlugin.Name
|
||||
);
|
||||
if (!lep) {
|
||||
lep = new PolylineEditPlugin(
|
||||
link,
|
||||
link.datas.points,
|
||||
onEditPointCreate
|
||||
);
|
||||
lep = new PolylineEditPlugin(link, { onEditPointCreate });
|
||||
link.addAssistantAppend(lep);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ import {
|
||||
JlGraphicAppKeyboardPlugin,
|
||||
KeyListener,
|
||||
} from '../plugins/KeyboardPlugin';
|
||||
import { ContextMenuPlugin } from '../ui/ContextMenu';
|
||||
import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu';
|
||||
import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils';
|
||||
|
||||
export const AppConsts = {
|
||||
@ -479,6 +479,14 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
return template as GT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册菜单
|
||||
* @param menu
|
||||
*/
|
||||
registerMenu(menu: ContextMenu) {
|
||||
this.menuPlugin.registerMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使能websocket Stomp通信
|
||||
*/
|
||||
@ -609,7 +617,7 @@ export class GraphicApp extends EventEmitter<GraphicAppEvents> {
|
||||
for (const item of this.graphicTemplateMap) {
|
||||
await item[1].loadAssets();
|
||||
}
|
||||
console.log('开始加载proto数据', protos);
|
||||
// console.log('开始加载proto数据', protos);
|
||||
// 加载数据到图形存储
|
||||
protos.forEach((proto) => {
|
||||
const template = this.getGraphicTemplatesByType(proto.graphicType);
|
||||
|
@ -917,14 +917,15 @@ export abstract class JlGraphicTemplate<G extends JlGraphic> {
|
||||
if (graphic._datas) {
|
||||
// 数据克隆
|
||||
const datas = graphic.saveData();
|
||||
g.updateData(datas);
|
||||
g.loadData(datas);
|
||||
}
|
||||
if (graphic._states) {
|
||||
// 状态克隆
|
||||
const state = graphic.getStates().clone();
|
||||
g.updateStates(state);
|
||||
g.loadState(state);
|
||||
}
|
||||
g.id = GraphicIdGenerator.next();
|
||||
g.repaint();
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
@ -143,6 +143,8 @@ export class CommonMouseTool extends AppInteractionPlugin {
|
||||
drag = false;
|
||||
graphicSelect = false;
|
||||
|
||||
rightTarget: DisplayObject | null = null;
|
||||
|
||||
/**
|
||||
* 可吸附位置列表
|
||||
*/
|
||||
@ -206,16 +208,22 @@ export class CommonMouseTool extends AppInteractionPlugin {
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
setCursor() {
|
||||
if (this.app.app.view.style) {
|
||||
setCursor(e: FederatedMouseEvent) {
|
||||
this.rightTarget = e.target as DisplayObject;
|
||||
if (e.target instanceof JlCanvas && this.app.app.view.style) {
|
||||
this.app.app.view.style.cursor = 'grab';
|
||||
}
|
||||
}
|
||||
|
||||
resumeCursor() {
|
||||
if (this.app.app.view.style) {
|
||||
if (
|
||||
this.rightTarget &&
|
||||
this.rightTarget instanceof JlCanvas &&
|
||||
this.app.app.view.style
|
||||
) {
|
||||
this.app.app.view.style.cursor = 'inherit';
|
||||
}
|
||||
this.rightTarget = null;
|
||||
}
|
||||
|
||||
onMouseDown(e: FederatedMouseEvent) {
|
||||
|
@ -69,10 +69,12 @@ export class GraphicCopyPlugin {
|
||||
const template = app.getGraphicTemplatesByType(g.type);
|
||||
const clone = template.clone(g);
|
||||
this.copys.push(clone);
|
||||
this.container.position.set(0, 0);
|
||||
this.container.addChild(clone);
|
||||
});
|
||||
this.app.canvas.on('mousemove', this.onPointerMove, this);
|
||||
this.app.canvas.on('pointertap', this.onFinish, this);
|
||||
this.app.canvas.on('mouseup', this.onFinish, this);
|
||||
this.app.canvas.on('rightup', this.cancle, this);
|
||||
this.keyListeners.forEach((kl) => {
|
||||
this.app.addKeyboardListener(kl);
|
||||
});
|
||||
@ -86,7 +88,8 @@ export class GraphicCopyPlugin {
|
||||
this.container.removeChildren();
|
||||
this.app.canvas.removeChild(this.container);
|
||||
this.app.canvas.off('mousemove', this.onPointerMove, this);
|
||||
this.app.canvas.off('pointertap', this.onFinish, this);
|
||||
this.app.canvas.off('mouseup', this.onFinish, this);
|
||||
this.app.canvas.off('rightup', this.cancle, this);
|
||||
this.keyListeners.forEach((kl) => {
|
||||
this.app.removeKeyboardListener(kl);
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
Color,
|
||||
Container,
|
||||
DisplayObject,
|
||||
FederatedMouseEvent,
|
||||
Graphics,
|
||||
IDestroyOptions,
|
||||
IPointData,
|
||||
@ -10,10 +11,14 @@ import { JlGraphic } from '../core';
|
||||
import { DraggablePoint } from '../graphic';
|
||||
import { calculateMirrorPoint, distance2 } from '../utils';
|
||||
import { GraphicDragEvent } from './CommonMousePlugin';
|
||||
import { MenuItemOptions, MenuOptions } from '../ui/Menu';
|
||||
import { ContextMenu } from '../ui/ContextMenu';
|
||||
|
||||
export abstract class GraphicEditPlugin extends Container {
|
||||
graphic: JlGraphic;
|
||||
constructor(g: JlGraphic) {
|
||||
export abstract class GraphicEditPlugin<
|
||||
DO extends DisplayObject = DisplayObject
|
||||
> extends Container {
|
||||
graphic: DO;
|
||||
constructor(g: DO) {
|
||||
super();
|
||||
this.graphic = g;
|
||||
this.zIndex = 2;
|
||||
@ -40,18 +45,37 @@ export abstract class GraphicEditPlugin extends Container {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class LineEditPlugin extends GraphicEditPlugin {
|
||||
const addWaypoint: MenuItemOptions = {
|
||||
name: '添加路径点',
|
||||
};
|
||||
const removeWaypoint: MenuItemOptions = {
|
||||
name: '移除路径点',
|
||||
};
|
||||
const clearWaypoints: MenuItemOptions = {
|
||||
name: '清除所有路径点',
|
||||
};
|
||||
const menuOptions: MenuOptions = {
|
||||
name: '图形编辑点菜单',
|
||||
groups: [
|
||||
{
|
||||
items: [addWaypoint, removeWaypoint, clearWaypoints],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const EditPointContextMenu = ContextMenu.init(menuOptions);
|
||||
|
||||
export interface ILineGraphic extends JlGraphic {
|
||||
get linePoints(): IPointData[];
|
||||
set linePoints(points: IPointData[]);
|
||||
}
|
||||
|
||||
export abstract class LineEditPlugin extends GraphicEditPlugin<ILineGraphic> {
|
||||
linePoints: IPointData[];
|
||||
editedPoints: DraggablePoint[] = [];
|
||||
onEditPointCreate?: onEditPointCreate;
|
||||
constructor(
|
||||
g: JlGraphic,
|
||||
linePoints: IPointData[],
|
||||
onEditPointCreate?: onEditPointCreate
|
||||
) {
|
||||
constructor(g: ILineGraphic) {
|
||||
super(g);
|
||||
this.linePoints = linePoints;
|
||||
this.onEditPointCreate = onEditPointCreate;
|
||||
this.linePoints = g.linePoints;
|
||||
this.graphic.on('pointupdate', this.reset, this);
|
||||
}
|
||||
|
||||
@ -71,21 +95,27 @@ export abstract class LineEditPlugin extends GraphicEditPlugin {
|
||||
}
|
||||
|
||||
export type onEditPointCreate = (
|
||||
g: JlGraphic,
|
||||
g: ILineGraphic,
|
||||
dp: DraggablePoint,
|
||||
index: number
|
||||
) => void;
|
||||
|
||||
export interface IEditPointOptions {
|
||||
/**
|
||||
* 编辑点创建处理
|
||||
*/
|
||||
onEditPointCreate?: onEditPointCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 折线编辑(兼容线段)
|
||||
*/
|
||||
export class PolylineEditPlugin extends LineEditPlugin {
|
||||
static Name = 'line_points_edit';
|
||||
constructor(
|
||||
g: JlGraphic,
|
||||
linePoints: IPointData[],
|
||||
onEditPointCreate?: onEditPointCreate
|
||||
) {
|
||||
super(g, linePoints, onEditPointCreate);
|
||||
options: IEditPointOptions;
|
||||
constructor(g: ILineGraphic, options?: IEditPointOptions) {
|
||||
super(g);
|
||||
this.options = Object.assign({}, options);
|
||||
this.name = PolylineEditPlugin.Name;
|
||||
this.initEditPoints();
|
||||
}
|
||||
@ -102,8 +132,8 @@ export class PolylineEditPlugin extends LineEditPlugin {
|
||||
cp.y = tlp.y;
|
||||
this.graphic.repaint();
|
||||
});
|
||||
if (this.onEditPointCreate) {
|
||||
this.onEditPointCreate(this.graphic, dp, i);
|
||||
if (this.options.onEditPointCreate) {
|
||||
this.options.onEditPointCreate(this.graphic, dp, i);
|
||||
}
|
||||
this.editedPoints.push(dp);
|
||||
}
|
||||
@ -120,19 +150,33 @@ export class PolylineEditPlugin extends LineEditPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
export interface BezierCurveEditPointOptions extends IEditPointOptions {
|
||||
// 曲线控制点辅助连线颜色
|
||||
auxiliaryLineColor?: string;
|
||||
// // 拖拽点颜色
|
||||
// pointColor?: string;
|
||||
// 连接点处是否平滑(点左右控制点是否在一条直线),默认true
|
||||
smooth?: boolean;
|
||||
// 控制点是否完全对称(对称必平滑)
|
||||
symmetry?: boolean;
|
||||
}
|
||||
|
||||
export interface ICompleteBezierCurveEditPointOptions
|
||||
extends BezierCurveEditPointOptions {
|
||||
smooth: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 贝塞尔曲线编辑
|
||||
*/
|
||||
export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
static Name = 'bezier_curve_points_edit';
|
||||
options: ICompleteBezierCurveEditPointOptions;
|
||||
// 曲线控制点辅助线
|
||||
auxiliaryLines: Graphics[] = [];
|
||||
constructor(
|
||||
g: JlGraphic,
|
||||
linePoints: IPointData[],
|
||||
onEditPointCreate?: onEditPointCreate
|
||||
) {
|
||||
super(g, linePoints, onEditPointCreate);
|
||||
constructor(g: ILineGraphic, options?: BezierCurveEditPointOptions) {
|
||||
super(g);
|
||||
this.options = Object.assign({}, { smooth: true }, options);
|
||||
this.name = BezierCurveEditPlugin.Name;
|
||||
this.initEditPoints();
|
||||
}
|
||||
@ -143,6 +187,7 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
}
|
||||
|
||||
initEditPoints() {
|
||||
console.log('initEditPoints');
|
||||
const cps = this.graphic.localToCanvasPoints(...this.linePoints);
|
||||
for (let i = 0; i < cps.length; i++) {
|
||||
const p = cps[i];
|
||||
@ -160,37 +205,84 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
this.drawAuxiliaryLine(line, p, np);
|
||||
this.auxiliaryLines.push(line);
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
EditPointContextMenu.open(e.global);
|
||||
}
|
||||
});
|
||||
dp.on('dragmove', (de: GraphicDragEvent) => {
|
||||
const tlp = this.graphic.canvasToLocalPoint(dp.position);
|
||||
const cp = this.linePoints[i];
|
||||
cp.x = tlp.x;
|
||||
cp.y = tlp.y;
|
||||
if (c === 0 && !startOrEnd) {
|
||||
const fp = this.linePoints[i - 1];
|
||||
const np = this.linePoints[i + 1];
|
||||
fp.x = fp.x + de.dx;
|
||||
fp.y = fp.y + de.dy;
|
||||
np.x = np.x + de.dx;
|
||||
np.y = np.y + de.dy;
|
||||
} else if (c === 1 && i !== 1) {
|
||||
const bp = this.linePoints[i - 1];
|
||||
const fp2 = this.linePoints[i - 2];
|
||||
const distance = distance2(bp, fp2);
|
||||
const mp = calculateMirrorPoint(bp, cp, distance);
|
||||
fp2.x = mp.x;
|
||||
fp2.y = mp.y;
|
||||
} else if (c === 2 && i !== cps.length - 2) {
|
||||
const bp = this.linePoints[i + 1];
|
||||
const np2 = this.linePoints[i + 2];
|
||||
const distance = distance2(bp, np2);
|
||||
const mp = calculateMirrorPoint(bp, cp, distance);
|
||||
np2.x = mp.x;
|
||||
np2.y = mp.y;
|
||||
if (this.options.smooth || this.options.symmetry) {
|
||||
if (c === 0 && !startOrEnd) {
|
||||
const fp = this.linePoints[i - 1];
|
||||
const np = this.linePoints[i + 1];
|
||||
fp.x = fp.x + de.dx;
|
||||
fp.y = fp.y + de.dy;
|
||||
np.x = np.x + de.dx;
|
||||
np.y = np.y + de.dy;
|
||||
} else if (c === 1 && i !== 1) {
|
||||
const bp = this.linePoints[i - 1];
|
||||
const fp2 = this.linePoints[i - 2];
|
||||
let mp;
|
||||
if (this.options.symmetry) {
|
||||
mp = calculateMirrorPoint(bp, cp);
|
||||
} else {
|
||||
const distance = distance2(bp, fp2);
|
||||
mp = calculateMirrorPoint(bp, cp, distance);
|
||||
}
|
||||
fp2.x = mp.x;
|
||||
fp2.y = mp.y;
|
||||
} else if (c === 2 && i !== cps.length - 2) {
|
||||
const bp = this.linePoints[i + 1];
|
||||
const np2 = this.linePoints[i + 2];
|
||||
let mp;
|
||||
if (this.options.symmetry) {
|
||||
mp = calculateMirrorPoint(bp, cp);
|
||||
} else {
|
||||
const distance = distance2(bp, np2);
|
||||
mp = calculateMirrorPoint(bp, cp, distance);
|
||||
}
|
||||
np2.x = mp.x;
|
||||
np2.y = mp.y;
|
||||
}
|
||||
}
|
||||
this.graphic.repaint();
|
||||
});
|
||||
if (this.onEditPointCreate) {
|
||||
this.onEditPointCreate(this.graphic, dp, i);
|
||||
if (this.options.onEditPointCreate) {
|
||||
this.options.onEditPointCreate(this.graphic, dp, i);
|
||||
}
|
||||
this.editedPoints.push(dp);
|
||||
if (this.auxiliaryLines.length > 0) {
|
||||
@ -202,7 +294,11 @@ export class BezierCurveEditPlugin extends LineEditPlugin {
|
||||
|
||||
drawAuxiliaryLine(line: Graphics, p1: IPointData, p2: IPointData) {
|
||||
line.clear();
|
||||
line.lineStyle(1, new Color('blue'));
|
||||
if (this.options.auxiliaryLineColor) {
|
||||
line.lineStyle(1, new Color(this.options.auxiliaryLineColor));
|
||||
} else {
|
||||
line.lineStyle(1, new Color('blue'));
|
||||
}
|
||||
line.moveTo(p1.x, p1.y);
|
||||
line.lineTo(p2.x, p2.y);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Container, Graphics, Point, Rectangle, Text, utils } from 'pixi.js';
|
||||
import { Color, Container, Graphics, Point, Rectangle, Text } from 'pixi.js';
|
||||
import { GraphicApp } from '../app';
|
||||
import { OutOfBound } from '../utils';
|
||||
import {
|
||||
@ -18,41 +18,11 @@ export class ContextMenuPlugin {
|
||||
constructor(app: GraphicApp) {
|
||||
this.app = app;
|
||||
}
|
||||
/**
|
||||
* 添加菜单
|
||||
*/
|
||||
addContextMenu(menu: ContextMenu) {
|
||||
if (this.contextMenuMap.has(menu.menuName)) {
|
||||
console.log(`已经存在name=${menu.menuName}的菜单`);
|
||||
}
|
||||
|
||||
registerMenu(menu: ContextMenu) {
|
||||
this.contextMenuMap.set(menu.menuName, menu);
|
||||
menu.plugin = this;
|
||||
}
|
||||
/**
|
||||
* 通过菜单名称打开菜单到指定坐标
|
||||
* @param menuName
|
||||
* @param global
|
||||
*/
|
||||
openMenu(menuName: string, global: Point) {
|
||||
const menu = this.contextMenuMap.get(menuName);
|
||||
if (menu) {
|
||||
this.open(menu, global);
|
||||
} else {
|
||||
console.warn(`不存在name=${menuName}的菜单`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 通过菜单名称关闭菜单
|
||||
* @param menuName
|
||||
*/
|
||||
closeMenu(menuName: string) {
|
||||
const menu = this.contextMenuMap.get(menuName);
|
||||
if (menu) {
|
||||
this.close(menu);
|
||||
} else {
|
||||
console.warn(`不存在name=${menuName}的菜单`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视口屏幕宽度
|
||||
@ -131,13 +101,13 @@ export class ContextMenuPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* 上下文菜单
|
||||
* 上下文菜单,多级嵌套
|
||||
*/
|
||||
export class ContextMenu extends Container {
|
||||
_plugin?: ContextMenuPlugin;
|
||||
parentMenuItem?: ContextMenuItem;
|
||||
openedSubMenu?: ContextMenu;
|
||||
menuOptions: MenuCompletionOptions;
|
||||
_plugin?: ContextMenuPlugin;
|
||||
opened = false;
|
||||
bg: Graphics;
|
||||
title?: Text;
|
||||
@ -149,20 +119,32 @@ export class ContextMenu extends Container {
|
||||
constructor(menuOptions: MenuOptions, parentMenuItem?: ContextMenuItem) {
|
||||
super();
|
||||
this.menuOptions = Object.assign({}, DefaultWhiteMenuOptions, menuOptions);
|
||||
this.name = this.menuOptions.name;
|
||||
this.bg = new Graphics();
|
||||
this.addChild(this.bg);
|
||||
this.groups = [];
|
||||
this.init();
|
||||
this.parentMenuItem = parentMenuItem;
|
||||
}
|
||||
|
||||
static init(options: MenuOptions): ContextMenu {
|
||||
return new ContextMenu(options);
|
||||
}
|
||||
|
||||
public get style(): MenuCompletionStyleOptions {
|
||||
return this.menuOptions.style;
|
||||
}
|
||||
|
||||
/**
|
||||
* 父级菜单
|
||||
*/
|
||||
public get parentMenu(): ContextMenu | undefined {
|
||||
return this.parentMenuItem?.menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 最顶级菜单
|
||||
*/
|
||||
public get rootMenu(): ContextMenu {
|
||||
if (this.parentMenu) {
|
||||
return this.parentMenu.rootMenu;
|
||||
@ -226,7 +208,7 @@ export class ContextMenu extends Container {
|
||||
const options = this.menuOptions;
|
||||
let maxItemWidth = 0;
|
||||
let borderHeight = 0;
|
||||
options.groups.forEach(group => {
|
||||
options.groups.forEach((group) => {
|
||||
const menuGroup = new MenuGroup(this, group);
|
||||
this.groups.push(menuGroup);
|
||||
borderHeight += menuGroup.totalHeight;
|
||||
@ -246,10 +228,10 @@ export class ContextMenu extends Container {
|
||||
if (options.style.border) {
|
||||
this.bg.lineStyle(
|
||||
options.style.borderWidth,
|
||||
utils.string2hex(options.style.borderColor)
|
||||
new Color(options.style.borderColor)
|
||||
);
|
||||
}
|
||||
this.bg.beginFill(utils.string2hex(options.style.backgroundColor));
|
||||
this.bg.beginFill(new Color(options.style.backgroundColor));
|
||||
if (options.style.borderRoundRadius > 0) {
|
||||
this.bg.drawRoundedRect(
|
||||
0,
|
||||
@ -263,10 +245,7 @@ export class ContextMenu extends Container {
|
||||
}
|
||||
this.bg.endFill();
|
||||
let groupHeight = 0;
|
||||
this.bg.lineStyle(
|
||||
splitLineWidth,
|
||||
utils.string2hex(options.style.borderColor)
|
||||
);
|
||||
this.bg.lineStyle(splitLineWidth, new Color(options.style.borderColor));
|
||||
for (let i = 0; i < this.groups.length; i++) {
|
||||
const group = this.groups[i];
|
||||
group.updateItemBox(maxItemWidth);
|
||||
@ -281,7 +260,6 @@ export class ContextMenu extends Container {
|
||||
groupHeight = splitLineY + splitLineWidth;
|
||||
}
|
||||
|
||||
this.addChild(this.bg);
|
||||
this.addChild(...this.groups);
|
||||
|
||||
this.interactive = true;
|
||||
@ -293,12 +271,82 @@ export class ContextMenu extends Container {
|
||||
});
|
||||
}
|
||||
|
||||
initGroups() {
|
||||
this.groups = [];
|
||||
const options = this.menuOptions;
|
||||
options.groups.forEach((group) => {
|
||||
const menuGroup = new MenuGroup(this, group);
|
||||
this.groups.push(menuGroup);
|
||||
});
|
||||
this.addChild(...this.groups);
|
||||
}
|
||||
|
||||
initTitle() {
|
||||
if (this.menuOptions.title) {
|
||||
this.title = new Text(this.menuOptions.title, { align: 'left' });
|
||||
}
|
||||
}
|
||||
|
||||
updateBg() {
|
||||
const options = this.menuOptions;
|
||||
let maxItemWidth = 0;
|
||||
let borderHeight = 0;
|
||||
const splitLineWidth = 1;
|
||||
|
||||
this.groups.forEach((menuGroup) => {
|
||||
borderHeight += menuGroup.totalHeight;
|
||||
if (menuGroup.maxWidth > maxItemWidth) {
|
||||
maxItemWidth = menuGroup.maxWidth;
|
||||
}
|
||||
});
|
||||
|
||||
const bgWidth = maxItemWidth + this.padding * 2;
|
||||
const bgHeight =
|
||||
borderHeight +
|
||||
this.groups.length * this.padding * 2 +
|
||||
(this.groups.length - 1) * splitLineWidth;
|
||||
|
||||
if (options.style.border) {
|
||||
this.bg.lineStyle(
|
||||
options.style.borderWidth,
|
||||
new Color(options.style.borderColor)
|
||||
);
|
||||
}
|
||||
this.bg.beginFill(new Color(options.style.backgroundColor));
|
||||
if (options.style.borderRoundRadius > 0) {
|
||||
this.bg.drawRoundedRect(
|
||||
0,
|
||||
0,
|
||||
bgWidth,
|
||||
bgHeight,
|
||||
options.style.borderRoundRadius
|
||||
);
|
||||
} else {
|
||||
this.bg.drawRect(0, 0, bgWidth, bgHeight);
|
||||
}
|
||||
this.bg.endFill();
|
||||
let groupHeight = 0;
|
||||
this.bg.lineStyle(splitLineWidth, new Color(options.style.borderColor));
|
||||
for (let i = 0; i < this.groups.length; i++) {
|
||||
const group = this.groups[i];
|
||||
group.updateItemBox(maxItemWidth);
|
||||
group.position.set(this.padding, groupHeight + this.padding);
|
||||
if (i === this.groups.length - 1) {
|
||||
// 最后一个
|
||||
break;
|
||||
}
|
||||
const splitLineY = groupHeight + group.height + this.padding * 2;
|
||||
this.bg.moveTo(0, splitLineY);
|
||||
this.bg.lineTo(bgWidth, splitLineY);
|
||||
groupHeight = splitLineY + splitLineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
this.groups.forEach((group) => group.update());
|
||||
this.updateBg();
|
||||
}
|
||||
|
||||
public get menuName(): string {
|
||||
return this.menuOptions.name;
|
||||
}
|
||||
@ -324,6 +372,7 @@ export class ContextMenu extends Container {
|
||||
if (this.parentMenu) {
|
||||
this.parentMenu.openSub(this, global);
|
||||
} else {
|
||||
this.update();
|
||||
this.plugin.open(this, global);
|
||||
}
|
||||
}
|
||||
@ -370,7 +419,7 @@ class MenuGroup extends Container {
|
||||
super();
|
||||
this.config = config;
|
||||
this.menu = menu;
|
||||
config.items.forEach(item => {
|
||||
config.items.forEach((item) => {
|
||||
this.items.push(new ContextMenuItem(menu, item));
|
||||
});
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
@ -392,12 +441,12 @@ class MenuGroup extends Container {
|
||||
|
||||
public get maxWidth(): number {
|
||||
const maxNameWidth = this.items
|
||||
.map(item => item.nameBounds.width)
|
||||
.sort()
|
||||
.map((item) => item.nameBounds.width)
|
||||
.sort((a, b) => a - b)
|
||||
.reverse()[0];
|
||||
const maxShortcutWidth = this.items
|
||||
.map(item => item.shortcutKeyBounds.width)
|
||||
.sort()
|
||||
.map((item) => item.shortcutKeyBounds.width)
|
||||
.sort((a, b) => a - b)
|
||||
.reverse()[0];
|
||||
const maxWidth =
|
||||
maxNameWidth +
|
||||
@ -410,12 +459,18 @@ class MenuGroup extends Container {
|
||||
|
||||
public get totalHeight(): number {
|
||||
let total = 0;
|
||||
this.items.forEach(item => (total += item.totalHeight));
|
||||
this.items.forEach((item) => (total += item.totalHeight));
|
||||
return total;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.items.forEach((item) => item.update());
|
||||
}
|
||||
|
||||
updateItemBox(maxItemWidth: number) {
|
||||
this.items.forEach(item => item.updateBox(maxItemWidth, item.totalHeight));
|
||||
this.items.forEach((item) =>
|
||||
item.updateBox(maxItemWidth, item.totalHeight)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,7 +483,7 @@ class ContextMenuItem extends Container {
|
||||
/**
|
||||
* 名称文本
|
||||
*/
|
||||
nameText?: Text;
|
||||
nameText: Text;
|
||||
/**
|
||||
* 快捷键文本
|
||||
*/
|
||||
@ -446,7 +501,12 @@ class ContextMenuItem extends Container {
|
||||
this.box = new Graphics();
|
||||
this.addChild(this.box);
|
||||
|
||||
this.initNameText();
|
||||
this.nameText = new Text(this.config.name, {
|
||||
fontSize: this.fontSize,
|
||||
fill: this.fontColor,
|
||||
});
|
||||
this.addChild(this.nameText);
|
||||
|
||||
this.initShortcutKeyText();
|
||||
this.initSubMenu();
|
||||
}
|
||||
@ -553,15 +613,6 @@ class ContextMenuItem extends Container {
|
||||
return this.style.fontColor;
|
||||
}
|
||||
|
||||
initNameText(): Text {
|
||||
this.nameText = new Text(this.config.name, {
|
||||
fontSize: this.fontSize,
|
||||
fill: this.fontColor,
|
||||
});
|
||||
this.addChild(this.nameText);
|
||||
return this.nameText;
|
||||
}
|
||||
|
||||
initShortcutKeyText(): Text | undefined {
|
||||
if (this.config.shortcutKeys && this.config.shortcutKeys.length > 0) {
|
||||
this.shortcutKeyText = new Text(this.config.shortcutKeys.join('+'), {
|
||||
@ -603,7 +654,7 @@ class ContextMenuItem extends Container {
|
||||
if (this.style && this.style.hoverColor) {
|
||||
hoverColor = this.style.hoverColor;
|
||||
}
|
||||
box.beginFill(utils.string2hex(hoverColor));
|
||||
box.beginFill(new Color(hoverColor));
|
||||
if (style.borderRoundRadius > 0) {
|
||||
box.drawRoundedRect(0, 0, width, height, style.borderRoundRadius);
|
||||
} else {
|
||||
@ -665,4 +716,26 @@ class ContextMenuItem extends Container {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
this.nameText.text = this.config.name;
|
||||
this.nameText.style.fontSize = this.fontSize;
|
||||
this.nameText.style.fill = this.fontColor;
|
||||
|
||||
if (this.shortcutKeyText) {
|
||||
if (this.config.shortcutKeys && this.config.shortcutKeys.length > 0) {
|
||||
this.shortcutKeyText.text = this.config.shortcutKeys.join('+');
|
||||
this.shortcutKeyText.style.fontSize = this.fontSize;
|
||||
this.shortcutKeyText.style.fill = this.fontColor;
|
||||
} else {
|
||||
this.shortcutKeyText.visible = false;
|
||||
}
|
||||
} else {
|
||||
this.initShortcutKeyText();
|
||||
}
|
||||
|
||||
if (this.subMenu) {
|
||||
this.subMenu.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,10 @@ export interface MenuItemOptions {
|
||||
* 是否禁用,默认不禁用
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 是否显示,默认显示
|
||||
*/
|
||||
visible?: boolean;
|
||||
/**
|
||||
* 快捷键
|
||||
*/
|
||||
@ -89,10 +93,6 @@ export interface MenuItemOptions {
|
||||
* 点击处理
|
||||
*/
|
||||
onClick?: () => void;
|
||||
// /**
|
||||
// * 菜单项字体样式,覆盖MenuOptions中的通用样式
|
||||
// */
|
||||
// style?: MenuItemStyle;
|
||||
fontColor?: string;
|
||||
/**
|
||||
* 子菜单
|
||||
@ -109,10 +109,6 @@ export interface MenuItemStyle {
|
||||
* 字体颜色
|
||||
*/
|
||||
fontColor?: string;
|
||||
// /**
|
||||
// * 字体对齐
|
||||
// */
|
||||
// align?: TextStyleAlign;
|
||||
/**
|
||||
* hover颜色
|
||||
*/
|
||||
@ -127,8 +123,13 @@ export interface MenuItemStyle {
|
||||
padding: number[] | number; // 内边距
|
||||
}
|
||||
export interface MenuCompletionItemStyle extends MenuItemStyle {
|
||||
/**
|
||||
* 文字颜色
|
||||
*/
|
||||
fontColor: string;
|
||||
// align: TextStyleAlign;
|
||||
/**
|
||||
* 激活颜色
|
||||
*/
|
||||
hoverColor: string;
|
||||
/**
|
||||
* 禁用下字体颜色
|
||||
@ -157,7 +158,6 @@ export const DefaultWhiteStyleOptions: MenuCompletionStyleOptions = {
|
||||
fontSize: 16,
|
||||
fontColor: '#000000',
|
||||
padding: [5, 25],
|
||||
// align: 'left',
|
||||
hoverColor: '#1E78DB',
|
||||
disabledFontColor: '#9D9D9D',
|
||||
},
|
||||
|
@ -79,18 +79,18 @@ export function recursiveFindChild(
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface BezierPoints {
|
||||
export interface BezierParam {
|
||||
p1: IPointData;
|
||||
p2: IPointData;
|
||||
cp1: IPointData;
|
||||
cp2: IPointData;
|
||||
}
|
||||
|
||||
export function convertToBezierPoints(points: IPointData[]): BezierPoints[] {
|
||||
if (points.length < 4 && points.length % 3 !== 1) {
|
||||
export function convertToBezierParams(points: IPointData[]): BezierParam[] {
|
||||
if (points.length < 4 || points.length % 3 !== 1) {
|
||||
throw new Error(`bezierCurve 数据错误: ${points}`);
|
||||
}
|
||||
const bps: BezierPoints[] = [];
|
||||
const bps: BezierParam[] = [];
|
||||
for (let i = 0; i < points.length - 3; i += 3) {
|
||||
const p1 = new Point(points[i].x, points[i].y);
|
||||
const p2 = new Point(points[i + 3].x, points[i + 3].y);
|
||||
@ -106,6 +106,69 @@ export function convertToBezierPoints(points: IPointData[]): BezierPoints[] {
|
||||
return bps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分段数计算贝塞尔曲线所有点坐标
|
||||
* @param basePoints
|
||||
* @param segmentsCount
|
||||
* @returns
|
||||
*/
|
||||
export function calculateBezierPoints(
|
||||
basePoints: IPointData[],
|
||||
segmentsCount: number
|
||||
): Point[] {
|
||||
const bps = convertToBezierParams(basePoints);
|
||||
const points: Point[] = [];
|
||||
bps.forEach((bp) => {
|
||||
points.push(
|
||||
...calculateOneBezierPoints(bp.p1, bp.p2, bp.cp1, bp.cp2, segmentsCount)
|
||||
);
|
||||
});
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分段数计算贝塞尔曲线所有点坐标
|
||||
* @param basePoints
|
||||
* @param segmentsCount
|
||||
* @returns
|
||||
*/
|
||||
export function calculateOneBezierPoints(
|
||||
p1: IPointData,
|
||||
p2: IPointData,
|
||||
cp1: IPointData,
|
||||
cp2: IPointData,
|
||||
segmentsCount: number
|
||||
): Point[] {
|
||||
const points: Point[] = [];
|
||||
const fromX = p1.x;
|
||||
const fromY = p1.y;
|
||||
const n = segmentsCount;
|
||||
let dt = 0;
|
||||
let dt2 = 0;
|
||||
let dt3 = 0;
|
||||
let t2 = 0;
|
||||
let t3 = 0;
|
||||
const cpX = cp1.x;
|
||||
const cpY = cp1.y;
|
||||
const cpX2 = cp2.x;
|
||||
const cpY2 = cp2.y;
|
||||
const toX = p2.x;
|
||||
const toY = p2.y;
|
||||
points.push(new Point(p1.x, p1.y));
|
||||
for (let i = 1, j = 0; i <= n; ++i) {
|
||||
j = i / n;
|
||||
dt = 1 - j;
|
||||
dt2 = dt * dt;
|
||||
dt3 = dt2 * dt;
|
||||
t2 = j * j;
|
||||
t3 = t2 * j;
|
||||
const px = dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX;
|
||||
const py = dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY;
|
||||
points.push(new Point(px, py));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算矩形中点
|
||||
*/
|
||||
@ -367,7 +430,7 @@ export function calculateIntersectionPointOfCircleAndPoint(
|
||||
* 计算点基于点的镜像点坐标
|
||||
* @param bp 基准点
|
||||
* @param p 待镜像的点坐标
|
||||
* @param distance 镜像点到基准点的距离,默认为p到基准点的距离
|
||||
* @param distance 镜像点到基准点的距离,默认为p到基准点的距离,即对称
|
||||
* @returns
|
||||
*/
|
||||
export function calculateMirrorPoint(
|
||||
|
@ -25,13 +25,21 @@ onMounted(() => {
|
||||
drawApp = new JlDrawApp(dom);
|
||||
initDrawApp(drawApp);
|
||||
loadDrawDatas(drawApp);
|
||||
window.addEventListener('resize', onResize);
|
||||
}
|
||||
});
|
||||
|
||||
function onResize() {
|
||||
if (drawApp) {
|
||||
drawApp.onDomResize(document.body.clientWidth, document.body.clientHeight);
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (drawApp) {
|
||||
drawApp.destroy();
|
||||
}
|
||||
window.removeEventListener('resize', onResize);
|
||||
document.body.style.overflow = 'auto';
|
||||
});
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user