1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2015 Paul Lemire paul.lemire350@gmail.com |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "pickboundingvolumejob_p.h" |
41 | #include "qpicktriangleevent.h" |
42 | #include "qpicklineevent.h" |
43 | #include "qpickpointevent.h" |
44 | #include <Qt3DCore/private/qaspectmanager_p.h> |
45 | #include <Qt3DRender/qobjectpicker.h> |
46 | #include <Qt3DRender/qviewport.h> |
47 | #include <Qt3DRender/qgeometryrenderer.h> |
48 | #include <Qt3DRender/private/qobjectpicker_p.h> |
49 | #include <Qt3DRender/private/nodemanagers_p.h> |
50 | #include <Qt3DRender/private/entity_p.h> |
51 | #include <Qt3DRender/private/objectpicker_p.h> |
52 | #include <Qt3DRender/private/managers_p.h> |
53 | #include <Qt3DRender/private/geometryrenderer_p.h> |
54 | #include <Qt3DRender/private/rendersettings_p.h> |
55 | #include <Qt3DRender/private/trianglesvisitor_p.h> |
56 | #include <Qt3DRender/private/job_common_p.h> |
57 | #include <Qt3DRender/private/qpickevent_p.h> |
58 | #include <Qt3DRender/private/pickboundingvolumeutils_p.h> |
59 | |
60 | #include <QSurface> |
61 | #include <QWindow> |
62 | #include <QOffscreenSurface> |
63 | |
64 | QT_BEGIN_NAMESPACE |
65 | |
66 | namespace Qt3DRender { |
67 | using namespace Qt3DRender::RayCasting; |
68 | |
69 | namespace Render { |
70 | |
71 | class PickBoundingVolumeJobPrivate : public Qt3DCore::QAspectJobPrivate |
72 | { |
73 | public: |
74 | PickBoundingVolumeJobPrivate(PickBoundingVolumeJob *q) : q_ptr(q) { } |
75 | ~PickBoundingVolumeJobPrivate() override = default; |
76 | |
77 | bool isRequired() const override; |
78 | void postFrame(Qt3DCore::QAspectManager *manager) override; |
79 | |
80 | enum CustomEventType { |
81 | MouseButtonClick = QEvent::User, |
82 | }; |
83 | |
84 | struct EventDetails { |
85 | Qt3DCore::QNodeId pickerId; |
86 | int sourceEventType; |
87 | QPickEventPtr resultingEvent; |
88 | Qt3DCore::QNodeId viewportNodeId; |
89 | }; |
90 | |
91 | QVector<EventDetails> dispatches; |
92 | PickBoundingVolumeJob *q_ptr; |
93 | Q_DECLARE_PUBLIC(PickBoundingVolumeJob) |
94 | }; |
95 | |
96 | |
97 | bool PickBoundingVolumeJobPrivate::isRequired() const |
98 | { |
99 | Q_Q(const PickBoundingVolumeJob); |
100 | return !q->m_pendingMouseEvents.isEmpty() || q->m_pickersDirty || q->m_oneEnabledAtLeast; |
101 | } |
102 | |
103 | void PickBoundingVolumeJobPrivate::postFrame(Qt3DCore::QAspectManager *manager) |
104 | { |
105 | using namespace Qt3DCore; |
106 | QNodeId previousId; |
107 | QObjectPicker *node = nullptr; |
108 | |
109 | for (auto res: qAsConst(t&: dispatches)) { |
110 | if (previousId != res.pickerId) { |
111 | node = qobject_cast<QObjectPicker *>(object: manager->lookupNode(id: res.pickerId)); |
112 | previousId = res.pickerId; |
113 | } |
114 | if (!node) |
115 | continue; |
116 | |
117 | QObjectPickerPrivate *dnode = static_cast<QObjectPickerPrivate *>(QObjectPickerPrivate::get(q: node)); |
118 | |
119 | // resolve front end details |
120 | QPickEvent *pickEvent = res.resultingEvent.data(); |
121 | if (pickEvent) { |
122 | QPickEventPrivate *dpickEvent = QPickEventPrivate::get(object: pickEvent); |
123 | dpickEvent->m_viewport = static_cast<QViewport *>(manager->lookupNode(id: res.viewportNodeId)); |
124 | dpickEvent->m_entityPtr = static_cast<QEntity *>(manager->lookupNode(id: dpickEvent->m_entity)); |
125 | } |
126 | |
127 | // dispatch event |
128 | switch (res.sourceEventType) { |
129 | case QEvent::MouseButtonPress: |
130 | dnode->pressedEvent(event: pickEvent); |
131 | break; |
132 | case QEvent::MouseButtonRelease: |
133 | dnode->releasedEvent(event: pickEvent); |
134 | break; |
135 | case MouseButtonClick: |
136 | dnode->clickedEvent(event: pickEvent); |
137 | break; |
138 | case QEvent::MouseMove: |
139 | dnode->movedEvent(event: pickEvent); |
140 | break; |
141 | case QEvent::Enter: |
142 | emit node->entered(); |
143 | dnode->setContainsMouse(true); |
144 | break; |
145 | case QEvent::Leave: |
146 | dnode->setContainsMouse(false); |
147 | emit node->exited(); |
148 | break; |
149 | default: Q_UNREACHABLE(); |
150 | } |
151 | } |
152 | |
153 | dispatches.clear(); |
154 | } |
155 | |
156 | namespace { |
157 | |
158 | void setEventButtonAndModifiers(const QMouseEvent &event, QPickEvent::Buttons &eventButton, int &eventButtons, int &eventModifiers) |
159 | { |
160 | switch (event.button()) { |
161 | case Qt::LeftButton: |
162 | eventButton = QPickEvent::LeftButton; |
163 | break; |
164 | case Qt::RightButton: |
165 | eventButton = QPickEvent::RightButton; |
166 | break; |
167 | case Qt::MiddleButton: |
168 | eventButton = QPickEvent::MiddleButton; |
169 | break; |
170 | case Qt::BackButton: |
171 | eventButton = QPickEvent::BackButton; |
172 | break; |
173 | default: |
174 | break; |
175 | } |
176 | |
177 | if (event.buttons() & Qt::LeftButton) |
178 | eventButtons |= QPickEvent::LeftButton; |
179 | if (event.buttons() & Qt::RightButton) |
180 | eventButtons |= QPickEvent::RightButton; |
181 | if (event.buttons() & Qt::MiddleButton) |
182 | eventButtons |= QPickEvent::MiddleButton; |
183 | if (event.buttons() & Qt::BackButton) |
184 | eventButtons |= QPickEvent::BackButton; |
185 | if (event.modifiers() & Qt::ShiftModifier) |
186 | eventModifiers |= QPickEvent::ShiftModifier; |
187 | if (event.modifiers() & Qt::ControlModifier) |
188 | eventModifiers |= QPickEvent::ControlModifier; |
189 | if (event.modifiers() & Qt::AltModifier) |
190 | eventModifiers |= QPickEvent::AltModifier; |
191 | if (event.modifiers() & Qt::MetaModifier) |
192 | eventModifiers |= QPickEvent::MetaModifier; |
193 | if (event.modifiers() & Qt::KeypadModifier) |
194 | eventModifiers |= QPickEvent::KeypadModifier; |
195 | } |
196 | |
197 | } // anonymous |
198 | |
199 | PickBoundingVolumeJob::PickBoundingVolumeJob() |
200 | : AbstractPickingJob(*new PickBoundingVolumeJobPrivate(this)) |
201 | , m_pickersDirty(true) |
202 | { |
203 | SET_JOB_RUN_STAT_TYPE(this, JobTypes::PickBoundingVolume, 0) |
204 | } |
205 | |
206 | void PickBoundingVolumeJob::setRoot(Entity *root) |
207 | { |
208 | m_node = root; |
209 | } |
210 | |
211 | void PickBoundingVolumeJob::setMouseEvents(const QList<QPair<QObject*, QMouseEvent>> &pendingEvents) |
212 | { |
213 | m_pendingMouseEvents.append(t: pendingEvents); |
214 | } |
215 | |
216 | void PickBoundingVolumeJob::setKeyEvents(const QList<QKeyEvent> &pendingEvents) |
217 | { |
218 | m_pendingKeyEvents.append(t: pendingEvents); |
219 | } |
220 | |
221 | void PickBoundingVolumeJob::markPickersDirty() |
222 | { |
223 | m_pickersDirty = true; |
224 | } |
225 | |
226 | bool PickBoundingVolumeJob::runHelper() |
227 | { |
228 | // Move to clear the events so that we don't process them several times |
229 | // if run is called several times |
230 | const auto mouseEvents = std::move(m_pendingMouseEvents); |
231 | |
232 | // If we have no events return early |
233 | if (mouseEvents.empty()) |
234 | return false; |
235 | |
236 | // Quickly look which picker settings we've got |
237 | if (m_pickersDirty) { |
238 | m_pickersDirty = false; |
239 | m_oneEnabledAtLeast = false; |
240 | m_oneHoverAtLeast = false; |
241 | |
242 | const auto activeHandles = m_manager->objectPickerManager()->activeHandles(); |
243 | for (const auto &handle : activeHandles) { |
244 | auto picker = m_manager->objectPickerManager()->data(handle); |
245 | m_oneEnabledAtLeast |= picker->isEnabled(); |
246 | m_oneHoverAtLeast |= picker->isHoverEnabled(); |
247 | if (m_oneEnabledAtLeast && m_oneHoverAtLeast) |
248 | break; |
249 | } |
250 | } |
251 | |
252 | // bail out early if no picker is enabled |
253 | if (!m_oneEnabledAtLeast) |
254 | return false; |
255 | |
256 | bool hasMoveEvent = false; |
257 | bool hasOtherEvent = false; |
258 | // Quickly look which types of events we've got |
259 | for (const auto &event : mouseEvents) { |
260 | const bool isMove = (event.second.type() == QEvent::MouseMove); |
261 | hasMoveEvent |= isMove; |
262 | hasOtherEvent |= !isMove; |
263 | } |
264 | |
265 | // In the case we have a move event, find if we actually have |
266 | // an object picker that cares about these |
267 | if (!hasOtherEvent) { |
268 | // Retrieve the last used object picker |
269 | ObjectPicker *lastCurrentPicker = m_manager->objectPickerManager()->data(handle: m_currentPicker); |
270 | |
271 | // The only way to set lastCurrentPicker is to click |
272 | // so we can return since if we're there it means we |
273 | // have only move events. But keep on if hover support |
274 | // is needed |
275 | if (lastCurrentPicker == nullptr && !m_oneHoverAtLeast) |
276 | return false; |
277 | |
278 | const bool caresAboutMove = (hasMoveEvent && |
279 | (m_oneHoverAtLeast || |
280 | (lastCurrentPicker && lastCurrentPicker->isDragEnabled()))); |
281 | // Early return if the current object picker doesn't care about move events |
282 | if (!caresAboutMove) |
283 | return false; |
284 | } |
285 | |
286 | PickingUtils::ViewportCameraAreaGatherer vcaGatherer; |
287 | // TO DO: We could cache this and only gather when we know the FrameGraph tree has changed |
288 | const QVector<PickingUtils::ViewportCameraAreaDetails> vcaDetails = vcaGatherer.gather(root: m_frameGraphRoot); |
289 | |
290 | // If we have no viewport / camera or area, return early |
291 | if (vcaDetails.empty()) |
292 | return false; |
293 | |
294 | // TO DO: |
295 | // If we have move or hover move events that someone cares about, we try to avoid expensive computations |
296 | // by compressing them into a single one |
297 | |
298 | const bool trianglePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::TrianglePicking); |
299 | const bool edgePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::LinePicking); |
300 | const bool pointPickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::PointPicking); |
301 | const bool primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested; |
302 | const bool frontFaceRequested = |
303 | m_renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace; |
304 | const bool backFaceRequested = |
305 | m_renderSettings->faceOrientationPickingMode() != QPickingSettings::FrontFace; |
306 | const float pickWorldSpaceTolerance = m_renderSettings->pickWorldSpaceTolerance(); |
307 | |
308 | // For each mouse event |
309 | for (const auto &event : mouseEvents) { |
310 | m_hoveredPickersToClear = m_hoveredPickers; |
311 | |
312 | QPickEvent::Buttons eventButton = QPickEvent::NoButton; |
313 | int eventButtons = 0; |
314 | int eventModifiers = QPickEvent::NoModifier; |
315 | |
316 | setEventButtonAndModifiers(event: event.second, eventButton, eventButtons, eventModifiers); |
317 | |
318 | // For each Viewport / Camera and Area entry |
319 | for (const PickingUtils::ViewportCameraAreaDetails &vca : vcaDetails) { |
320 | PickingUtils::HitList sphereHits; |
321 | QRay3D ray = rayForViewportAndCamera(vca, eventSource: event.first, pos: event.second.pos()); |
322 | if (!ray.isValid()) { |
323 | // An invalid rays is when we've lost our surface or the mouse |
324 | // has moved out of the viewport In case of a button released |
325 | // outside of the viewport, we still want to notify the |
326 | // lastCurrent entity about this. |
327 | dispatchPickEvents(event: event.second, sphereHits: PickingUtils::HitList(), eventButton, eventButtons, eventModifiers, allHitsRequested: m_renderSettings->pickResultMode(), |
328 | viewportNodeId: vca.viewportNodeId); |
329 | continue; |
330 | } |
331 | |
332 | PickingUtils::HierarchicalEntityPicker entityPicker(ray); |
333 | entityPicker.setLayerFilterIds(vca.layersFilters); |
334 | if (entityPicker.collectHits(manager: m_manager, root: m_node)) { |
335 | if (trianglePickingRequested) { |
336 | PickingUtils::TriangleCollisionGathererFunctor gathererFunctor; |
337 | gathererFunctor.m_frontFaceRequested = frontFaceRequested; |
338 | gathererFunctor.m_backFaceRequested = backFaceRequested; |
339 | gathererFunctor.m_manager = m_manager; |
340 | gathererFunctor.m_ray = ray; |
341 | gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); |
342 | sphereHits << gathererFunctor.computeHits(entities: entityPicker.entities(), mode: m_renderSettings->pickResultMode()); |
343 | } |
344 | if (edgePickingRequested) { |
345 | PickingUtils::LineCollisionGathererFunctor gathererFunctor; |
346 | gathererFunctor.m_manager = m_manager; |
347 | gathererFunctor.m_ray = ray; |
348 | gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; |
349 | gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); |
350 | sphereHits << gathererFunctor.computeHits(entities: entityPicker.entities(), mode: m_renderSettings->pickResultMode()); |
351 | PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits); |
352 | } |
353 | if (pointPickingRequested) { |
354 | PickingUtils::PointCollisionGathererFunctor gathererFunctor; |
355 | gathererFunctor.m_manager = m_manager; |
356 | gathererFunctor.m_ray = ray; |
357 | gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; |
358 | gathererFunctor.m_entityToPriorityTable = entityPicker.entityToPriorityTable(); |
359 | sphereHits << gathererFunctor.computeHits(entities: entityPicker.entities(), mode: m_renderSettings->pickResultMode()); |
360 | PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits); |
361 | } |
362 | if (!primitivePickingRequested) { |
363 | sphereHits << entityPicker.hits(); |
364 | PickingUtils::AbstractCollisionGathererFunctor::sortHits(results&: sphereHits); |
365 | if (m_renderSettings->pickResultMode() != QPickingSettings::AllPicks) |
366 | sphereHits = { sphereHits.front() }; |
367 | } |
368 | } |
369 | |
370 | // Dispatch events based on hit results |
371 | dispatchPickEvents(event: event.second, sphereHits, eventButton, eventButtons, eventModifiers, allHitsRequested: m_renderSettings->pickResultMode(), |
372 | viewportNodeId: vca.viewportNodeId); |
373 | } |
374 | } |
375 | |
376 | // Clear Hovered elements that needs to be cleared |
377 | // Send exit event to object pickers on which we |
378 | // had set the hovered flag for a previous frame |
379 | // and that aren't being hovered any longer |
380 | clearPreviouslyHoveredPickers(); |
381 | return true; |
382 | } |
383 | |
384 | void PickBoundingVolumeJob::dispatchPickEvents(const QMouseEvent &event, |
385 | const PickingUtils::HitList &sphereHits, |
386 | QPickEvent::Buttons eventButton, |
387 | int eventButtons, |
388 | int eventModifiers, |
389 | bool allHitsRequested, |
390 | Qt3DCore::QNodeId viewportNodeId) |
391 | { |
392 | Q_D(PickBoundingVolumeJob); |
393 | |
394 | ObjectPicker *lastCurrentPicker = m_manager->objectPickerManager()->data(handle: m_currentPicker); |
395 | // If we have hits |
396 | if (!sphereHits.isEmpty()) { |
397 | // Note: how can we control that we want the first/last/all elements along the ray to be picked |
398 | |
399 | // How do we differentiate betwnee an Entity with no GeometryRenderer and one with one, both having |
400 | // an ObjectPicker component when it comes |
401 | |
402 | for (const QCollisionQueryResult::Hit &hit : qAsConst(t: sphereHits)) { |
403 | Entity *entity = m_manager->renderNodesManager()->lookupResource(id: hit.m_entityId); |
404 | HObjectPicker objectPickerHandle = entity->componentHandle<ObjectPicker>(); |
405 | |
406 | // If the Entity which actually received the hit doesn't have |
407 | // an object picker component, we need to check the parent if it has one ... |
408 | while (objectPickerHandle.isNull() && entity != nullptr) { |
409 | entity = entity->parent(); |
410 | if (entity != nullptr) |
411 | objectPickerHandle = entity->componentHandle<ObjectPicker>(); |
412 | } |
413 | |
414 | ObjectPicker *objectPicker = m_manager->objectPickerManager()->data(handle: objectPickerHandle); |
415 | if (objectPicker != nullptr && objectPicker->isEnabled()) { |
416 | |
417 | if (lastCurrentPicker && !allHitsRequested) { |
418 | // if there is a current picker, it will "grab" all events until released. |
419 | // Clients should test that entity is what they expect (or only use |
420 | // world coordinates) |
421 | objectPicker = lastCurrentPicker; |
422 | } |
423 | |
424 | // Send the corresponding event |
425 | Vector3D localIntersection = hit.m_intersection; |
426 | if (entity && entity->worldTransform()) |
427 | localIntersection = entity->worldTransform()->inverted() * hit.m_intersection; |
428 | |
429 | QPickEventPtr pickEvent; |
430 | switch (hit.m_type) { |
431 | case QCollisionQueryResult::Hit::Triangle: |
432 | pickEvent.reset(t: new QPickTriangleEvent(event.localPos(), |
433 | convertToQVector3D(v: hit.m_intersection), |
434 | convertToQVector3D(v: localIntersection), |
435 | hit.m_distance, |
436 | hit.m_primitiveIndex, |
437 | hit.m_vertexIndex[0], |
438 | hit.m_vertexIndex[1], |
439 | hit.m_vertexIndex[2], |
440 | eventButton, eventButtons, |
441 | eventModifiers, |
442 | convertToQVector3D(v: hit.m_uvw))); |
443 | break; |
444 | case QCollisionQueryResult::Hit::Edge: |
445 | pickEvent.reset(t: new QPickLineEvent(event.localPos(), |
446 | convertToQVector3D(v: hit.m_intersection), |
447 | convertToQVector3D(v: localIntersection), |
448 | hit.m_distance, |
449 | hit.m_primitiveIndex, |
450 | hit.m_vertexIndex[0], hit.m_vertexIndex[1], |
451 | eventButton, eventButtons, eventModifiers)); |
452 | break; |
453 | case QCollisionQueryResult::Hit::Point: |
454 | pickEvent.reset(t: new QPickPointEvent(event.localPos(), |
455 | convertToQVector3D(v: hit.m_intersection), |
456 | convertToQVector3D(v: localIntersection), |
457 | hit.m_distance, |
458 | hit.m_vertexIndex[0], |
459 | eventButton, eventButtons, eventModifiers)); |
460 | break; |
461 | case QCollisionQueryResult::Hit::Entity: |
462 | pickEvent.reset(t: new QPickEvent(event.localPos(), |
463 | convertToQVector3D(v: hit.m_intersection), |
464 | convertToQVector3D(v: localIntersection), |
465 | hit.m_distance, |
466 | eventButton, eventButtons, eventModifiers)); |
467 | break; |
468 | default: |
469 | Q_UNREACHABLE(); |
470 | } |
471 | Qt3DRender::QPickEventPrivate::get(object: pickEvent.data())->m_entity = hit.m_entityId; |
472 | switch (event.type()) { |
473 | case QEvent::MouseButtonPress: { |
474 | // Store pressed object handle |
475 | m_currentPicker = objectPickerHandle; |
476 | m_currentViewport = viewportNodeId; |
477 | // Send pressed event to m_currentPicker |
478 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), .sourceEventType: event.type(), .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
479 | objectPicker->setPressed(true); |
480 | break; |
481 | } |
482 | |
483 | case QEvent::MouseButtonRelease: { |
484 | // Only send the release event if it was pressed |
485 | if (objectPicker->isPressed()) { |
486 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), .sourceEventType: event.type(), .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
487 | objectPicker->setPressed(false); |
488 | } |
489 | if (lastCurrentPicker != nullptr && m_currentPicker == objectPickerHandle) { |
490 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), |
491 | .sourceEventType: PickBoundingVolumeJobPrivate::MouseButtonClick, |
492 | .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
493 | m_currentPicker = HObjectPicker(); |
494 | m_currentViewport = {}; |
495 | } |
496 | break; |
497 | } |
498 | #if QT_CONFIG(gestures) |
499 | case QEvent::Gesture: { |
500 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), |
501 | .sourceEventType: PickBoundingVolumeJobPrivate::MouseButtonClick, |
502 | .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
503 | break; |
504 | } |
505 | #endif |
506 | case QEvent::MouseMove: { |
507 | if ((objectPicker->isPressed() || objectPicker->isHoverEnabled()) && objectPicker->isDragEnabled()) |
508 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), .sourceEventType: event.type(), .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
509 | Q_FALLTHROUGH(); // fallthrough |
510 | } |
511 | case QEvent::HoverMove: { |
512 | if (!m_hoveredPickers.contains(t: objectPickerHandle)) { |
513 | if (objectPicker->isHoverEnabled()) { |
514 | // Send entered event to objectPicker |
515 | d->dispatches.push_back(t: {.pickerId: objectPicker->peerId(), .sourceEventType: QEvent::Enter, .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
516 | // and save it in the hoveredPickers |
517 | m_hoveredPickers.push_back(t: objectPickerHandle); |
518 | } |
519 | } |
520 | break; |
521 | } |
522 | |
523 | default: |
524 | break; |
525 | } |
526 | } |
527 | |
528 | // The ObjectPicker was hit -> it is still being hovered |
529 | m_hoveredPickersToClear.removeAll(t: objectPickerHandle); |
530 | |
531 | lastCurrentPicker = m_manager->objectPickerManager()->data(handle: m_currentPicker); |
532 | } |
533 | |
534 | // Otherwise no hits |
535 | } else { |
536 | switch (event.type()) { |
537 | case QEvent::MouseButtonRelease: { |
538 | // Send release event to m_currentPicker |
539 | if (lastCurrentPicker != nullptr && m_currentViewport == viewportNodeId) { |
540 | m_currentPicker = HObjectPicker(); |
541 | m_currentViewport = {}; |
542 | QPickEventPtr pickEvent(new QPickEvent); |
543 | lastCurrentPicker->setPressed(false); |
544 | d->dispatches.push_back(t: {.pickerId: lastCurrentPicker->peerId(), .sourceEventType: event.type(), .resultingEvent: pickEvent, .viewportNodeId: viewportNodeId}); |
545 | } |
546 | break; |
547 | } |
548 | default: |
549 | break; |
550 | } |
551 | } |
552 | } |
553 | |
554 | void PickBoundingVolumeJob::clearPreviouslyHoveredPickers() |
555 | { |
556 | Q_D(PickBoundingVolumeJob); |
557 | |
558 | for (const HObjectPicker &pickHandle : qAsConst(t&: m_hoveredPickersToClear)) { |
559 | ObjectPicker *pick = m_manager->objectPickerManager()->data(handle: pickHandle); |
560 | if (pick) |
561 | d->dispatches.push_back(t: {.pickerId: pick->peerId(), .sourceEventType: QEvent::Leave, .resultingEvent: {}, .viewportNodeId: {}}); |
562 | m_hoveredPickers.removeAll(t: pickHandle); |
563 | } |
564 | |
565 | m_hoveredPickersToClear.clear(); |
566 | } |
567 | |
568 | } // namespace Render |
569 | |
570 | } // namespace Qt3DRender |
571 | |
572 | QT_END_NAMESPACE |
573 | |