Cascades touch propagation

When a user touches a UI control in your app, the Cascades framework delivers a touch event to that control, and you can handle the event in any way you want. These types of interactions are straightforward: users touch the control that they want to interact with, and you specify your app's response based on this intention.

What might not be obvious is that other controls in your app can also receive the touch event and have the opportunity to respond to it. This behavior is called touch propagation and determines how and when the controls in your app receive events. Objects in Cascades are arranged in a hierarchical structure, with parent/child relationships. Consider a  Container  that includes two Button controls:

Container {
    Button {
        text: "Button 1"
    }
 
    Button {
        text: "Button 2"
    }
}

The Container is the parent of the Button controls, and the Button controls are children of the Container. Cascades represents this relationship by using a structure called a scene graph. This graph is a tree of controls (also called nodes) that shows the parent/child relationships between all elements in your app. You can't view the scene graph for your app in the Momentics IDE or anywhere else; it's just a concept that makes it easier to visualize how Cascades tracks object relationships internally.

Here's what the scene graph would look like for the code sample above:


Diagram showing the scene graph for the given example.

When a user touches a control and a touch event is generated, the control that was touched isn't the only control that receives the event. The parent control (and its parent, and so on, up to the root of the scene graph) also receives the event and can act on it. In the example above, if a user touches one of the Button controls, both the Button and its parent Container receive the touch event.

Propagation phases

When a touch event occurs and is delivered to various parent and child nodes in the scene graph, the nodes don't receive the event simultaneously. There are three phases of touch propagation, and each phase gives different nodes the opportunity to handle events at different times:

  • Capture phase
  • At-target phase
  • Bubble phase

Capture phase

This phase allows parent nodes to listen for and respond to events that are intended for one of their children. Parent nodes are the first nodes to receive the event, even before the child node that is targeted by the event. You can handle an event in this phase when you want to intercept the event before it reaches the node that was targeted. Consider the following scene graph:


Diagram showing a scene graph for a Container parent with a Label child.

If a user touches the  Label , its parent  Container  receives the event first in the capture phase. When a control receives an event in this phase, it emits the  touchCapture()  signal to give you the opportunity to handle the event.

Only nodes in the scene graph that have children receive events in the capture phase. Leaf nodes, which are nodes that don't have children, don't receive events in this phase.

Here's how to handle an event in the capture phase in QML. The text of the Label changes to indicate when an event is received in the capture phase. It's important to note that even if the touchCapture() signal is emitted, the parent Container also emits the  touch()  signal in its bubble phase. A custom captured property is used to indicate that an event occurred in the capture phase and the Label text shouldn't be updated when the touch() signal is emitted in the bubble phase.

import bb.cascades 1.0
 
Page {
    Container {
        id: captureContainer
         
        // This property indicates whether this container has received
        // a touch event in the capture phase
        property bool captured: false
        Label {
            id: captureLabel
            text: "Not captured"
        }
         
        onTouch: {
            // If the touch event wasn't already received in the
            // capture phase, update the text of the label
            if (!captureContainer.captured)
                captureLabel.text = "Not captured";
            captureContainer.captured = false;
        }
         
        onTouchCapture: {
            // If the touch event is received in the capture phase,
            // update the label text and set the captured property
            // to true (to prevent the text from being updated in
            // the onTouch signal handler)
            captureLabel.text = "Captured!"
            captureContainer.captured = true;
        }
    }
}
// In your application's source file
 
// Create the root page and top-level container
Page* root = new Page;
Container* topContainer = new Container;
 
// Create the label and add it to the top-level container
captureLabel = Label::create("Not captured");
topContainer->add(captureLabel);
 
// Connect the signals of the top-level container to slots.
// If any Q_ASSERT statement(s) indicate that the slot failed
// to connect to the signal, make sure you know exactly why
// this has happened. This is not normal, and will cause your
// app to stop working
bool res = QObject::connect(topContainer,
                            SIGNAL(touch(bb::cascades::TouchEvent*)),
                            this,
                            SLOT(onTouch(bb::cascades::TouchEvent*)));

// This is only available in Debug builds
Q_ASSERT(res);

res = QObject::connect(topContainer,
                     SIGNAL(touchCapture(bb::cascades::TouchEvent*)),
                     this,
                     SLOT(onTouchCapture(bb::cascades::TouchEvent*)));

// This is only available in Debug builds
Q_ASSERT(res);
 
 // Since the variable is not used in the app, this is added to avoid
 // a compiler warning
Q_UNUSED(res);
 
// Initialize the variable that indicates whether the container has
// received an event in the capture phase
captured = false;
 
// Set the content of the page and display it
root->setContent(topContainer);
app->setScene(root);
 
...
 
// Define the slot function for the touch() signal
void App::onTouch(bb::cascades::TouchEvent* event)
{
    Q_UNUSED(event);
 
    // If the touch event wasn't already received in the capture phase,
    // update the text of the label
    if (!captured)
        captureLabel->setText("Not captured");
    captured = false;
}
 
// Define the slot function for the touchCapture() signal
void App::onTouchCapture(bb::cascades::TouchEvent* event)
{
    Q_UNUSED(event);
     
    // If the touch event is received in the capture phase, update
    // the label text and set the captured variable to true
    captureLabel->setText("Captured!");
    captured = true;
}
// In your application's header file
 
public slots:
    void onTouch(bb::cascades::TouchEvent* event);
    void onTouchCapture(bb::cascades::TouchEvent* event);
 
private:
    Label* captureLabel;
    bool captured;

Not applicable

At-target phase

This phase occurs when a node receives a touch event and that node is the intended target of the event, as opposed to an ancestor of the intended target. An ancestor of the intended target node is any node that is a parent of the target node, or a parent of the parent of the target node, and so on, all the way up to the root of the scene graph.

This phase is the most common one in which to handle touch events. When an event is received in this phase, you can safely assume that a user is interacting directly with a control on the screen, such as a  Button  or a  Label . The  touch()  signal is emitted in this phase, and you can handle this signal to respond to a touch that's targeted at a specific control.

Consider the following image of a Button inside a  Container :


Diagram showing a Button within a Container.

If a user touches the button, the Button receives the touch event in the at-target phase because the Button is interpreted as the intended target of the touch event. The Container doesn't receive the event in this phase; instead, it receives the event in the capture phase (as discussed above) and again in the bubble phase. If the user touches anywhere else in the Container (but not the Button), the Container control receives the touch event in the at-target phase.

Bubble phase

This phase occurs after the at-target phase. The event travels sequentially from leaf nodes toward the root of the scene graph, which gives each node along the way the opportunity to handle the event. The touch()  signal is emitted in this phase (similar to the at-target phase).

Consider the following image of a  Button  in two  Container  controls:


Diagram showing a Container with a Button within another Container.

The corresponding scene graph for this arrangement looks like the following tree diagram:


Diagram showing the scene graph of a Container with a Button within another Container.

If a user touches the button, the Button receives the touch event in the at-target phase. If the event isn't consumed in this phase, the event travels up the nodes in the scene graph, and the dark gray Container receives the event in the bubble phase. If the event still isn't consumed, the event reaches the light gray Container, also in the bubble phase.

Determining the propagation phase of an event

You might notice that the  touch()  signal is emitted in both the at-target phase and bubble phase. To determine which phase a control receives a touch event, you can use the propagationPhase property, which is part of the  TouchEvent  parameter that's included in the touch() signal. By comparing the value of this property to enumeration values in the PropagationPhase  class (NoneCapturingAtTarget, and Bubbling), you can respond to touch events in different phases using the same signal handler.

Here's how you might connect to the touch() signal to determine the phase in which a control received a touch event:

onTouch: {
    if (event.propagationPhase == PropagationPhase.AtTarget) {
        // Handle the event in the at-target phase
    } else if (event.propagationPhase == PropagationPhase.Bubbling) {
        // Handle the event in the bubble phase
    }
}
void App::onTouch(bb::cascades::TouchEvent* event)
{
    if (event->propagationPhase() == PropagationPhase::AtTarget) {
        // Handle the event in the at-target phase
    } else if (event->propagationPhase() ==
        PropagationPhase::Bubbling) {
        // Handle the event in the bubble phase
    }
}

Not applicable

Propagation modes

By default, Cascades delivers touch events to all eligible nodes in the scene graph. If you want to consume an event and prevent other nodes from acting on the event, you need to create this functionality yourself. However, Cascades does give you a bit of control over how touch events are propagated to various nodes. You can control whether touch events are delivered to a visual node and its children by using the touchPropagationMode property, which is part of  VisualNode . This property accepts enumeration values from the  TouchPropagationMode  class: FullNone, or PassThrough.

When the propagation mode for a node is set to Full (the default value), touch events are delivered to that node and all other eligible nodes that are connected to it in the scene graph (including all children of a particular node, if the children are the intended targets of the touch). When the propagation mode is set to None, events are not delivered to the node or any children of the node. When the propagation mode is set to PassThrough, events are not delivered to the node itself, but eligible children of the node receive the events.

For example, consider the same image of a  Button  inside two  Container  controls that was presented in the previous section:


Diagram showing a Container with a Button within another Container.

If the touch propagation mode of the dark gray Container is set to None, that Container don't receive touch events. In addition, the Button doesn't receive touch events either. If the touch propagation mode of the dark gray Container is set to PassThrough, that Container doesn't receive touch events but the Button does receive them.

Overlap touch policies

Like propagation modes, overlap touch policies let you control which nodes receive touch events in your app. Unlike propagation modes, these policies determine propagation based on how UI controls are displayed on the screen, instead of how they're arranged in the scene graph. You can specify whether touch events are can pass through a control to the controls behind it by using the overlapTouchPolicy property, which is part of  VisualNode . This property accepts enumeration values from the  OverlapTouchPolicy  class (Deny or Allow).

When the overlap touch policy for a control is set to Deny (the default value), touch events aren't delivered to any controls that are placed behind the control on the screen. When the overlap touch policy is set to Allow, touch events pass through controls and to the controls behind.

It's important to note that overlap touch policies apply only to nodes that aren't ancestors of each other. For example, if you add a  Button  to a  Container , the Container is the parent (and, thus, an ancestor) of the Button. Any overlap touch policy that you specify for the Button don't prevent the Container from receiving touch events, even though the Container appears beneath the Button visually. In contrast, if you add two Button controls to the same Container, the buttons aren't ancestors of each other. If the buttons overlap visually on the screen, an overlap touch policy that you apply to the top Button would affect whether the Button receives touch events.


Diagram illustrating how overlap touch policies apply only to nodes that aren't ancestors of each other.

An example of touch propagation

The three phases of touch propagation don't necessarily occur sequentially, and it can be difficult to visualize the order in which nodes receive a touch event. The following is an example of touch propagation in action.

Consider the following tree of nodes (on the left) and their visual appearance on the screen (on the right):


A diagram illustrating a tree of nodes and their visual appearance.

A user touches exactly where the purple rectangle is placed (on the image on the right). A touch event is generated and sent to several of the nodes in the tree. From top to bottom, nodes E, D, B, and A all receive the event (as long as none of these nodes consume the event, and the overlap touch policy on node E is set to allow events to pass through to nodes beneath).

Because node E is displayed on top of all of the other nodes, you might expect that this node receives the event first. However, if you recall the different propagation phases, you'll see that things aren't quite that straightforward. To make it easier to visualize the propagation of the touch event, the tree can be broken into two propagation paths: E - A and D - B - A:


A diagram illustrating the propagation of a touch event.

Propagation of the touch event starts on the path that contains the top node that was touched (path E - A). If none of the nodes in the graph consume the event and node E has an overlap touch policy of Allow, the event propagates as follows:

  1. Node A receives the event in the capture phase. This node is the parent of node E, so it's the first node that's eligible to receive and respond to the event.
  2. Node E receives the event in the at-target phase. This node is considered the intended target of the touch event. This node doesn't receive the event in the capture phase because it's a leaf node.
  3. Node B receives the event in the capture phase. This node is the parent of node D, which is immediately beneath node E visually, so node B is the next eligible node to receive the event. Node A doesn't receive the event again because it already received the event during the capture phase.
  4. Node D receives the event in the at-target phase. Similar to node E, this leaf node doesn't receive the event in the capture phase.
  5. Node B receives the event in the bubble phase. Now that all nodes that are eligible to receive the event in the at-target phase (namely, nodes E and D) have processed the event, the event starts to propagate up the tree toward the root node.
  6. Node A receives the event in the bubble phase. Propagation continues up the tree until the root node is reached.

At each step in the propagation, the nodes emit signals ( touchCapture()  in the capture phase, touch()  in the at-target and bubble phases) so that an app can respond appropriately.

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus