Skip to content
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

feat(ICollection/Layer): Group Rewrite #7473

Closed
wants to merge 147 commits into from
Closed

feat(ICollection/Layer): Group Rewrite #7473

wants to merge 147 commits into from

Conversation

asturur
Copy link
Member

@asturur asturur commented Nov 4, 2021

Motivation

#7316 #6776 #7136 #7299 #7130 #7142
Group is obsolete. We need something much better for fabric.
I think this is a successful predecessor that can replace Group entirely.
This PR aims first and foremost to enable/support nested selection under an Object.

Gist

I managed to come up with this solution thanks to this fabricjs/fabricjs.com#315:

Creating custom objects built from other objects is an extremely powerful option, but it takes you closer to fabric's edge.

You may experience problems. Let's use an example to explain.

When an object (a) renders, it transforms the rendering context to it's center point.
In order to render another object (b) as a child of (a) we need to take that into account.
We can handle (b)'s transform matrix relative to (a)'s center point. This will render (b) correctly.

However, it will cause many problems.

Let's take, for example, one of the simplest and most powerful rendering optimizations to explain.

Before an object renders it checks if it's visible on screen. If it isn't it skips the rendering process.
Checking if it's visible means the object checks it's transform matrix relative to the canvas.
But we've handled it's transform matrix relative to (a)....

This will cause (b) to skip renders in some cases and not show at all.

This gotcha affects almost everything fabric has to offer.

The solution is simple.

Handle the transform matrix relative to canvas, ALWAYS. When rendering (b), transform the rendering context from (a)'s center point back to canvas.

For more information and detailed explanation, check the dedicated pages: Using transformations, Subclassing.

This is why Layer objects are transformed relative to canvas

Impl

  • Expose a method that adds the transform diff from the previous call to objects.
  • In order to layout we need to disable transform propagation.

To Do

  • object caching (copy from group?)
  • performance - in case of a very large amount of objects (SprayBrush)
  • objectCaching=false: need a better hook for _applyMatrixDiff
  • additional layout strategies (exposed public method getLayoutStrategyResult)
  • getLayoutStrategyResult with clip path?
  • support rotation
  • overflow - clipping during selection??
  • test svg import/export
  • svg import: handle object doubled opacity, handle layout prop, transform, subTargetCheck
  • deep interactivity.
  • tests - cover all group cases, stress tests, etc.
  • active selection cases

Additional

  • migrate SprayBrush from group?

* Create layer.class.js

* Update build.js

* Update layer.class.js

* stable

* Update layer.class.js

* add/remove objects

* Update layer.class.js

* svg

* typo

* Update layer.class.js

* fix(): remove + layout

* fix(): layout

* svg

* Update layer.class.js

* Update layer.class.js

* lint

* Update canvas.class.js

* Update layer.class.js

* Revert "svg"

This reverts commit 1433f56.

* perf(): `SprayBrush` case

`SprayBrush` case - a huge amount of objects
optimizing logic according to `subTargetCheck`

* parent property

* Update layer.class.js

* Update layer.class.js

* feat(Object#onSelect): support returning a descendant of object

Return a descendant of object (`subTargets` prop of the first argument passed to `onSelect`).
This makes selecting a child object of a collection possible.

* _applyLayoutStrategy: add context + call on object change

* fix(_setActiveObject): handle nested selection if object is already selected

* fix(): call setCoords, getObjectsBoundingBox

* Update canvas.class.js

* ci(): lint

* Update canvas_events.mixin.js

* better `getObjectsBoundingBox`

* Revert "Update canvas_events.mixin.js"

This reverts commit 025a76f.

* Update canvas.class.js

* Update layer.class.js

* Update canvas_events.mixin.js

* Update canvas_events.mixin.js

* Update layer.class.js

* Update layer.class.js

* Update object_stacking.mixin.js

* Update active_selection.class.js

* Update layer.class.js

* Update layer.class.js

* Update layer.class.js

* Update layer.class.js

* fix(): render selected on top

* lint

* Update layer.class.js

* fix(): nested selection

* Update layer.class.js

* fix(): selection stack order

selecting an object belonging to the same parent while under a foreign object.

* Update layer.class.js

* fix(): caching
@asturur asturur changed the title feat() Layer v2 (#7472) feat(Layer): Group Rewrite Nov 4, 2021
@ShaMan123
Copy link
Contributor

ShaMan123 commented Nov 6, 2021

Changed _applyMatrixDiff hook to setCoords. This introduced a problem with object caching.
Hooking to render is OK, hooking to setCoords only is not good because it renders a frame+ late
fixed

@ShaMan123
Copy link
Contributor

ShaMan123 commented Nov 13, 2021

Last commits optimized layer in such a way I think it is now more performant than group.
Instead of rendering all the time after applying the matrix diff it does the following:

  1. keep objects up to date from setCoords but doesn't re-render
  2. render once an object is selected/deselected (the selected object is not rendered by layer)

The last major thing is rotation + bounding box

src/parser.js Outdated
@@ -29,6 +29,7 @@
display: 'visible',
visibility: 'visible',
transform: 'transformMatrix',
'fabric-layout': 'layout',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

@asturur
Copy link
Member Author

asturur commented Dec 2, 2021

i m starting to review and taking notes. Can you leave the code steady for a bit?

@ShaMan123
Copy link
Contributor

ShaMan123 commented Dec 2, 2021 via email

@asturur
Copy link
Member Author

asturur commented Dec 2, 2021

There is some good stuff here!
Some lesser things probably shouldn't be done. I left extensive comments in our doc instead of triggering a tons of emails back and fort with code comments.

@ShaMan123
Copy link
Contributor

I will work with PRs and merge into this one so you won't get overwhelmed with commits or changes while trying to review this.
Next up #7529

_setActiveObject: function(object, e) {
if (this._activeObject === object) {
_setActiveObject: function (object, e) {
var isCollection = Array.isArray(object._objects), activeObject = this._activeObject;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For fixing the drag of multiple object isCollection should be fixed and line 1151 should be

var isCollection = (Array.isArray(object._objects) && object.type != 'activeSelection'), activeObject = this._activeObject;

Copy link
Contributor

@ShaMan123 ShaMan123 Jan 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll look into it.
Just wanted to make sure you've tested this with #7529 merged into your local branch.
This should do it

git fetch upstream
git merge ShaMan123/layer-proposal-patch --squash

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't test it but I have a few thoughts.
If I understand correctly if we accept your proposal selecting objects nested in active selection will become impossible.
Can you confirm?
If so this case should be handled by the dev overriding onSelect of active selection.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for me its working as it should selecting in icollection when its there but doesnt cancel selection for active selection, tested on #7529 merged branch.

@asturur
Copy link
Member Author

asturur commented Jan 10, 2022

* getAncestors

* isInFrontOf

* tests

* fix(Canvas): `_chooseObjectsToRender`

* Update object.js

* Update object.js
@ShaMan123
Copy link
Contributor

ShaMan123 commented Jan 10, 2022

@ebigtm says:

after the fix i presented yesterday a new issue popped, after dragging the objects it casts ray on the object the mouse up was on and selects it, the fix i found is a bit rough: var result = object.type == 'iCollection' ? object.onSelect({ e: e, object: activeObject, subTargets: this.targets.filter(function (object) { return object.isSelectable(); }), activeSubTargets: object._objects.length > 0 ? true : subTargets && subTargets.filter(function (object) { return object.isSelectable(); }) }) : object.onSelect({ e: e, object: activeObject, });

maybe it can be done differntly but generally i added a condition regarding the type, maybe we can use isCollection var instead?

fabric.js/src/canvas.class.js

Lines 1192 to 1197 in 5571a3a

var result = object.onSelect({
e: e,
object: activeObject,
subTargets: this.targets.filter(function (object) { return object.isSelectable(); }),
activeSubTargets: subTargets && subTargets.filter(function (object) { return object.isSelectable(); })
});

@ShaMan123
Copy link
Contributor

closing this in favor of #7669
There's still selection logic that might be useful

@ShaMan123 ShaMan123 closed this Feb 14, 2022
@ShaMan123 ShaMan123 mentioned this pull request Apr 4, 2022
48 tasks
@ShaMan123 ShaMan123 deleted the layer-proposal branch September 12, 2022 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants