feat: add background fill functionality for layers

- Implemented FillLayerBackgroundCommand to fill the background of layers with a specified color.
- Introduced BackgroundFillManager to manage background fill operations.
- Updated LayerManager to include fillLayerBackground method.
- Enhanced LayersPanel with a color picker for filling layer backgrounds.
- Modified RasterizeLayerCommand to reflect changes in terminology and functionality.
- Adjusted LayerSort to ensure filled layers are rendered at the correct z-index.
- Updated relevant utility functions and components to support new fill feature.
This commit is contained in:
bighuixiang
2025-07-14 23:42:28 +08:00
parent 24e9ba8ae5
commit fc9a3eddc2
12 changed files with 524 additions and 57 deletions

View File

@@ -1,3 +1,4 @@
import { BackgroundFillManager } from "./BackgroundFillManager";
import {
AddLayerCommand,
PasteLayerCommand,
@@ -89,6 +90,13 @@ export class LayerManager {
this.commandManager = options.commandManager;
this.canvasManager = options.canvasManager || null;
this.backgroundFillManager = new BackgroundFillManager({
canvas: this.canvas,
layers: this.layers,
commandManager: this.commandManager,
canvasManager: this.canvasManager,
});
// 编辑器模式draw(绘画)、select(选择)、pan(拖拽)
this.editorMode = options.editorMode || CanvasConfig.defaultTool;
@@ -332,6 +340,14 @@ export class LayerManager {
}
// 如果是组图层 则给所有子对象设置裁剪对象
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
if (layer.fill) {
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象
fabricObject.setCoords();
}
}
layer.children.forEach((childLayer) => {
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
if (childObj) {
@@ -339,6 +355,15 @@ export class LayerManager {
childObj.dirty = true; // 标记为脏对象
childObj.setCoords();
}
if (childLayer.fill) {
const fabricObject = this.canvas.getObjects().find((o) => o.id === childLayer.fill.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象
fabricObject.setCoords();
}
}
});
} else {
layer.fabricObjects?.forEach((obj) => {
@@ -349,9 +374,28 @@ export class LayerManager {
fabricObject.setCoords();
}
});
if (layer.fill) {
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象
fabricObject.setCoords();
}
}
}
});
}
/**
* 填充图层背景
* @param {string} layerId 图层ID
* @param {string} fillColor 填充颜色
*/
async fillLayerBackground(layerId, fillColor) {
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor);
}
/**
* 创建新图层
* @param {string} name 图层名称
@@ -908,9 +952,25 @@ export class LayerManager {
}
}
if (child.fill) {
// 如果图层有填充颜色,设置所有对象的填充颜色
const { object } = findObjectById(this.canvas, child.fill.id);
if (object) {
acc.push(object);
}
}
return acc;
}, []);
if (layer.fill) {
// 如果图层有填充颜色,设置所有对象的填充颜色
const { object } = findObjectById(this.canvas, layer.fill.id);
if (object) {
allObjects.push(object);
}
}
if (allObjects.length) {
// 切换到选择模式
this?.toolManager?.setTool(OperationType.SELECT);
@@ -2742,7 +2802,7 @@ export class LayerManager {
* @returns {boolean} 是否可以栅格化
*/
canRasterizeLayer(layerId) {
const layer = this.getLayerById(layerId);
const layer = findLayerRecursively(this.layers.value, layerId)?.layer;
if (!layer) return false;
// 不允许栅格化背景图层和固定图层
@@ -2751,13 +2811,9 @@ export class LayerManager {
}
// 检查图层是否有内容可以栅格化
if (layer.type === "group") {
if (layer.type === "group" || layer.children.length > 0) {
// 组图层:检查是否有子图层且子图层有内容
return (
layer.children &&
layer.children.length > 0 &&
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
);
return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0);
} else {
// 普通图层:检查是否有对象
return layer.fabricObjects && layer.fabricObjects.length > 0;
@@ -2835,7 +2891,7 @@ export class LayerManager {
let isUpdating = false;
let lastUpdateTime = 0;
let hasMoved = false; // 追踪是否实际发生了移动
const UPDATE_THRESHOLD = 16; // 约60fps
const UPDATE_THRESHOLD = 32; // 约60fps
// 移动开始事件处理
const handleMovingStart = (e) => {