Open georeith opened 8 years ago
Yeah it is, nothing major just context can always be changed, especially with ES6 arrow methods and lexical scoping would be nice to have consistent access to that value. 99% of the time though this
is fine.
We could probably add it in a really simple way, through Emitter#emit()
. If there's an event object, it could set #currentTarget
to this
before emitting the event...
If that's too extreme (probably), then maybe the Event
classes that should behave this way could define a "private" property called #_updateTarget
, and if that's set to true
, it would do the above.
Sounds good.
One question now I've had time to think about it. Are we being consistent with the hitTest as sometimes its the this
value of the first triggered listener and other times its the highest item under the mouse no? I might just be misunderstanding the possible values of Event#target
though.
Perhaps we should just set Event#target
to whatever the first currentTarget
is and ensure it is always the same throughout. If its the view its the view, if it fires on a object before propagating to view then it is that, assuming we do child -> parent propagation and not just view (haven't actually tested that).
click on rectangle1:
click on rectangle2:
click on group:
click on view:
Unless I'm misunderstanding Event#target
there and it is always the highest visible item like the DOM (if so ignore this I'm being a numpty). This lets you do everything, determine that the event is propagated and from where, and if you need to delegate events just hitTest
the item with the event.point
to get the child it hit.
Yeah that's what I was wondering about too. So this whole hitTest()
/ getTarget()
magic wasn't even necessary. Oh well. : )
Ha sorry I can't really remember what we were going over before it might have already been suggested, was just running it over my head last night and seems like the most elegant without degrading performance. Also I messed up the naming a bit in that sketch this is a better one:
Yeah I think I had a misconception about what #target
should return, hence our endless disagreements and looping earlier in the year, probably. Apologies for that. I will revert the hitTest()
magic.
@lehni Not at all. I think we both just had a stressful day and felt we were right I was making assumptions on snippets of code, I know you were fed up with the events just having refactored them and rightly so, I know the feeling. I think we are on to a winner here though.
@georeith yeah I do remember some preexisting levels of stress that day, unrelated : )
I do like where we ended up here, code looks good! I wasn't keen on the hitTest()
hack.
It would be good to have unit tests in place for all this mouse event stuff... I guess we could just emulate prerecorded mouse sequences by the use of dispatchEvent()
?
That's what we've settled on doing with our unit tests. Here's what we've been using to dispatch events:
const scope = new paper.PaperScope();
const document = scope.document;
export const mouse = {
move(x, y) {
document.dispatchEvent(new window.MouseEvent('mousemove', {
clientX: x,
clientY: y,
}));
},
down(x, y) {
scope.project.view.element.dispatchEvent(new window.MouseEvent('mousedown', {
clientX: x,
clientY: y,
}));
},
up(x, y) {
document.dispatchEvent(new window.MouseEvent('mouseup', {
clientX: x,
clientY: y,
}));
},
};
@elliotbonneville that's great! We should do this too. What are you testing for this way?
@georeith now with event.currentTarget
and docs.
@georeith I've been juggling too many things this week, didn't realize we made a wrong assumption, and this was masked by the accidental removal of a code optimization that only executes the hit-test when it's sure to be needed. I've added this optimization back now (ab24f92373245754621bb156e340a79b894853b7), and it shows that we do need the hitTest()
getter magic. See this example:
https://jsfiddle.net/lehni/ou13xkzs/
The event is installed on the outmost <div>
only, yet event.target
points at whatever element is on top of the stack where you click, wether it subscribes to the event or not. This is what I wanted to achieve with this additional getTarget()
code, and also what I pointed out in your suggestion earlier this year as a short-coming.
I hope we can finally clear this confusion up : ) I think we have two options: Keep the code simple and always run a hit-test, wether we need it or not (slow), or bring back my getTarget()
getter that uses the already calculated hit-test result if it exists or performs a hit-test if it's the first time it's requested (depending on the value of hitItems
).
@lehni Indeed that is the HTML way. I was thinking we would only do top of stack if it subscribes to the event (which deviates from HTML but saves additional hit-testing) leaving it up to the developer to do it or not.
So what I was thinking was the first emitted callback sets event#target
to its owner. And any additional propagation has the same value just setting event#currentTarget
to its owner.
Although I'm happy with if we do match the HTML spec too. Just worried it will make mousemove listeners slow for scenes with a large number of items. Not sure how optimised hit-test itself is. Under what circumstances does the hit-test already exist? Originally I imagined you ran a hit-test only on items with listeners for that event.
Edit: It could be possible to run the full hit-test once, cache it and invalidate it whenever there is a geometrical change. Is that what the hit-test optimisation does? That would still be slow for what I imagine are common cases like mousemove based translation or resize which would invalidate it on every event though.
The way I had written is, the hit-test is only run when it's directly required (e.g. because of click
, mouseenter
, mouseleave
events), or when event.target
is first accessed. And per handling of event, the hit-test is never run more than once. So if you don't use event.target
, there is never any impact on performance. And if you do, then it is necessary to behave the same as HTML, which I think is the only logic way now really...
@lehni Ah ok I like that it is in a getter on event.target
. Although is it not possible that could be messed up? As in my event callback I move an item over event#point
and then access event#target
would it not wrongly report that item as being the event#target
?
I guess it would, but I guess we could live with that? : ) But yeah,you've got a point. Damn,...
@lehni Hmmm I'm just worried it would create more issues. Its a bit of a strange side effect to include in code, having to remember to read event.target
before modifying anything.
Yeah I agree.
Always run the test?
@lehni I guess we have to to match HTML spec.
@georeith matching the spec means always running the hit-test. We could also activate it by default but offer a flag that turns it off?
What I mean is: There could be a need for no "correct" item-level event support. It could be nice to be able to install events on the view only, and not have paper.js take care of event#target, for performance reasons.
@lehni Sounds good, I would just return null
or throw an error for the target if you switch it off instead of returning a possibly incorrect value.
Calling
event.stopPropagation()
does not stop an event propagating to the view.Also the event on the view has the target set as the view, although the original target was the item, is this done on purpose?
Edit: The issue is here:
Propagation to view seems to be handled by
emitMouseEvents
if the first two OR statements are false. However propagation detection is inemitMouseEvent
and uses a closuredMouseEvent
so the view receives a different one with the wrong target and without thestopped
property.