绘制形状
This commit is contained in:
@@ -42,11 +42,13 @@
|
||||
const toolManager = inject('toolManager') as any
|
||||
const objectManager = inject('objectManager') as any
|
||||
const canvasManager = inject('canvasManager') as any
|
||||
const layerManager = inject('layerManager') as any
|
||||
const tool = computed(() => toolManager.currentTool.value)
|
||||
const historyIndex = computed(() => stateManager.historyIndex.value)
|
||||
const historyList = computed(() => stateManager.historyList.value)
|
||||
const isUndo = computed(() => !historyList.value[historyIndex.value - 1])
|
||||
const isRedo = computed(() => !historyList.value[historyIndex.value + 1])
|
||||
const activeLayerId = computed(() => layerManager.activeID.value)
|
||||
const tools = ref([
|
||||
{ name: OperationType.SELECT, icon: 'dc-select', iconSize: 16, disabled: ref(false) },
|
||||
{ name: OperationType.PAN, icon: 'dc-move', iconSize: 18, disabled: ref(false) },
|
||||
@@ -60,7 +62,42 @@
|
||||
on: () => onImageClick()
|
||||
},
|
||||
{ name: OperationType.SELECTBOX, icon: 'dc-selectbox', iconSize: 16, disabled: ref(false) },
|
||||
{ name: OperationType.RECTANGLE, icon: 'dc-rectangle', iconSize: 16, disabled: ref(false) },
|
||||
{
|
||||
name: OperationType.RECTANGLE,
|
||||
icon: 'dc-rectangle',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{
|
||||
name: OperationType.LINE,
|
||||
icon: 'dc-line',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{
|
||||
name: OperationType.ARROW,
|
||||
icon: 'dc-arrow',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{
|
||||
name: OperationType.ELLIPSE,
|
||||
icon: 'dc-ellipse',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{
|
||||
name: OperationType.TRIANGLE,
|
||||
icon: 'dc-triangle',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{
|
||||
name: OperationType.STAR,
|
||||
icon: 'dc-star',
|
||||
iconSize: 16,
|
||||
disabled: ref(false)
|
||||
},
|
||||
{ type: 'line' },
|
||||
{
|
||||
name: OperationType.UNDO,
|
||||
@@ -75,6 +112,20 @@
|
||||
iconSize: 18,
|
||||
disabled: isRedo,
|
||||
on: () => stateManager.redoState()
|
||||
},
|
||||
{
|
||||
name: 'copy',
|
||||
icon: 'dc-copy',
|
||||
iconSize: 16,
|
||||
disabled: computed(() => !activeLayerId.value),
|
||||
on: () => onCopyActiveLayer()
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
icon: 'dc-delete',
|
||||
iconSize: 18,
|
||||
disabled: computed(() => !activeLayerId.value),
|
||||
on: () => onDeleteActiveLayer()
|
||||
}
|
||||
])
|
||||
const onClickTool = (tool: any) => {
|
||||
@@ -91,6 +142,14 @@
|
||||
objectManager.setBlendMode(id, BlendMode.MULTIPLY)
|
||||
objectManager.setFillRepeat(id)
|
||||
}
|
||||
const onCopyActiveLayer = () => {
|
||||
if (!activeLayerId.value) return
|
||||
layerManager.copyLayerById(activeLayerId.value)
|
||||
}
|
||||
const onDeleteActiveLayer = () => {
|
||||
if (!activeLayerId.value) return
|
||||
layerManager.deleteLayerById(activeLayerId.value)
|
||||
}
|
||||
const onWorkbench = async () => {
|
||||
exportCanvasToImage(canvasManager.canvas).then((url) => {
|
||||
emit('workbench', { url })
|
||||
|
||||
@@ -33,6 +33,19 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Opacity</div>
|
||||
<div class="value">
|
||||
<depth-slider
|
||||
v-model="opacity"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:tipFormatter="(v) => v + '%'"
|
||||
@input="inputOpacity"
|
||||
@change="changeOpacity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Gap X</div>
|
||||
<div class="value">
|
||||
@@ -101,6 +114,7 @@
|
||||
const gapX = ref(0)
|
||||
const gapY = ref(0)
|
||||
const offset = ref({ x: 0, y: 0 })
|
||||
const opacity = ref(100)
|
||||
|
||||
const updateData = async () => {
|
||||
await nextTick()
|
||||
@@ -113,6 +127,7 @@
|
||||
x: Math.round((fill.offsetX / props.object.width) * 100),
|
||||
y: Math.round((fill.offsetY / props.object.height) * 100)
|
||||
}
|
||||
opacity.value = Math.round(props.object.opacity * 100)
|
||||
}
|
||||
updateData()
|
||||
|
||||
@@ -138,6 +153,15 @@
|
||||
objectManager.updateFillRepeatGap(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
const inputOpacity = () => setOpacity(false)
|
||||
const changeOpacity = () => setOpacity(true)
|
||||
const setOpacity = (isRecord: boolean) => {
|
||||
const options = {
|
||||
opacity: opacity.value / 100
|
||||
}
|
||||
objectManager.updateOpacity(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
stateManager.event.add('canvas:undo', updateData)
|
||||
stateManager.event.add('canvas:redo', updateData)
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
><svg-icon :name="layer.visible ? 'dc-show' : 'dc-hide'" size="15"
|
||||
/></span>
|
||||
<span @click.stop="onClickDelete"><svg-icon name="dc-delete" size="13" /></span>
|
||||
<span><svg-icon name="dc-down_arrow" size="11" /></span>
|
||||
<!-- <span><svg-icon name="dc-down_arrow" size="11" /></span> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
layerManager.dragSort(data.info.id, newIndex)
|
||||
}
|
||||
const addLayer = () => {
|
||||
layerManager.createEmptyLayer()
|
||||
layerManager.createEmptyLayer(true, true)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { fabric } from 'fabric-with-all'
|
||||
import { createId } from '../../tools/tools'
|
||||
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
|
||||
|
||||
export class LayerManager {
|
||||
stateManager: any
|
||||
@@ -53,6 +54,7 @@ export class LayerManager {
|
||||
this.canvasManager.renderAll()
|
||||
}
|
||||
}
|
||||
/** 删除指定图层 */
|
||||
deleteLayerById(id, isActive = true) {
|
||||
this.canvasManager.deleteObjectById(id)
|
||||
if (id === this.activeID.value && isActive) {
|
||||
@@ -60,6 +62,24 @@ export class LayerManager {
|
||||
}
|
||||
if (isActive) this.stateManager.recordState()
|
||||
}
|
||||
/** 复制指定图层 */
|
||||
copyLayerById(id) {
|
||||
const object = this.canvasManager.getObjectById(id)
|
||||
if (!object) return console.warn('复制图层失败,对象不存在ID:', id)
|
||||
cloneObjects([object]).then(objects => {
|
||||
const newObject = objects[0]
|
||||
const info = JSON.parse(JSON.stringify(newObject.info))
|
||||
info.id = createId("image")
|
||||
// info.name = info.name
|
||||
newObject.set({
|
||||
top: newObject.top + 15,
|
||||
left: newObject.left + 15,
|
||||
info: info,
|
||||
})
|
||||
this.canvasManager.add(newObject)
|
||||
this.setActiveID(newObject.info.id)
|
||||
})
|
||||
}
|
||||
// 拖拽排序
|
||||
dragSort(id, newIndex) {
|
||||
const index = Math.abs(this.layers.value.length - newIndex - 1)
|
||||
@@ -90,7 +110,7 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
/** 创建空图层 */
|
||||
createEmptyLayer(isRecord = true) {
|
||||
createEmptyLayer(isRecord = true, isActive = false) {
|
||||
const emptyObject = new fabric.Rect({
|
||||
width: 0,
|
||||
height: 0,
|
||||
@@ -102,6 +122,7 @@ export class LayerManager {
|
||||
})
|
||||
this.setLayerPosition(emptyObject)
|
||||
this.canvasManager.add(emptyObject, isRecord)
|
||||
if (isActive) this.setActiveID(emptyObject.info.id, false)
|
||||
return emptyObject
|
||||
}
|
||||
/** 创建文本图层 */
|
||||
@@ -138,23 +159,109 @@ export class LayerManager {
|
||||
if (isActive) this.setActiveID(rectObject.info.id)
|
||||
return rectObject
|
||||
}
|
||||
/** 创建圆形图层 */
|
||||
async createCircleLayer(options?: any, isActive = false) {
|
||||
const circleObject = new fabric.Circle({
|
||||
/** 创建直线图层 */
|
||||
async createLineLayer(options?: any, isActive = false) {
|
||||
const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0]
|
||||
delete options.x1
|
||||
delete options.y1
|
||||
delete options.x2
|
||||
delete options.y2
|
||||
const lineObject = new fabric.Line(line, {
|
||||
stroke: 'black', // 线条颜色
|
||||
strokeWidth: 2, // 线条粗细
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("line"),
|
||||
name: '直线图层',
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(lineObject, options)
|
||||
await this.canvasManager.add(lineObject)
|
||||
if (isActive) this.setActiveID(lineObject.info.id)
|
||||
return lineObject
|
||||
}
|
||||
/** 创建椭圆图层 */
|
||||
async createEllipseLayer(options?: any, isActive = false) {
|
||||
const ellipseObject = new fabric.Ellipse({
|
||||
radius: 50,
|
||||
fill: '#000',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("circle"),
|
||||
name: '圆形图层',
|
||||
id: createId("ellipse"),
|
||||
name: '椭圆图层',
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(circleObject, options)
|
||||
await this.canvasManager.add(circleObject)
|
||||
if (isActive) this.setActiveID(circleObject.info.id)
|
||||
return circleObject
|
||||
this.setLayerPosition(ellipseObject, options)
|
||||
await this.canvasManager.add(ellipseObject)
|
||||
if (isActive) this.setActiveID(ellipseObject.info.id)
|
||||
return ellipseObject
|
||||
}
|
||||
/** 创建三角形图层 */
|
||||
async createTriangleLayer(options?: any, isActive = false) {
|
||||
const triangleObject = new fabric.Triangle({
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#000',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("triangle"),
|
||||
name: '三角形图层',
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(triangleObject, options)
|
||||
await this.canvasManager.add(triangleObject)
|
||||
if (isActive) this.setActiveID(triangleObject.info.id)
|
||||
return triangleObject
|
||||
}
|
||||
/** 创建五角星图层 */
|
||||
async createStarLayer(options?: any, isActive = false) {
|
||||
const width = options?.width || 100
|
||||
const height = options?.height || 100
|
||||
delete options.points
|
||||
const starObject = new fabric.Polygon(getStarArr(width, height), {
|
||||
fill: '#000',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("star"),
|
||||
name: '五角星图层',
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(starObject, options)
|
||||
await this.canvasManager.add(starObject)
|
||||
if (isActive) this.setActiveID(starObject.info.id)
|
||||
return starObject
|
||||
}
|
||||
/** 创建箭头图层 */
|
||||
async createArrowLayer(options?: any, isActive = false) {
|
||||
const width = options?.width || 100
|
||||
const height = options?.height || 10
|
||||
delete options.width
|
||||
delete options.height
|
||||
const arrowObject = new fabric.Path(getArrowPath(width, height), {
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 3, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("star"),
|
||||
name: '箭头图层',
|
||||
...(options?.info || {}),
|
||||
}
|
||||
});
|
||||
this.setLayerPosition(arrowObject, options)
|
||||
this.canvasManager.add(arrowObject)
|
||||
if (isActive) this.setActiveID(arrowObject.info.id)
|
||||
return arrowObject
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 创建图片图层 */
|
||||
async createImageLayer(imgOrUrl: string | HTMLImageElement, options?: any, isRecord = true) {
|
||||
const canvasWidth = this.canvasManager.canvasWidth
|
||||
|
||||
@@ -207,5 +207,24 @@ export class ObjectManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 修改透明度
|
||||
* @param id 目标对象ID
|
||||
* @param options 透明度参数
|
||||
* @param options.opacity 透明度
|
||||
* @param isRecord 是否记录
|
||||
*/
|
||||
async updateOpacity(id: string, options: any, isRecord: boolean) {
|
||||
const object = this.getFillRepeatObject(id)
|
||||
if (!object) return null
|
||||
const opacity = options.opacity
|
||||
object.set("opacity", opacity);
|
||||
this.canvasManager.renderAll()
|
||||
if (isRecord) {
|
||||
this.stateManager.recordState()
|
||||
this.layerManager.updateLayerThumbnailsById(id)
|
||||
}
|
||||
}
|
||||
|
||||
dispose() { }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OperationType, OperationTypes } from "../tools/layerHelper";
|
||||
import { fabric } from 'fabric-with-all'
|
||||
/** 矩形工具管理器 */
|
||||
export class RectToolManager {
|
||||
@@ -10,6 +11,9 @@ export class RectToolManager {
|
||||
startX: number = 0
|
||||
startY: number = 0
|
||||
demoObject: any
|
||||
tools = [
|
||||
OperationType.RECTANGLE
|
||||
]
|
||||
constructor(options) {
|
||||
this.canvasManager = options.canvasManager
|
||||
this.stateManager = options.stateManager
|
||||
248
src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts
Normal file
248
src/components/Canvas/DepthCanvas/manager/ShapeToolManager.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { OperationType, OperationTypes } from "../tools/layerHelper";
|
||||
import { getStarArr, getArrowPath, distance, angleBetweenPointsDegrees } from "../tools/canvasMethod";
|
||||
import { fabric } from 'fabric-with-all'
|
||||
/** 形状管理器 */
|
||||
export class ShapeToolManager {
|
||||
// 管理器
|
||||
canvasManager: any
|
||||
stateManager: any
|
||||
layerManager: any
|
||||
toolManager: any
|
||||
|
||||
isDragging: boolean = false
|
||||
startX: number = 0
|
||||
startY: number = 0
|
||||
demoObject: any
|
||||
tools = [
|
||||
OperationType.RECTANGLE,
|
||||
OperationType.LINE,
|
||||
OperationType.ARROW,
|
||||
OperationType.ELLIPSE,
|
||||
OperationType.TRIANGLE,
|
||||
OperationType.STAR,
|
||||
]
|
||||
constructor(options) {
|
||||
this.canvasManager = options.canvasManager
|
||||
this.stateManager = options.stateManager
|
||||
this.layerManager = options.layerManager
|
||||
this.toolManager = options.toolManager
|
||||
}
|
||||
mouseDownEvent(e) {
|
||||
this.isDragging = false
|
||||
this.demoObject = null
|
||||
|
||||
this.startX = e.absolutePointer.x
|
||||
this.startY = e.absolutePointer.y
|
||||
const currentTool = this.toolManager.currentTool.value
|
||||
if (currentTool === OperationType.RECTANGLE) {
|
||||
this.demoObject = this.downRectangle()
|
||||
} else if (currentTool === OperationType.LINE) {
|
||||
this.demoObject = this.downLine()
|
||||
} else if (currentTool === OperationType.ELLIPSE) {
|
||||
this.demoObject = this.downEllipse()
|
||||
} else if (currentTool === OperationType.TRIANGLE) {
|
||||
this.demoObject = this.downTriangle()
|
||||
} else if (currentTool === OperationType.STAR) {
|
||||
this.demoObject = this.downStar()
|
||||
} else if (currentTool === OperationType.ARROW) {
|
||||
this.demoObject = this.downArrow()
|
||||
}
|
||||
if (!this.demoObject) return;
|
||||
this.demoObject.set({
|
||||
evented: false,
|
||||
})
|
||||
this.demoObject.set
|
||||
this.isDragging = true
|
||||
this.canvasManager.canvas.add(this.demoObject)
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseMoveEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
var left = this.startX
|
||||
var top = this.startY
|
||||
if (width < 0) {
|
||||
left += width
|
||||
width = -width
|
||||
}
|
||||
if (height < 0) {
|
||||
top += height
|
||||
height = -height
|
||||
}
|
||||
const currentTool = this.toolManager.currentTool.value
|
||||
if (currentTool === OperationType.RECTANGLE) {
|
||||
this.moveRectangle({ width, height, left, top })
|
||||
} else if (currentTool === OperationType.LINE) {
|
||||
this.moveLine(e.absolutePointer)
|
||||
} else if (currentTool === OperationType.ELLIPSE) {
|
||||
this.moveEllipse({ width, height, left, top })
|
||||
} else if (currentTool === OperationType.TRIANGLE) {
|
||||
this.moveTriangle({ width, height, left, top })
|
||||
} else if (currentTool === OperationType.STAR) {
|
||||
this.moveStar({ width, height, left, top })
|
||||
} else if (currentTool === OperationType.ARROW) {
|
||||
this.moveArrow(e.absolutePointer)
|
||||
}
|
||||
this.demoObject.set({
|
||||
evented: false,
|
||||
})
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseUpEvent(e) {
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
const object = this.demoObject.toJSON("evented")
|
||||
const currentTool = this.toolManager.currentTool.value
|
||||
if (currentTool === OperationType.RECTANGLE) {
|
||||
this.upRectangle(object)
|
||||
} else if (currentTool === OperationType.LINE) {
|
||||
this.upLine(object)
|
||||
} else if (currentTool === OperationType.ELLIPSE) {
|
||||
this.upEllipse(object)
|
||||
} else if (currentTool === OperationType.TRIANGLE) {
|
||||
this.upTriangle(object)
|
||||
} else if (currentTool === OperationType.STAR) {
|
||||
this.upStar(object)
|
||||
} else if (currentTool === OperationType.ARROW) {
|
||||
this.upArrow(object)
|
||||
}
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
this.demoObject = null
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
|
||||
/** 绘制矩形 */
|
||||
downRectangle() {
|
||||
const rect = new fabric.Rect({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
})
|
||||
return rect
|
||||
}
|
||||
moveRectangle({ width, height, left, top }) {
|
||||
this.demoObject.set({ width, height, left, top })
|
||||
|
||||
}
|
||||
upRectangle(object) {
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
this.layerManager.createRectLayer(object, true)
|
||||
}
|
||||
|
||||
/** 绘制直线 */
|
||||
downLine() {
|
||||
const line = new fabric.Line([this.startX, this.startY, this.startX, this.startY], {
|
||||
stroke: 'black', // 线条颜色
|
||||
strokeWidth: 2 // 线条粗细
|
||||
})
|
||||
return line
|
||||
}
|
||||
moveLine({ x, y }) {
|
||||
this.demoObject.set({
|
||||
x1: this.startX,
|
||||
y1: this.startY,
|
||||
x2: x,
|
||||
y2: y,
|
||||
})
|
||||
}
|
||||
upLine(object) {
|
||||
this.layerManager.createLineLayer(object, true)
|
||||
}
|
||||
|
||||
/** 绘制椭圆 */
|
||||
downEllipse() {
|
||||
const circle = new fabric.Ellipse({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
fill: '#000',
|
||||
})
|
||||
return circle
|
||||
}
|
||||
moveEllipse({ width, height, left, top }) {
|
||||
this.demoObject.set({ rx: width / 2, ry: height / 2, left, top })
|
||||
}
|
||||
upEllipse(object) {
|
||||
if (object.rx === 0) object.rx = 50
|
||||
if (object.ry === 0) object.ry = 50
|
||||
this.layerManager.createEllipseLayer(object, true)
|
||||
}
|
||||
|
||||
|
||||
/** 绘制三角形 */
|
||||
downTriangle() {
|
||||
const triangle = new fabric.Triangle({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
})
|
||||
return triangle
|
||||
}
|
||||
moveTriangle({ width, height, left, top }) {
|
||||
this.demoObject.set({ width, height, left, top })
|
||||
}
|
||||
upTriangle(object) {
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
this.layerManager.createTriangleLayer(object, true)
|
||||
}
|
||||
|
||||
|
||||
/** 绘制五角星 */
|
||||
downStar() {
|
||||
const star = new fabric.Polygon(getStarArr(0, 0), {
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
width: 0,
|
||||
height: 0,
|
||||
fill: '#000',
|
||||
strokeLineJoin: 'round', // 圆角连接
|
||||
strokeLineCap: 'round', // 圆角端点
|
||||
});
|
||||
|
||||
return star
|
||||
}
|
||||
moveStar({ width, height, left, top }) {
|
||||
this.demoObject.set({ left, top, width, height, points: getStarArr(width, height) })
|
||||
}
|
||||
upStar(object) {
|
||||
if (object.width === 0) object.width = 100
|
||||
if (object.height === 0) object.height = 100
|
||||
this.layerManager.createStarLayer(object, true)
|
||||
}
|
||||
|
||||
/** 绘制箭头 */
|
||||
downArrow() {
|
||||
return new fabric.Path();
|
||||
}
|
||||
moveArrow({ x, y }) {
|
||||
const width = distance(this.startX, this.startY, x, y)
|
||||
const angle = angleBetweenPointsDegrees(this.startX, this.startY, x, y)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
const arrow = new fabric.Path(getArrowPath(width, 10), {
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 3, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
originY: 'center',
|
||||
angle: angle,
|
||||
});
|
||||
this.canvasManager.canvas.add(arrow)
|
||||
this.demoObject = arrow
|
||||
}
|
||||
upArrow(object) {
|
||||
this.layerManager.createArrowLayer(object, true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
dispose() { }
|
||||
}
|
||||
@@ -54,6 +54,32 @@ export class ToolManager {
|
||||
name: OperationType.RECTANGLE,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
/** 直线工具 */
|
||||
{
|
||||
name: OperationType.LINE,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
/** 箭头工具 */
|
||||
{
|
||||
name: OperationType.ARROW,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
/** 椭圆工具 */
|
||||
{
|
||||
name: OperationType.ELLIPSE,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
/** 三角形工具 */
|
||||
{
|
||||
name: OperationType.TRIANGLE,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
/** 五角星工具 */
|
||||
{
|
||||
name: OperationType.STAR,
|
||||
cursor: "crosshair",
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
onMounted() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isBoolean } from "lodash-es";
|
||||
import { OperationType, OperationTypes } from "../../tools/layerHelper";
|
||||
import { RectToolManager } from "../RectToolManager"
|
||||
import { ShapeToolManager } from "../ShapeToolManager"
|
||||
import { AISelectboxToolManager } from "../AISelectboxToolManager"
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export class CanvasEventManager {
|
||||
toolManager: this.toolManager,
|
||||
layerManager: this.layerManager,
|
||||
}
|
||||
this.rectToolManager = new RectToolManager(managers)
|
||||
this.shapeToolManager = new ShapeToolManager(managers)
|
||||
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
|
||||
|
||||
// 初始化所有事件
|
||||
@@ -209,9 +209,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseDownEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseDownEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseDownEvent(opt);
|
||||
} else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) {
|
||||
this.canvas.isDragging = true;
|
||||
this.canvas.lastPosX = opt.e.clientX;
|
||||
@@ -237,9 +237,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseMoveEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseMoveEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseMoveEvent(opt);
|
||||
} else if (this.canvas.isDragging) {
|
||||
const vpt = this.canvas.viewportTransform;
|
||||
vpt[4] += opt.e.clientX - this.canvas.lastPosX;
|
||||
@@ -321,9 +321,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseDownEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseDownEvent(opt);
|
||||
} else if (currentTool === OperationType.PAN) {
|
||||
|
||||
// 平滑停止任何正在进行的惯性动画
|
||||
@@ -386,9 +386,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseMoveEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseMoveEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseMoveEvent(opt);
|
||||
} else if (currentTool === OperationType.PAN) {
|
||||
|
||||
// 检查是否是触摸事件
|
||||
@@ -496,9 +496,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseUpEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.PAN) {
|
||||
|
||||
// 重置触摸状态
|
||||
@@ -668,9 +668,9 @@ export class CanvasEventManager {
|
||||
} else if (currentTool === OperationType.SELECTBOX) {
|
||||
// 选择框模式
|
||||
this.aiSelectboxToolManager.mouseUpEvent(opt);
|
||||
} else if (currentTool === OperationType.RECTANGLE) {
|
||||
// 矩形模式
|
||||
this.rectToolManager.mouseUpEvent(opt);
|
||||
} else if (this.shapeToolManager.tools.includes(currentTool)) {
|
||||
// 形状模式
|
||||
this.shapeToolManager.mouseUpEvent(opt);
|
||||
} else if (this.canvas.isDragging) {
|
||||
// if (this.lastMousePositions.length > 1 && opt && opt.e) {
|
||||
// this.animationManager.applyInertiaEffect(
|
||||
@@ -1074,7 +1074,7 @@ export class CanvasEventManager {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.rectToolManager?.dispose()
|
||||
this.shapeToolManager?.dispose()
|
||||
this.aiSelectboxToolManager?.dispose()
|
||||
// 移除所有事件监听
|
||||
this.canvas.off();
|
||||
|
||||
@@ -9,13 +9,14 @@ export class KeyEventManager {
|
||||
/** 处理键盘事件 */
|
||||
_handleKeyDown: any
|
||||
handleKeyDown(event: any) {
|
||||
const activeID = this.stateManager.layerManager.activeID.value
|
||||
const ctrl = event.ctrlKey ? 'ctrl-' : "";
|
||||
const shift = event.shiftKey ? 'shift-' : "";
|
||||
const key = event.key;
|
||||
const reg = new RegExp(`^${ctrl}${shift}${key}$`, 'i')
|
||||
const list = [
|
||||
// { key: "ctrl-c", handler: () => this.handleCopy(event) },
|
||||
// { key: "delete", handler: () => this.handleDelete(event) },
|
||||
{ key: "ctrl-c", handler: () => this.stateManager.layerManager.copyLayerById(activeID) },
|
||||
{ key: "delete", handler: () => this.stateManager.layerManager.deleteLayerById(activeID) },
|
||||
{ key: "ctrl-z", handler: () => this.stateManager.undoState() },
|
||||
{ key: "ctrl-shift-z", handler: () => this.stateManager.redoState() },
|
||||
]
|
||||
|
||||
@@ -39,4 +39,57 @@ export async function getObjectsBoundingBox(objects = []) {
|
||||
width: box2.x - box1.x,
|
||||
height: box2.y - box1.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取五角星数组 */
|
||||
export function getStarArr(width = 0, height = 0) {
|
||||
const arr = [
|
||||
{ x: 0, y: -0.5 }, // 顶点0 (上)
|
||||
{ x: 0.15, y: -0.15 }, // 顶点1 (内)
|
||||
{ x: 0.50, y: -0.15 }, // 顶点2 (右上外)
|
||||
{ x: 0.20, y: 0.10 }, // 顶点3 (内)
|
||||
{ x: 0.30, y: 0.50 }, // 顶点4 (右下外)
|
||||
{ x: 0.0, y: 0.25 }, // 顶点5 (内)
|
||||
{ x: -0.30, y: 0.50 }, // 顶点6 (左下外)
|
||||
{ x: -0.20, y: 0.10 }, // 顶点7 (内)
|
||||
{ x: -0.50, y: -0.15 }, // 顶点8 (左上外)
|
||||
{ x: -0.15, y: -0.15 } // 顶点9 (内)
|
||||
]
|
||||
return arr.map(item => ({
|
||||
x: item.x * width,
|
||||
y: item.y * height,
|
||||
}))
|
||||
}
|
||||
/** 获取箭头路径 */
|
||||
export function getArrowPath(width = 0, height = 0) {
|
||||
const arr = [
|
||||
["M", 0, height / 2],
|
||||
["L", width, height / 2],
|
||||
["M", width - 8, 0],
|
||||
["L", width, height / 2],
|
||||
["L", width - 8, height],
|
||||
]
|
||||
var path = ""
|
||||
arr.forEach(item => {
|
||||
path += item.join(" ") + " "
|
||||
})
|
||||
return path
|
||||
}
|
||||
|
||||
/** 计算两点之间的距离 */
|
||||
export function distance(x1, y1, x2, y2) {
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
/** 计算两点之间的角度(角度) */
|
||||
export function angleBetweenPointsDegrees(x1, y1, x2, y2) {
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
|
||||
// 计算弧度并转换为角度
|
||||
const rad = Math.atan2(dy, dx);
|
||||
const deg = rad * 180 / Math.PI;
|
||||
|
||||
return deg;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,16 @@ export const OperationType = {
|
||||
ERASER: "eraser", // 橡皮擦模式
|
||||
IMAGE: "image",// 图片工具模式
|
||||
SELECTBOX: "selectbox",// 选择框工具模式
|
||||
|
||||
RECTANGLE: "rectangle",// 矩形工具模式
|
||||
LINE: "line",// 直线工具模式
|
||||
ARROW: "arrow",// 箭头工具模式
|
||||
ELLIPSE: "ellipse",// 椭圆工具模式
|
||||
TRIANGLE: "triangle",// 三角形工具模式
|
||||
STAR: "star",// 五角星工具模式
|
||||
|
||||
|
||||
|
||||
TEXT: "text",// 文字工具模式
|
||||
UNDO: "undo",// 撤销工具模式
|
||||
REDO: "redo",// 重做工具模式
|
||||
|
||||
Reference in New Issue
Block a user