-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
update(AligningGuidelines): Fix some bugs and add custom features. #10120
base: master
Are you sure you want to change the base?
Changes from 9 commits
c1054f1
9523d69
a764f5e
d581738
1a5e2f1
c96219f
d88e5fc
28861b1
bbc55d2
4f241b3
88f7922
c18d25c
a2af8e1
0c390d8
af519ad
c41d019
e292b86
630cbfd
898950f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,9 @@ import type { | |
BasicTransformEvent, | ||
Canvas, | ||
FabricObject, | ||
TBBox, | ||
TPointerEvent, | ||
} from 'fabric'; | ||
import { Point, util } from 'fabric'; | ||
import { Point } from 'fabric'; | ||
import { | ||
collectHorizontalPoint, | ||
collectVerticalPoint, | ||
|
@@ -15,14 +14,11 @@ import { | |
drawPointList, | ||
drawVerticalLine, | ||
} from './util/draw'; | ||
import { getObjectsByTarget } from './util/get-objects-by-target'; | ||
import { collectLine } from './util/collect-line'; | ||
import type { | ||
AligningLineConfig, | ||
HorizontalLine, | ||
VerticalLine, | ||
} from './typedefs'; | ||
import type { AligningLineConfig, LineProps } from './typedefs'; | ||
import { aligningLineConfig } from './constant'; | ||
import { getObjectsByTarget } from './util/get-objects-by-target'; | ||
import { getContraryMap, getPointMap } from './util/basic'; | ||
|
||
type TransformEvent = BasicTransformEvent<TPointerEvent> & { | ||
target: FabricObject; | ||
|
@@ -32,113 +28,153 @@ export type { AligningLineConfig } from './typedefs'; | |
|
||
export function initAligningGuidelines( | ||
canvas: Canvas, | ||
options: Partial<AligningLineConfig> = {}, | ||
options: Partial<AligningLineConfig> = {} | ||
) { | ||
Object.assign(aligningLineConfig, options); | ||
|
||
const horizontalLines = new Set<string>(); | ||
const verticalLines = new Set<string>(); | ||
// When we drag to resize using center points like mt, ml, mb, and mr, | ||
// we do not need to draw line segments; we only need to draw the target points. | ||
let onlyDrawPoint = false; | ||
const cacheMap = new Map<string, [TBBox, Point[]]>(); | ||
const cacheMap = new Map<string, Point[]>(); | ||
|
||
const getCaCheMapValue = (object: FabricObject) => { | ||
// If there is an ID and the ID is unique, we can cache using the ID for acceleration. | ||
// However, since Fabric does not have a built-in ID, we use the position information as the key for caching. | ||
// const cacheKey = object.id; | ||
const cacheKey = [ | ||
object.calcTransformMatrix().toString(), | ||
object.width, | ||
object.height, | ||
].join(); | ||
const cacheValue = cacheMap.get(cacheKey); | ||
if (cacheValue) return cacheValue; | ||
const coords = object.getCoords(); | ||
const rect = util.makeBoundingBoxFromPoints(coords); | ||
const value: [TBBox, Point[]] = [rect, coords]; | ||
const value = object.getCoords(); | ||
value.push(object.getCenterPoint()); | ||
cacheMap.set(cacheKey, value); | ||
return value; | ||
}; | ||
|
||
function moving(e: TransformEvent) { | ||
const activeObject = e.target; | ||
activeObject.setCoords(); | ||
const target = e.target; | ||
// We need to obtain the real-time coordinates of the current object, so we need to update them in real-time | ||
target.setCoords(); | ||
onlyDrawPoint = false; | ||
verticalLines.clear(); | ||
horizontalLines.clear(); | ||
|
||
const objects = getObjectsByTarget(activeObject); | ||
const activeObjectRect = activeObject.getBoundingRect(); | ||
|
||
for (const object of objects) { | ||
const objectRect = getCaCheMapValue(object)[0]; | ||
const { vLines, hLines } = collectLine({ | ||
activeObject, | ||
activeObjectRect, | ||
objectRect, | ||
}); | ||
vLines.forEach((o) => { | ||
verticalLines.add(JSON.stringify(o)); | ||
}); | ||
hLines.forEach((o) => { | ||
horizontalLines.add(JSON.stringify(o)); | ||
}); | ||
} | ||
// Find the shapes associated with the current graphic to draw reference lines for it. | ||
const objects = | ||
options.getObjectsByTarget?.(target) ?? getObjectsByTarget(target); | ||
const points: Point[] = []; | ||
// Collect all the points to draw reference lines. | ||
for (const object of objects) points.push(...getCaCheMapValue(object)); | ||
|
||
// Obtain horizontal and vertical reference lines. | ||
const { vLines, hLines } = collectLine(target, points); | ||
vLines.forEach((o) => { | ||
// Objects cannot be deduplicated; convert them to strings for deduplication. | ||
verticalLines.add(JSON.stringify(o)); | ||
}); | ||
hLines.forEach((o) => { | ||
// Objects cannot be deduplicated; convert them to strings for deduplication. | ||
horizontalLines.add(JSON.stringify(o)); | ||
}); | ||
} | ||
|
||
function scalingOrResizing(e: TransformEvent) { | ||
// br bl tr tl mb ml mt mr | ||
const activeObject = e.target; | ||
activeObject.setCoords(); | ||
const target = e.target; | ||
// We need to obtain the real-time coordinates of the current object, so we need to update them in real-time | ||
target.setCoords(); | ||
// The value of action can be scaleX, scaleY, scale, resize, etc. | ||
// If it does not start with "scale," it is considered a modification of size. | ||
const isScale = String(e.transform.action).startsWith('scale'); | ||
verticalLines.clear(); | ||
horizontalLines.clear(); | ||
|
||
const objects = getObjectsByTarget(activeObject); | ||
const objects = | ||
options.getObjectsByTarget?.(target) ?? getObjectsByTarget(target); | ||
let corner = e.transform.corner; | ||
if (activeObject.flipX) corner = corner.replace('l', 'r').replace('r', 'l'); | ||
if (activeObject.flipY) corner = corner.replace('t', 'b').replace('b', 't'); | ||
let index = ['tl', 'tr', 'br', 'bl', 'mt', 'mr', 'mb', 'ml'].indexOf( | ||
corner, | ||
); | ||
if (index == -1) return; | ||
onlyDrawPoint = index > 3; | ||
// When the shape is flipped, the tl obtained through getCoords is actually tr, | ||
// and tl is actually tr. We need to make correction adjustments. | ||
// tr <-> tl、 bl <-> br、 mb <-> mt、 ml <-> mr | ||
if (target.flipX) corner = corner.replace('l', 'r').replace('r', 'l'); | ||
if (target.flipY) corner = corner.replace('t', 'b').replace('b', 't'); | ||
const coords = target.getCoords(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't see properly but is this constant used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When executing the flip, its value becomes true, which is set internally by Fabric. The video below shows the bug that occurs when it is turned off. 2024-11-07.10.01.35.movThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you are right, it should be deleted. |
||
// Obtain the coordinates of the current operation point through the value of corner. | ||
// users can be allowed to customize and pass in custom corners. | ||
const pointMap = options.getPointMap?.(target) ?? getPointMap(target); | ||
if (!(corner in pointMap)) return; | ||
onlyDrawPoint = corner.includes('m'); | ||
if (onlyDrawPoint) { | ||
const angle = activeObject.getTotalAngle(); | ||
const angle = target.getTotalAngle(); | ||
// When the shape is rotated, it is meaningless to draw points using the center point. | ||
if (angle % 90 != 0) return; | ||
index -= 4; | ||
} | ||
let point = activeObject.getCoords()[index]; | ||
for (const object of objects) { | ||
const [rect, coords] = getCaCheMapValue(object); | ||
const center = new Point( | ||
rect.left + rect.width / 2, | ||
rect.top + rect.height / 2, | ||
); | ||
const list = [...coords, center]; | ||
const props = { activeObject, point, list, isScale, index }; | ||
const vLines = collectVerticalPoint(props); | ||
const hLines = collectHorizontalPoint(props); | ||
vLines.forEach((o) => { | ||
verticalLines.add(JSON.stringify(o)); | ||
}); | ||
hLines.forEach((o) => { | ||
horizontalLines.add(JSON.stringify(o)); | ||
}); | ||
if (vLines.length || hLines.length) | ||
point = activeObject.getCoords()[index]; | ||
} | ||
// If manipulating tl, then when the shape changes size, it should be positioned by br, | ||
// and the same applies to others. | ||
// users can be allowed to customize and pass in custom corners. | ||
const contraryMap = | ||
options.getContraryMap?.(target) ?? getContraryMap(target); | ||
|
||
const point = pointMap[corner]; | ||
const diagonalPoint = contraryMap[corner]; | ||
const uniformIsToggled = e.e[canvas.uniScaleKey!]; | ||
let isUniform = | ||
(canvas.uniformScaling && !uniformIsToggled) || | ||
(!canvas.uniformScaling && uniformIsToggled); | ||
// When controlling through the center point, | ||
// if isUniform is true, it actually changes the skew, so it is meaningless. | ||
if (onlyDrawPoint) isUniform = false; | ||
|
||
const list: Point[] = []; | ||
for (const object of objects) list.push(...getCaCheMapValue(object)); | ||
|
||
const props = { | ||
target, | ||
point, | ||
diagonalPoint, | ||
corner, | ||
list, | ||
isScale, | ||
isUniform, | ||
}; | ||
// Obtain horizontal and vertical reference lines. | ||
const vLines = collectVerticalPoint(props); | ||
const hLines = collectHorizontalPoint(props); | ||
vLines.forEach((o) => { | ||
// Objects cannot be deduplicated; convert them to strings for deduplication. | ||
verticalLines.add(JSON.stringify(o)); | ||
}); | ||
hLines.forEach((o) => { | ||
// Objects cannot be deduplicated; convert them to strings for deduplication. | ||
horizontalLines.add(JSON.stringify(o)); | ||
}); | ||
} | ||
|
||
function beforeRender() { | ||
canvas.clearContext(canvas.contextTop); | ||
} | ||
function afterRender() { | ||
if (onlyDrawPoint) { | ||
const list: Array<VerticalLine | HorizontalLine> = []; | ||
for (const v of verticalLines) list.push(JSON.parse(v)); | ||
for (const h of horizontalLines) list.push(JSON.parse(h)); | ||
const list: LineProps[] = []; | ||
if (!options.closeVLine) { | ||
for (const v of verticalLines) list.push(JSON.parse(v)); | ||
} | ||
if (!options.closeHLine) { | ||
for (const h of horizontalLines) list.push(JSON.parse(h)); | ||
} | ||
drawPointList(canvas, list); | ||
} else { | ||
for (const v of verticalLines) drawVerticalLine(canvas, JSON.parse(v)); | ||
for (const h of horizontalLines) | ||
drawHorizontalLine(canvas, JSON.parse(h)); | ||
if (!options.closeVLine) { | ||
for (const v of verticalLines) drawVerticalLine(canvas, JSON.parse(v)); | ||
} | ||
if (!options.closeHLine) { | ||
for (const h of horizontalLines) { | ||
drawHorizontalLine(canvas, JSON.parse(h)); | ||
} | ||
} | ||
} | ||
} | ||
function mouseUp() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried this PR and tried using this function to filter out objects that have properties
isGridLine
on themlike
if (!object.isGridLine) { set.add(object) }
but then no guidelines happened at all
was I using it wrong maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Smrtnyk
Can you provide a simple demo to reproduce your issue? This will help me locate the problem. Regarding this function, if customized, it means that when moving the target element, it only processes the reference line for the graphical representation of the return value
The input parameter of this function is the current shape being operated on, not a loop representing all shapes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is what I tried
I have a grid that I toggle, used as a helper when adding shapes
for aligning guidelines I want to ignore all lines in that grid
and I thought I can ignore them if I don't add them to the set that is being returned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Smrtnyk
I wrote the custom code according to your method and found that it works. You can compare it with the demo to see where the differences are.
custom demo
2024-11-08.10.37.59.mov