mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 23:12:21 -04:00

* add fill tool * add fill tool logic * open colour picker on fill tool activation * refactor/add fill attr to io clozes * fill masks in editor * fill text and inactive masks in reviewer * fix lint * remove debug option
263 lines
7.1 KiB
TypeScript
263 lines
7.1 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import { fabric } from "fabric";
|
|
import { get } from "svelte/store";
|
|
|
|
import { opacityStateStore } from "../store";
|
|
import { BORDER_COLOR, isPointerInBoundingBox, SHAPE_MASK_COLOR } from "./lib";
|
|
import { undoStack } from "./tool-undo-redo";
|
|
import { onPinchZoom } from "./tool-zoom";
|
|
|
|
let activeLine;
|
|
let activeShape;
|
|
let linesList: fabric.Line[] = [];
|
|
let pointsList: fabric.Circle[] = [];
|
|
let drawMode = false;
|
|
|
|
export const drawPolygon = (canvas: fabric.Canvas): void => {
|
|
// remove selectable for shapes
|
|
canvas.discardActiveObject();
|
|
canvas.forEachObject(function(o) {
|
|
o.selectable = false;
|
|
});
|
|
|
|
canvas.selectionColor = "rgba(0, 0, 0, 0)";
|
|
canvas.on("mouse:down", function(options) {
|
|
try {
|
|
if (options.target && options.target["id"] === pointsList[0]["id"]) {
|
|
generatePolygon(canvas, pointsList);
|
|
} else {
|
|
addPoint(canvas, options);
|
|
}
|
|
} catch (e) {
|
|
// Cannot read properties of undefined (reading 'id')
|
|
}
|
|
});
|
|
|
|
canvas.on("mouse:move", function(options) {
|
|
// if pinch zoom is active, remove all points and lines
|
|
if (onPinchZoom(options)) {
|
|
removeUnfinishedPolygon(canvas);
|
|
return;
|
|
}
|
|
|
|
if (activeLine && activeLine.class === "line") {
|
|
const pointer = canvas.getPointer(options.e);
|
|
activeLine.set({
|
|
x2: pointer.x,
|
|
y2: pointer.y,
|
|
});
|
|
|
|
const points = activeShape.get("points");
|
|
points[pointsList.length] = {
|
|
x: pointer.x,
|
|
y: pointer.y,
|
|
};
|
|
|
|
activeShape.set({ points });
|
|
}
|
|
canvas.renderAll();
|
|
});
|
|
};
|
|
|
|
const toggleDrawPolygon = (canvas: fabric.Canvas): void => {
|
|
drawMode = !drawMode;
|
|
if (drawMode) {
|
|
activeLine = null;
|
|
activeShape = null;
|
|
linesList = [];
|
|
pointsList = [];
|
|
drawMode = false;
|
|
canvas.selection = true;
|
|
} else {
|
|
drawMode = true;
|
|
canvas.selection = false;
|
|
}
|
|
};
|
|
|
|
const addPoint = (canvas: fabric.Canvas, options): void => {
|
|
const pointer = canvas.getPointer(options.e);
|
|
const origX = pointer.x;
|
|
const origY = pointer.y;
|
|
|
|
if (!isPointerInBoundingBox(pointer)) {
|
|
return;
|
|
}
|
|
|
|
const point = new fabric.Circle({
|
|
radius: 5,
|
|
fill: "transparent",
|
|
stroke: "#333333",
|
|
strokeWidth: 1.5,
|
|
originX: "center",
|
|
originY: "center",
|
|
left: origX,
|
|
top: origY,
|
|
selectable: false,
|
|
hasBorders: false,
|
|
hasControls: false,
|
|
objectCaching: false,
|
|
perPixelTargetFind: false,
|
|
});
|
|
|
|
if (pointsList.length === 0) {
|
|
point.set({
|
|
stroke: "red",
|
|
});
|
|
}
|
|
|
|
const linePoints = [origX, origY, origX, origY];
|
|
|
|
const line = new fabric.Line(linePoints, {
|
|
strokeWidth: 2,
|
|
fill: "#999999",
|
|
stroke: "#999999",
|
|
originX: "center",
|
|
originY: "center",
|
|
selectable: false,
|
|
hasBorders: false,
|
|
hasControls: false,
|
|
evented: false,
|
|
objectCaching: false,
|
|
});
|
|
line["class"] = "line";
|
|
|
|
if (activeShape) {
|
|
const pointer = canvas.getPointer(options.e);
|
|
const points = activeShape.get("points");
|
|
points.push({
|
|
x: pointer.x,
|
|
y: pointer.y,
|
|
});
|
|
|
|
const polygon = new fabric.Polygon(points, {
|
|
stroke: "#333333",
|
|
strokeWidth: 1,
|
|
fill: "#cccccc",
|
|
opacity: 0.3,
|
|
selectable: false,
|
|
hasBorders: false,
|
|
hasControls: false,
|
|
evented: false,
|
|
objectCaching: false,
|
|
});
|
|
|
|
canvas.remove(activeShape);
|
|
canvas.add(polygon);
|
|
activeShape = polygon;
|
|
canvas.renderAll();
|
|
} else {
|
|
const polyPoint = [{ x: origX, y: origY }];
|
|
const polygon = new fabric.Polygon(polyPoint, {
|
|
stroke: "#333333",
|
|
strokeWidth: 1,
|
|
fill: "#cccccc",
|
|
opacity: 0.3,
|
|
selectable: false,
|
|
hasBorders: false,
|
|
hasControls: false,
|
|
evented: false,
|
|
objectCaching: false,
|
|
});
|
|
|
|
activeShape = polygon;
|
|
canvas.add(polygon);
|
|
}
|
|
|
|
activeLine = line;
|
|
pointsList.push(point);
|
|
linesList.push(line);
|
|
|
|
canvas.add(line);
|
|
canvas.add(point);
|
|
canvas.renderAll();
|
|
};
|
|
|
|
const generatePolygon = (canvas: fabric.Canvas, pointsList): void => {
|
|
const points: { x: number; y: number }[] = [];
|
|
pointsList.forEach((point) => {
|
|
points.push({
|
|
x: point.left,
|
|
y: point.top,
|
|
});
|
|
canvas.remove(point);
|
|
});
|
|
|
|
linesList.forEach((line) => {
|
|
canvas.remove(line);
|
|
});
|
|
|
|
canvas.remove(activeShape).remove(activeLine);
|
|
|
|
const polygon = new fabric.Polygon(points, {
|
|
fill: SHAPE_MASK_COLOR,
|
|
objectCaching: false,
|
|
stroke: BORDER_COLOR,
|
|
strokeWidth: 1,
|
|
strokeUniform: true,
|
|
noScaleCache: false,
|
|
selectable: false,
|
|
opacity: get(opacityStateStore) ? 0.4 : 1,
|
|
});
|
|
polygon["id"] = "polygon-" + new Date().getTime();
|
|
if (polygon.width! > 5 && polygon.height! > 5) {
|
|
canvas.add(polygon);
|
|
canvas.setActiveObject(polygon);
|
|
// view undo redo tools
|
|
undoStack.onObjectAdded(polygon["id"]);
|
|
}
|
|
|
|
toggleDrawPolygon(canvas);
|
|
};
|
|
|
|
// https://github.com/fabricjs/fabric.js/issues/6522
|
|
export const modifiedPolygon = (canvas: fabric.Canvas, polygon: fabric.Polygon): void => {
|
|
const matrix = polygon.calcTransformMatrix();
|
|
const transformedPoints = polygon.get("points")!
|
|
.map(function(p) {
|
|
return new fabric.Point(p.x - polygon.pathOffset.x, p.y - polygon.pathOffset.y);
|
|
})
|
|
.map(function(p) {
|
|
return fabric.util.transformPoint(p, matrix);
|
|
});
|
|
|
|
const polygon1 = new fabric.Polygon(transformedPoints, {
|
|
fill: polygon.fill ?? SHAPE_MASK_COLOR,
|
|
objectCaching: false,
|
|
stroke: BORDER_COLOR,
|
|
strokeWidth: 1,
|
|
strokeUniform: true,
|
|
noScaleCache: false,
|
|
opacity: get(opacityStateStore) ? 0.4 : 1,
|
|
});
|
|
polygon1["id"] = polygon["id"];
|
|
|
|
canvas.remove(polygon);
|
|
canvas.add(polygon1);
|
|
};
|
|
|
|
/**
|
|
* Removes the currently unfinished polygon, if any, and reset internal state
|
|
* @returns whether or not such a polygon was removed and state was reset
|
|
*/
|
|
export const removeUnfinishedPolygon = (canvas: fabric.Canvas): boolean => {
|
|
if (!activeShape) {
|
|
// generatePolygon should've already removed points/lines and reset state
|
|
return false;
|
|
}
|
|
canvas.remove(activeShape).remove(activeLine);
|
|
pointsList.forEach((point) => {
|
|
canvas.remove(point);
|
|
});
|
|
linesList.forEach((line) => {
|
|
canvas.remove(line);
|
|
});
|
|
activeLine = null;
|
|
activeShape = null;
|
|
linesList = [];
|
|
pointsList = [];
|
|
drawMode = false;
|
|
canvas.selection = true;
|
|
return true;
|
|
};
|