添加箭头
This commit is contained in:
parent
383c97e305
commit
f962070209
@ -33,6 +33,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import CanvasProperty from './properties/CanvasProperty.vue';
|
||||
|
||||
const drawStore = useDrawStore();
|
||||
</script>
|
||||
|
80
src/components/draw-app/properties/CanvasProperty.vue
Normal file
80
src/components/draw-app/properties/CanvasProperty.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<q-form>
|
||||
<q-input
|
||||
outlined
|
||||
v-model.number="canvas.width"
|
||||
@blur="onUpdate"
|
||||
label="画布宽 *"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val > 0) || '画布宽必须大于0']"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
type="number"
|
||||
v-model.number="canvas.height"
|
||||
@blur="onUpdate"
|
||||
label="画布高 *"
|
||||
lazy-rules
|
||||
:rules="[(val) => val > 0 || '画布高必须大于0']"
|
||||
/>
|
||||
|
||||
<q-input
|
||||
outlined
|
||||
v-model="canvas.backgroundColor"
|
||||
@blur="onUpdate"
|
||||
label="画布背景色 *"
|
||||
lazy-rules
|
||||
:rules="[(val) => (val && val.length > 0) || '画布背景色必须设置']"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<q-icon name="colorize" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-color
|
||||
:model-value="canvas.backgroundColor"
|
||||
@change="
|
||||
(val) => {
|
||||
canvas.backgroundColor = val;
|
||||
onUpdate();
|
||||
}
|
||||
"
|
||||
/>
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</q-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { onMounted, onUnmounted, reactive } from 'vue';
|
||||
const drawStore = useDrawStore();
|
||||
|
||||
const canvas = reactive({
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
backgroundColor: '#ffffff',
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('画布属性表单mounted');
|
||||
const jc = drawStore.getJlCanvas();
|
||||
canvas.width = jc.properties.width;
|
||||
canvas.height = jc.properties.height;
|
||||
canvas.backgroundColor = jc.properties.backgroundColor;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// console.log('画布属性表单unmounted');
|
||||
});
|
||||
|
||||
function onUpdate() {
|
||||
// console.log('画布属性更新');
|
||||
const app = drawStore.getDrawApp();
|
||||
app.updateCanvasAndRecord({
|
||||
...canvas,
|
||||
viewportTransform: app.canvas.properties.viewportTransform,
|
||||
});
|
||||
}
|
||||
</script>
|
@ -13,6 +13,9 @@ import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { iscsGraphicData } from 'src/protos/iscs_graphic_data';
|
||||
import { toStorageTransform } from './graphics/GraphicDataBase';
|
||||
import { fromUint8Array } from 'js-base64';
|
||||
import { Arrow, ArrowTemplate } from 'src/graphics/arrow/Arrow';
|
||||
import { ArrowData } from './graphics/ArrowInteraction';
|
||||
import { ArrowDraw } from 'src/graphics/arrow/ArrowDrawAssistant';
|
||||
|
||||
// import { Notify } from 'quasar';
|
||||
|
||||
@ -82,23 +85,24 @@ export function initDrawApp(): IDrawApp {
|
||||
isSupportDeletion: isSupportDeletion,
|
||||
});
|
||||
const app = drawApp;
|
||||
// app.canvas.on('_rightclick', (e) => {
|
||||
// if (app.drawing) return;
|
||||
// UndoOptions.disabled = !app.opRecord.hasUndo;
|
||||
// RedoOptions.disabled = !app.opRecord.hasRedo;
|
||||
new ArrowDraw(drawApp, new ArrowTemplate(new ArrowData()));
|
||||
app.canvas.on('_rightclick', (e) => {
|
||||
if (app.drawing) return;
|
||||
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.on('destroy', async () => {});
|
||||
UndoOptions.handler = () => {
|
||||
app.opRecord.undo();
|
||||
};
|
||||
RedoOptions.handler = () => {
|
||||
app.opRecord.redo();
|
||||
};
|
||||
SelectAllOptions.handler = () => {
|
||||
app.selectAllGraphics();
|
||||
};
|
||||
DefaultCanvasMenu.open(e.global);
|
||||
});
|
||||
app.on('destroy', async () => {});
|
||||
app.addKeyboardListener(
|
||||
new KeyListener({
|
||||
value: 'KeyS',
|
||||
@ -136,6 +140,10 @@ export function saveDrawDatas(app: IDrawApp) {
|
||||
viewportTransform: toStorageTransform(canvasData.viewportTransform),
|
||||
});
|
||||
const graphics = app.queryStore.getAllGraphics();
|
||||
if (Arrow.Type === g.type) {
|
||||
const arrowData = (g as Arrow).saveData();
|
||||
storage.arrows.push((arrowData as ArrowData).data);
|
||||
}
|
||||
console.log(storage, '保存数据', graphics);
|
||||
const base64 = fromUint8Array(storage.serialize());
|
||||
return base64;
|
||||
|
48
src/drawApp/graphics/ArrowInteraction.ts
Normal file
48
src/drawApp/graphics/ArrowInteraction.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import * as pb_1 from 'google-protobuf';
|
||||
// import { IArrowData, Arrow } from 'src/graphics/arrow/Arrow';
|
||||
import { iscsGraphicData } from 'src/protos/iscs_graphic_data';
|
||||
import { IArrowData, Arrow } from 'src/graphics/arrow/Arrow';
|
||||
import { GraphicDataBase } from './GraphicDataBase';
|
||||
import { IPointData } from 'pixi.js';
|
||||
|
||||
export class ArrowData extends GraphicDataBase implements IArrowData {
|
||||
constructor(data?: iscsGraphicData.Arrow) {
|
||||
let arrow;
|
||||
if (data) {
|
||||
arrow = data;
|
||||
} else {
|
||||
arrow = new iscsGraphicData.Arrow({
|
||||
common: GraphicDataBase.defaultCommonInfo(Arrow.Type),
|
||||
});
|
||||
}
|
||||
super(arrow);
|
||||
}
|
||||
|
||||
public get data(): iscsGraphicData.Arrow {
|
||||
return this.getData<iscsGraphicData.Arrow>();
|
||||
}
|
||||
|
||||
get code(): string {
|
||||
return this.data.code;
|
||||
}
|
||||
set code(v: string) {
|
||||
this.data.code = v;
|
||||
}
|
||||
get points(): IPointData[] {
|
||||
return this.data.points;
|
||||
}
|
||||
set points(points: IPointData[]) {
|
||||
this.data.points = points.map(
|
||||
(p) => new iscsGraphicData.Point({ x: p.x, y: p.y })
|
||||
);
|
||||
}
|
||||
clone(): ArrowData {
|
||||
return new ArrowData(this.data.cloneMessage());
|
||||
}
|
||||
copyFrom(data: ArrowData): void {
|
||||
pb_1.Message.copyInto(data.data, this.data);
|
||||
}
|
||||
eq(other: ArrowData): boolean {
|
||||
return pb_1.Message.equals(this.data, other.data);
|
||||
}
|
||||
}
|
92
src/graphics/arrow/Arrow.ts
Normal file
92
src/graphics/arrow/Arrow.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Graphics, IPointData } from 'pixi.js';
|
||||
import {
|
||||
GraphicData,
|
||||
JlGraphic,
|
||||
JlGraphicTemplate,
|
||||
ILineGraphic,
|
||||
} from 'jl-graphic';
|
||||
|
||||
export interface IArrowData extends GraphicData {
|
||||
get code(): string; // 编号
|
||||
set code(v: string);
|
||||
get points(): IPointData[]; // 线坐标点
|
||||
set points(points: IPointData[]);
|
||||
clone(): IArrowData;
|
||||
copyFrom(data: IArrowData): void;
|
||||
eq(other: IArrowData): boolean;
|
||||
}
|
||||
|
||||
export const ArrowConsts = {
|
||||
lineColor: '#0000CD',
|
||||
lineWidth: 5,
|
||||
};
|
||||
|
||||
export class Arrow extends JlGraphic implements ILineGraphic {
|
||||
static Type = 'Arrow';
|
||||
lineGraphic: Graphics;
|
||||
arrowGraphic: Graphics;
|
||||
transformSave: boolean;
|
||||
|
||||
constructor() {
|
||||
super(Arrow.Type);
|
||||
this.lineGraphic = new Graphics();
|
||||
this.arrowGraphic = new Graphics();
|
||||
this.transformSave = true;
|
||||
this.addChild(this.lineGraphic);
|
||||
this.addChild(this.arrowGraphic);
|
||||
}
|
||||
|
||||
doRepaint() {
|
||||
if (this.datas.points.length < 2) {
|
||||
throw new Error('Arrow坐标数据异常');
|
||||
}
|
||||
|
||||
this.lineGraphic.clear();
|
||||
this.lineGraphic.lineStyle(ArrowConsts.lineWidth, ArrowConsts.lineColor);
|
||||
const p1 = this.datas.points[0];
|
||||
const p2 = this.datas.points[1];
|
||||
this.lineGraphic.moveTo(p1.x, p1.y);
|
||||
this.lineGraphic.lineTo(p2.x, p2.y);
|
||||
this.arrowGraphic.clear();
|
||||
this.arrowGraphic.beginFill(ArrowConsts.lineColor, 1);
|
||||
if (this.arrowGraphic.drawRegularPolygon) {
|
||||
let roation = Math.PI / 2;
|
||||
const angle = Math.atan2(p1.y - p2.y, p1.x - p2.x);
|
||||
if (angle > Math.PI / 2) {
|
||||
roation = angle - Math.PI / 2;
|
||||
} else if (angle <= Math.PI / 2 && angle > 0) {
|
||||
roation = Math.PI / 2 - angle;
|
||||
} else {
|
||||
roation = angle - Math.PI / 2;
|
||||
}
|
||||
this.arrowGraphic.drawRegularPolygon(p2.x, p2.y, 10, 3, roation);
|
||||
}
|
||||
this.arrowGraphic.endFill();
|
||||
}
|
||||
|
||||
get code(): string {
|
||||
return this.datas.code;
|
||||
}
|
||||
|
||||
get datas(): IArrowData {
|
||||
return this.getDatas<IArrowData>();
|
||||
}
|
||||
|
||||
get linePoints(): IPointData[] {
|
||||
return this.datas.points;
|
||||
}
|
||||
set linePoints(points: IPointData[]) {
|
||||
const old = this.datas.clone();
|
||||
old.points = points;
|
||||
this.updateData(old);
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrowTemplate extends JlGraphicTemplate<Arrow> {
|
||||
constructor(dataTemplate: IArrowData) {
|
||||
super(Arrow.Type, { dataTemplate });
|
||||
}
|
||||
new() {
|
||||
return new Arrow();
|
||||
}
|
||||
}
|
204
src/graphics/arrow/ArrowDrawAssistant.ts
Normal file
204
src/graphics/arrow/ArrowDrawAssistant.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import {
|
||||
DraggablePoint,
|
||||
IGraphicApp,
|
||||
GraphicDrawAssistant,
|
||||
GraphicInteractionPlugin,
|
||||
GraphicTransformEvent,
|
||||
IDrawApp,
|
||||
JlGraphic,
|
||||
linePoint,
|
||||
AbsorbablePosition,
|
||||
AbsorbableLine,
|
||||
ILineGraphic,
|
||||
PolylineEditPlugin,
|
||||
} from 'jl-graphic';
|
||||
import { IArrowData, Arrow, ArrowConsts, ArrowTemplate } from './Arrow';
|
||||
import {
|
||||
DisplayObject,
|
||||
FederatedMouseEvent,
|
||||
Graphics,
|
||||
IHitArea,
|
||||
Point,
|
||||
} from 'pixi.js';
|
||||
|
||||
export class ArrowDraw extends GraphicDrawAssistant<ArrowTemplate, IArrowData> {
|
||||
points: Point[] = [];
|
||||
lineGraphic = new Graphics();
|
||||
arrowGraphic = new Graphics();
|
||||
|
||||
constructor(app: IDrawApp, template: ArrowTemplate) {
|
||||
super(app, template, 'call_made', '箭头Arrow');
|
||||
this.container.addChild(this.lineGraphic);
|
||||
this.container.addChild(this.arrowGraphic);
|
||||
|
||||
ArrowPointEditPlugin.init(app, this);
|
||||
}
|
||||
|
||||
bind(): void {
|
||||
super.bind();
|
||||
}
|
||||
unbind(): void {
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
onLeftDown(e: FederatedMouseEvent): void {
|
||||
const { x, y } = this.toCanvasCoordinates(e.global);
|
||||
const p = new Point(x, y);
|
||||
this.points.push(p);
|
||||
if (this.points.length === 2) {
|
||||
this.createAndStore(true);
|
||||
}
|
||||
}
|
||||
|
||||
onRightClick(): void {
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
onEsc(): void {
|
||||
this.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
redraw(p: Point): void {
|
||||
if (this.points.length < 1) return;
|
||||
const p1 = this.points[0];
|
||||
this.lineGraphic.clear();
|
||||
this.lineGraphic.lineStyle(ArrowConsts.lineWidth, ArrowConsts.lineColor);
|
||||
this.lineGraphic.moveTo(p1.x, p1.y);
|
||||
this.lineGraphic.lineTo(p.x, p.y);
|
||||
this.arrowGraphic.clear();
|
||||
this.arrowGraphic.beginFill(ArrowConsts.lineColor, 1);
|
||||
if (this.arrowGraphic.drawRegularPolygon) {
|
||||
let roation = Math.PI / 2;
|
||||
const angle = Math.atan2(p1.y - p.y, p1.x - p.x);
|
||||
if (angle > Math.PI / 2) {
|
||||
roation = angle - Math.PI / 2;
|
||||
} else if (angle <= Math.PI / 2 && angle >= 0) {
|
||||
roation = Math.PI / 2 - angle;
|
||||
} else {
|
||||
roation = angle - Math.PI / 2;
|
||||
}
|
||||
this.arrowGraphic.drawRegularPolygon(p.x, p.y, 10, 3, roation);
|
||||
}
|
||||
this.arrowGraphic.endFill();
|
||||
}
|
||||
|
||||
prepareData(data: IArrowData): boolean {
|
||||
if (this.points.length < 2) {
|
||||
console.log('Arrow绘制因点不够取消绘制');
|
||||
return false;
|
||||
}
|
||||
data.points = this.points;
|
||||
return true;
|
||||
}
|
||||
|
||||
clearCache(): void {
|
||||
this.points = [];
|
||||
this.lineGraphic.clear();
|
||||
this.arrowGraphic.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class ArrowGraphicHitArea implements IHitArea {
|
||||
arrow: Arrow;
|
||||
constructor(arrow: Arrow) {
|
||||
this.arrow = arrow;
|
||||
}
|
||||
contains(x: number, y: number): boolean {
|
||||
const p1 = this.arrow.datas.points[0];
|
||||
const p2 = this.arrow.datas.points[1];
|
||||
if (linePoint(p1, p2, { x, y }, ArrowConsts.lineWidth)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildAbsorbablePositions(arrow: Arrow): AbsorbablePosition[] {
|
||||
const aps: AbsorbablePosition[] = [];
|
||||
|
||||
const arrows = arrow.queryStore.queryByType<Arrow>(Arrow.Type);
|
||||
const canvas = arrow.getCanvas();
|
||||
arrows.forEach((other) => {
|
||||
if (other.id === arrow.id) {
|
||||
return;
|
||||
}
|
||||
const [p1, p2] = [
|
||||
other.localToCanvasPoint(other.datas.points[0]),
|
||||
other.localToCanvasPoint(other.datas.points[1]),
|
||||
];
|
||||
const ala = new AbsorbableLine(
|
||||
new Point(p1.x, 0),
|
||||
new Point(p1.x, canvas.height)
|
||||
);
|
||||
aps.push(ala);
|
||||
const alb = new AbsorbableLine(
|
||||
new Point(p2.x, 0),
|
||||
new Point(p2.x, canvas.height)
|
||||
);
|
||||
aps.push(alb);
|
||||
});
|
||||
return aps;
|
||||
}
|
||||
|
||||
function onEditPointCreate(g: ILineGraphic, dp: DraggablePoint): void {
|
||||
const arrow = g as Arrow;
|
||||
dp.on('transformstart', (e: GraphicTransformEvent) => {
|
||||
if (e.isShift()) {
|
||||
arrow.getGraphicApp().setOptions({
|
||||
absorbablePositions: buildAbsorbablePositions(arrow),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class ArrowPointEditPlugin extends GraphicInteractionPlugin<Arrow> {
|
||||
static Name = 'ArrowPointDrag';
|
||||
drawAssistant: ArrowDraw;
|
||||
|
||||
constructor(app: IGraphicApp, da: ArrowDraw) {
|
||||
super(ArrowPointEditPlugin.Name, app);
|
||||
this.drawAssistant = da;
|
||||
}
|
||||
static init(app: IGraphicApp, da: ArrowDraw) {
|
||||
return new ArrowPointEditPlugin(app, da);
|
||||
}
|
||||
filter(...grahpics: JlGraphic[]): Arrow[] | undefined {
|
||||
return grahpics.filter((g) => g.type == Arrow.Type) as Arrow[];
|
||||
}
|
||||
bind(g: Arrow): void {
|
||||
g.eventMode = 'static';
|
||||
g.cursor = 'pointer';
|
||||
g.hitArea = new ArrowGraphicHitArea(g);
|
||||
g.transformSave = true;
|
||||
g.on('selected', this.onSelected, this);
|
||||
g.on('unselected', this.onUnselected, this);
|
||||
}
|
||||
unbind(g: Arrow): void {
|
||||
g.off('selected', this.onSelected, this);
|
||||
g.off('unselected', this.onUnselected, this);
|
||||
}
|
||||
|
||||
onContextMenu(e: FederatedMouseEvent) {
|
||||
const target = e.target as DisplayObject;
|
||||
const arrow = target.getGraphic() as Arrow;
|
||||
this.app.updateSelected(arrow);
|
||||
}
|
||||
|
||||
onSelected(g: DisplayObject): void {
|
||||
const arrow = g as Arrow;
|
||||
const lep = new PolylineEditPlugin(arrow, { onEditPointCreate });
|
||||
arrow.addAssistantAppend(lep);
|
||||
lep.showAll();
|
||||
}
|
||||
onUnselected(g: DisplayObject): void {
|
||||
const arrow = g as Arrow;
|
||||
const lep = arrow.getAssistantAppend<PolylineEditPlugin>(
|
||||
PolylineEditPlugin.Name
|
||||
);
|
||||
if (lep) {
|
||||
lep.hideAll();
|
||||
}
|
||||
}
|
||||
}
|
@ -111,6 +111,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { successNotify } from 'src/utils/CommonNotify';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useDrawStore } from 'src/stores/draw-store';
|
||||
import { Arrow } from 'src/graphics/arrow/Arrow';
|
||||
|
||||
const $q = useQuasar();
|
||||
const route = useRoute();
|
||||
@ -195,7 +196,7 @@ onMounted(() => {
|
||||
} else {
|
||||
drawStore.setDraftId(null);
|
||||
}
|
||||
const drawAssistantsTypes = [];
|
||||
const drawAssistantsTypes = [Arrow.Type];
|
||||
drawAssistantsTypes.forEach((type) => {
|
||||
const drawAssistant = drawStore.getDrawApp().getDrawAssistant(type);
|
||||
if (drawAssistant) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { QTable } from 'quasar';
|
||||
import { initDrawApp, destroyDrawApp } from 'src/drawApp/drawApp';
|
||||
import { initDrawApp, destroyDrawApp, getDrawApp } from 'src/drawApp/drawApp';
|
||||
import {
|
||||
DrawAssistant,
|
||||
GraphicData,
|
||||
@ -23,7 +23,9 @@ export const useDrawStore = defineStore('draw', {
|
||||
drawGraphicType: (state) => state.drawAssistant?.type,
|
||||
drawGraphicName: (state) => state.drawAssistant?.description,
|
||||
drawGraphicTemplate: (state) => state.drawAssistant?.graphicTemplate,
|
||||
|
||||
getApp: () => {
|
||||
return getDrawApp();
|
||||
},
|
||||
selectedGraphicType: (state) => {
|
||||
if (state.selectedGraphics) {
|
||||
if (state.selectedGraphics.length === 1) {
|
||||
@ -103,6 +105,5 @@ export const useDrawStore = defineStore('draw', {
|
||||
setDraftId(id: number | null) {
|
||||
this.draftId = id;
|
||||
},
|
||||
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user