aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2021-02-25 18:20:54 +0100
committerShawn Rutledge <shawn.rutledge@qt.io>2021-03-04 10:20:08 +0100
commit518bfcc61d15e3dc7692b360646d5838dcc9ae32 (patch)
tree9ad4739af9501f0df6179d0699b2049817767519
parentaa23f786dee088480cd5b25a3e3ff7405d4c4b1c (diff)
Fix click-to-focus in items within subscenes; improve logging
This fixes the ability to focus an item by clicking (such as a TextInput) when it's part of a subscene mapped to a 3D object in Qt Quick 3D, even if there are multiple subscenes with focusable items. QQuickDeliveryAgentPrivate::setFocusInScope(subsceneRoot, textInput) for example did not succeed; for one thing, this check fails: // Does this change the active focus? if (item == rootItem || scopePrivate->activeFocus) { because in a 3D scene, so far the viewport has focus by default, so the given scope (subscene root) does not have active focus. Each window ultimately has one actively focused item, so we need to delegate to the delivery agent belonging to the window's root item, to set its active focus. It's not even clear whether it's really a good idea for each subscene delivery agent to have its own QQuickDeliveryAgentPrivate::activeFocusItem. It might give us flexibility: perhaps each subscene root item should be a focus scope, and each delivery agent should decide which item would be hypothetically focused if the whole subscene got focus. But for now, it seems enough to set the activeFocusItem on the root item of the whole scene. Another problem was that when QQuickItem::forceActiveFocus() goes up the parent hierarchy, it didn't find a focus scope. So when you clicked on a TextInput that already had its focus property set to true, 1) QQuickItem::setFocus() does nothing because d->focus is already set 2) QQuickItem::forceActiveFocus() did not find a parent focus scope Therefore neither of them changed anything, and it wouldn't get active focus. Setting the ItemIsFocusScope flag on each subscene root item fixes (2), and that seems to be enough for now. Another problem was that QQuickWindow::event() was calling QQuickDeliveryAgent::grabberAgent() even when delivering an event that is NOT a press event, in spite of the comment "When delivering _update_ and _release_ events to existing grabbers, use the subscene delivery agent, if any." A hover event often results in a HoverHandler getting a passive grab, but that passive grab is not a reason to deliver a subsequent press event to the same subscene. When the mouse moves, we need to start over with picking in the 3D scene. When the 60fps frame-synchronous hover event occurs, we need to start over with picking, in case 3D objects are being animated under the cursor. When a press occurs, we need to start over with picking in case the press occurs in a different location from the last hover (even though that's unlikely with a mouse, it happens easily with a touchscreen). Another problem was that QQuickItemPrivate::deliveryAgent() was not finding the subscene DA during delivery of key events. A child item's extra is often not allocated, but we still need to keep looking at the parents. The optimization from 68c103225f4e8bd6c1b18ef547108fd60f398c0f was also wrong: after an Item's default initialization, we always need to do the search for the subcene DA. Only if we are sure that the item is NOT in a subscene (as in all normal 2D scenes) we can avoid doing that search next time. Consolidate the number of lines of output in the qt.quick.focus logging category and show the activeFocusItem transition. As with most logging in Qt Quick, it's expected that you set QT_MESSAGE_PATTERN to include %{function} so that you can always see where each line comes from. Therefore the log output itself has only minimal context (as in "q focus item x in scope y") rather than repeating the function name. Change-Id: I1b2a989c02c58c966653f965c0de512aa641bb99 Reviewed-by: Andy Nichols <andy.nichols@qt.io> (cherry picked from commit 543598a6cc07d67e7651c9f65c058465ea6d8425) Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/quick/items/qquickitem.cpp43
-rw-r--r--src/quick/items/qquickitem_p.h4
-rw-r--r--src/quick/items/qquickwindow.cpp2
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp41
4 files changed, 67 insertions, 23 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index f4eea2923a..cab7364919 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -3196,7 +3196,7 @@ QQuickItemPrivate::QQuickItemPrivate()
, replayingPressEvent(false)
, touchEnabled(false)
, hasCursorHandler(false)
- , hasSubsceneDeliveryAgent(false)
+ , maybeHasSubsceneDeliveryAgent(true)
, dirtyAttributes(0)
, nextDirtyItem(nullptr)
, prevDirtyItem(nullptr)
@@ -5202,22 +5202,47 @@ QPointF QQuickItemPrivate::adjustedPosForTransform(const QPointF &centroidParent
Returns the delivery agent for the narrowest subscene containing this item,
but falls back to QQuickWindowPrivate::deliveryAgent if there are no subscenes.
+ If this item is not sure whether it's in a subscene (as by default), we need to
+ explore the parents to find out.
+
+ If this item is in a subscene, we will find that DA during the exploration,
+ and return it.
+
+ If we find the root item without finding a DA, then we know that this item
+ does NOT belong to a subscene, so we remember that by setting
+ maybeHasSubsceneDeliveryAgent to false, so that exploration of the parents
+ can be avoided next time.
+
+ In the usual case in normal 2D scenes without subscenes,
+ maybeHasSubsceneDeliveryAgent gets set to false here.
+
\note When a Qt Quick scene is shown in the usual way in its own window,
subscenes are ignored, and QQuickWindowPrivate::deliveryAgent is used.
Subscene delivery agents are used only in QtQuick 3D so far.
*/
QQuickDeliveryAgent *QQuickItemPrivate::deliveryAgent()
{
- // optimization: don't go up the parent hierarchy if this item is not aware of being in a subscene
- if (hasSubsceneDeliveryAgent) {
+ Q_Q(QQuickItem);
+ if (maybeHasSubsceneDeliveryAgent) {
QQuickItemPrivate *p = this;
do {
+ if (qmlobject_cast<QQuickRootItem *>(p->q_ptr)) {
+ // found the root item without finding a different DA:
+ // it means we don't need to repeat this search next time.
+ // TODO maybe optimize further: make this function recursive, and
+ // set it to false on each item that we visit in the tail
+ maybeHasSubsceneDeliveryAgent = false;
+ break;
+ }
if (p->extra.isAllocated()) {
if (auto da = p->extra->subsceneDeliveryAgent)
return da;
- p = p->parentItem ? QQuickItemPrivate::get(p->parentItem) : nullptr;
}
+ p = p->parentItem ? QQuickItemPrivate::get(p->parentItem) : nullptr;
} while (p);
+ // arriving here is somewhat unexpected: a detached root can easily be created (just set an item's parent to null),
+ // but why would we deliver events to that subtree? only if root got detached while an item in that subtree still has a grab?
+ qCDebug(lcPtr) << "detached root of" << q << "is not a QQuickRootItem and also does not have its own DeliveryAgent";
}
if (window)
return QQuickWindowPrivate::get(window)->deliveryAgent;
@@ -5231,7 +5256,7 @@ QQuickDeliveryAgentPrivate *QQuickItemPrivate::deliveryAgentPrivate()
}
/*! \internal
- Ensures that this item, presumably the root of a sub-scene (e.g. because it
+ Ensures that this item, presumably the root of a subscene (e.g. because it
is mapped onto a 3D object in Qt Quick 3D), has a delivery agent to be used
when delivering events to the subscene: i.e. when the viewport delivers an
event to the subscene, or when the outer delivery agent delivers an update
@@ -5241,11 +5266,17 @@ QQuickDeliveryAgentPrivate *QQuickItemPrivate::deliveryAgentPrivate()
QQuickDeliveryAgent *QQuickItemPrivate::ensureSubsceneDeliveryAgent()
{
Q_Q(QQuickItem);
- hasSubsceneDeliveryAgent = true;
+ // We are (about to be) sure that it has one now; but just to save space,
+ // we avoid storing a DA pointer in each item; so deliveryAgent() always needs to
+ // go up the hierarchy to find it. maybeHasSubsceneDeliveryAgent tells it to do that.
+ maybeHasSubsceneDeliveryAgent = true;
if (extra.isAllocated() && extra->subsceneDeliveryAgent)
return extra->subsceneDeliveryAgent;
extra.value().subsceneDeliveryAgent = new QQuickDeliveryAgent(q);
qCDebug(lcPtr) << "created new" << extra->subsceneDeliveryAgent;
+ // every subscene root needs to be a focus scope so that when QQuickItem::forceActiveFocus()
+ // goes up the parent hierarchy, it finds the subscene root and calls setFocus() on it
+ q->setFlag(QQuickItem::ItemIsFocusScope);
return extra->subsceneDeliveryAgent;
}
diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h
index caa6edb15b..99cd3a3269 100644
--- a/src/quick/items/qquickitem_p.h
+++ b/src/quick/items/qquickitem_p.h
@@ -482,8 +482,8 @@ public:
bool replayingPressEvent:1;
bool touchEnabled:1;
bool hasCursorHandler:1;
- // set true when this item expects events via a subscene delivery agent; false otherwise
- bool hasSubsceneDeliveryAgent:1;
+ // set true when this item does not expect events via a subscene delivery agent; false otherwise
+ bool maybeHasSubsceneDeliveryAgent:1;
enum DirtyType {
TransformOrigin = 0x00000001,
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 643e47c8a0..d71e6ec1a5 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -1386,7 +1386,7 @@ bool QQuickWindow::event(QEvent *e)
}
if (ret)
return true;
- } else if (pe->pointCount()) {
+ } else if (pe->pointCount() && !pe->isBeginEvent()) {
// single-point event
if (auto *ptda = QQuickDeliveryAgent::grabberAgent(pe, pe->points().first()))
da = ptda;
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp
index 91d42412fb..1302f2825c 100644
--- a/src/quick/util/qquickdeliveryagent.cpp
+++ b/src/quick/util/qquickdeliveryagent.cpp
@@ -344,15 +344,13 @@ static inline bool singleWindowOnScreen(QQuickWindow *win)
void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item,
Qt::FocusReason reason, FocusOptions options)
{
+ Q_Q(QQuickDeliveryAgent);
Q_ASSERT(item);
Q_ASSERT(scope || item == rootItem);
- qCDebug(lcFocus) << "QQuickDeliveryAgentPrivate::setFocusInScope():";
- qCDebug(lcFocus) << " scope:" << (QObject *)scope;
+ qCDebug(lcFocus) << q << "focus" << item << "in scope" << scope;
if (scope)
- qCDebug(lcFocus) << " scopeSubFocusItem:" << (QObject *)QQuickItemPrivate::get(scope)->subFocusItem;
- qCDebug(lcFocus) << " item:" << (QObject *)item;
- qCDebug(lcFocus) << " activeFocusItem:" << (QObject *)activeFocusItem;
+ qCDebug(lcFocus) << " scopeSubFocusItem:" << QQuickItemPrivate::get(scope)->subFocusItem;
QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(scope) : nullptr;
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
@@ -457,17 +455,23 @@ void QQuickDeliveryAgentPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *
if (!changed.isEmpty())
notifyFocusChangesRecur(changed.data(), changed.count() - 1);
+ if (isSubsceneAgent) {
+ auto da = QQuickWindowPrivate::get(rootItem->window())->deliveryAgent;
+ qCDebug(lcFocus) << " delegating setFocusInScope to" << da;
+ QQuickWindowPrivate::get(rootItem->window())->deliveryAgentPrivate()->setFocusInScope(da->rootItem(), item, reason, options);
+ }
+ if (oldActiveFocusItem == activeFocusItem)
+ qCDebug(lcFocus) << " activeFocusItem remains" << activeFocusItem << "in" << q;
+ else
+ qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
}
void QQuickDeliveryAgentPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options)
{
Q_ASSERT(item);
Q_ASSERT(scope || item == rootItem);
-
- qCDebug(lcFocus) << "QQuickDeliveryAgentPrivate::clearFocusInScope():";
- qCDebug(lcFocus) << " scope:" << (QObject *)scope;
- qCDebug(lcFocus) << " item:" << (QObject *)item;
- qCDebug(lcFocus) << " activeFocusItem:" << (QObject *)activeFocusItem;
+ Q_Q(QQuickDeliveryAgent);
+ qCDebug(lcFocus) << q << "clear focus" << item << "in scope" << scope;
QQuickItemPrivate *scopePrivate = nullptr;
if (scope) {
@@ -547,6 +551,11 @@ void QQuickDeliveryAgentPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem
if (!changed.isEmpty())
notifyFocusChangesRecur(changed.data(), changed.count() - 1);
+
+ if (oldActiveFocusItem == activeFocusItem)
+ qCDebug(lcFocus) << "activeFocusItem remains" << activeFocusItem << "in" << q;
+ else
+ qCDebug(lcFocus) << " activeFocusItem" << oldActiveFocusItem << "->" << activeFocusItem << "in" << q;
}
void QQuickDeliveryAgentPrivate::clearFocusObject()
@@ -1403,8 +1412,8 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
auto itemPriv = QQuickItemPrivate::get(handler->parentItem());
// An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
// whereas the subscene root item already knows it has its own DA.
- if (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)
- itemPriv->hasSubsceneDeliveryAgent = grabGained;
+ if (grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
+ itemPriv->maybeHasSubsceneDeliveryAgent = true;
subsceneAgent = itemPriv->deliveryAgent();
}
} else {
@@ -1447,8 +1456,8 @@ void QQuickDeliveryAgentPrivate::onGrabChanged(QObject *grabber, QPointingDevice
auto itemPriv = QQuickItemPrivate::get(grabberItem);
// An item that is NOT a subscene root needs to track whether it got a grab via a subscene delivery agent,
// whereas the subscene root item already knows it has its own DA.
- if (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent)
- itemPriv->hasSubsceneDeliveryAgent = grabGained;
+ if (grabGained && (!itemPriv->extra.isAllocated() || !itemPriv->extra->subsceneDeliveryAgent))
+ itemPriv->maybeHasSubsceneDeliveryAgent = true;
subsceneAgent = itemPriv->deliveryAgent();
}
}
@@ -1726,6 +1735,10 @@ bool QQuickDeliveryAgentPrivate::deliverPressOrReleaseEvent(QPointerEvent *event
}
for (QQuickItem *item : targetItems) {
+ // failsafe: when items get into a subscene somehow, ensure that QQuickItemPrivate::deliveryAgent() can find it
+ if (isSubsceneAgent)
+ QQuickItemPrivate::get(item)->maybeHasSubsceneDeliveryAgent = true;
+
hasFiltered.clear();
if (!handlersOnly && sendFilteredPointerEvent(event, item)) {
if (event->isAccepted())