-
-
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
feat(LayoutManager): Handle the case of activeSelection with objects inside different groups #9651
Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
Build Stats
|
Ok this reproduction case uses clone, so it shares the bug with the objects not being registered. |
The bug was split around 2 locations:
I have to check the jest failures, but this fixes the group interactivity |
This PR requires an end to end test |
I would like to review this thoroughly when I find the time |
This could be probably done differently by defining an active selection strategy, but the registration part is in the layout manager and i don't want to flip more things right now. |
Yes but please when you have time for fabricJS repo, do this first, we have still 2 bugs in the layout manager that block us from drawing a line and cutting 6.0 |
src/LayoutManager/LayoutManager.ts
Outdated
this.performLayout({ | ||
...layoutingEvents.map((key) => | ||
object.on(key, (e) => { | ||
target.layoutManager.performLayout({ | ||
trigger: key, | ||
e: { ...e, target: object }, |
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.
target here should be outside of e and called something like 'triggerSource' or something on that way.
Usually what we call e is the event that generated the call to the callback, and in this case target isn't part of it.
So adding it there is a bit confusing. I understand we want to comunicate to the dev which object moved and triggered a layout, but that can be done with the specific name.
out of scope for this pr
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.
Sounds correct
On top of that it would be just smarter if when creating an active selection we would understand is pointless to register for events its own children and just register the activeSelection itself. |
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.
See comments
The biggest question to answer is: Do you want to create a subclass ActiveSelectionLayoutManger
?
It is a design question.
I think it makes it easier to understand
What do you think?
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.
Great test
src/LayoutManager/LayoutManager.ts
Outdated
this.performLayout({ | ||
...layoutingEvents.map((key) => | ||
object.on(key, (e) => { | ||
target.layoutManager.performLayout({ | ||
trigger: key, | ||
e: { ...e, target: object }, |
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.
Sounds correct
@@ -130,11 +142,27 @@ export class LayoutManager { | |||
const { target } = context; | |||
const { canvas } = target; | |||
// handle layout triggers subscription | |||
// @TODO: gate the registration when the group is interactive |
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 redundant IMO since if the group is not interactive nothing will trigger imperative layout because the user will not be able to select and transform a nested object.
It does subscribe the objects with no need but if the group becomes interactive we save the dev from a gotcha needing to subscribe the targets when toggling interactivity
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.
toggling interactivity will subscribe all the targets, that is why i deprecated .interactive = true for a setInteractive(true). We already need to take in account that subTargetCheck also needs to be true, better to make it a controlled process.
As a counter argument interactive group is an edge case, for now we don't need to go to the extreme that objects get registered for handlers only when they get selected or when a trasform action starts, but at least when the group is interactive yes.
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.
As a counter argument interactive group is an edge case, for now we don't need to go to the extreme that objects get registered for handlers only when they get selected or when a trasform action starts, but at least when the group is interactive yes.
This is not bad
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.
However toggling interactivity... I am not sure. There is another PR I did which I mentioned in this PR regarding it
I will find it
src/LayoutManager/LayoutManager.ts
Outdated
// so we need to subscribe the active selection event to trigger the parent performLayout | ||
withDifferentParent.forEach(({ group, parent }) => { | ||
if (parent) { | ||
// we may subscribe an active selection more than once, |
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 doesn't sound good, bad for perf and for logic
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 in contrast with wanting to register all the objects all the time.
Right now if an active selection has 2 objects in the same group i think will try to register twice, but trying a different approach may fix that.
@@ -27,6 +27,8 @@ export abstract class LayoutStrategy { | |||
|
|||
/** | |||
* Used by the `LayoutManager` to perform layout | |||
* @TODO/fix: if this method is calcResult, should calc unconditionally. |
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.
interesting
please elaborate
* This will be not removed but slowly replaced with a method setInteractive | ||
* that will take care of enabling subTargetCheck and necessary object events. | ||
* There is too much attached to group interactivity to just be evaluated by a |
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 disagree
IMO there is no need
In some open PR I made interactive
include logically subTargetCheck
because that is how we use it so the code should reflect it.
It makes it easier to deal with and restores subTargetCheck
to what it originally was.
So the dev can flag interactive
and that is enough
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.
There is no need to register events for objects that will never move or be target of actions.
Group interactivity was put in as a part of the layout manager while it has nothing to do with layouting, nor layouting should take care of attaching event for detecting movements.
Those 2 parts now are strongly bound together and i can't think of my layout logic without having to think of the interaction and registration part.
Registering everything all the time doesn't bring performance issues, but is also completely unnecessary.
I am not against |
@ShaMan123 this is the version with the subclassed ActiveSelectionLayoutManager. Seems leaner but of course it isn't. |
Oh well is an option only at the cost of making subscribe public, because we need to register the event on the parent layout manager while the activeSelection won't have any real registration. |
There is still more work to do to have something that looks readable and is robust. |
Ok this should be ready to go now. Code size is larger than the initial approach but there is no workaround for that. |
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 reviewed the code.
The only thing that I think needs to change is the dirty flagging.
487a881
to
225432f
Compare
f0c5982
to
a610c2f
Compare
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.
@asturur please review
Should we extract the subscription logic before the release? It will be breaking. Up to you.
const event = { foo: 'bar' }; | ||
triggers.forEach((trigger) => as.fire(trigger, event)); | ||
expect(asPerformLayout).not.toHaveBeenCalled(); | ||
expect(groupPerformLayout.mock.calls).toMatchObject([ |
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 wanted to do the same for groupPerformLayout2
but it throws a weird error or max call stack exceeded
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.
It happened to me too, and there was a bug in the code in that moment. Maybe is worth double checking
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.
still happening
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.
Ok i ll look with fresh eyes, maybe there is a situation in which you get an infinite nested loop.
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.
no issues for me, it works fine
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 think you should have committed it
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 did i pushed up commits with the checks for groupPerformLayout2 mirrored to groupPerformLayout
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.
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.
Missed that! Great!
* Subscribe an object to transform events that will trigger a layout change on the parent | ||
* This is important only for interactive groups. | ||
* @param object | ||
* @param context | ||
*/ | ||
protected subscribe( | ||
object: FabricObject, | ||
context: RegistrationContext & Partial<StrictLayoutContext> |
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 think we should change the type for all subscription methods
context: RegistrationContext & Partial<StrictLayoutContext> | |
context: RegistrationContext |
e769487
to
4e061da
Compare
No. We just declare the LayoutManager as beta and amen. Do not extend it, do not touch it, it will change. |
4e061da
to
653a067
Compare
How can i see the changes from the latest commit? it gives me that everything was done 50 minutes ago |
view commits 6aa468c...HEAD |
The e2e test is failing because dirty flag is not being set for some reason |
Because you deleted the catch'all dirty flag. Indeed when reviewing i was thinking why do that |
I restored it, let's see if is that one. |
because it didn't make a difference from what I could see |
We talked about this, who is going to clear the cache for the groups that do not have a layout result like clipPath or fixed? Indeed the test pass now, and that cache invalidation was the part 2 of the 2 part fixes i discussed during the PR all the time, active selection events registration was just half of the fix. |
yes, weird. I restored it locally and re-ran. Maybe I forgot to build. |
Confirmed, all good |
@@ -231,7 +251,7 @@ export class LayoutManager { | |||
target.setPositionByOrigin(nextCenter, CENTER, CENTER); | |||
// invalidate | |||
target.setCoords(); | |||
target.set({ dirty: true }); | |||
target.set('dirty', true); |
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 can be removed now because of the one in onAfterLayout
Monday i will add some comments on the test because is a bit hard to remember why mock should be called or not and then i will merge. |
Description
This PR is supposed to help with the issue described in #9600
The strategy tried here is to:
This should allow for N groups to subscribe the same active selection to modify perform layout on the groups.
The test case in the vanilla template is a good way to play around with those changes