Start the app loop

Now that we've completed the required initialization for our app, it's time to start coding the app. In main.c, the glview_loop() function runs continuously, waiting for input. Whenever an event occurs, glview_loop() calls the event_handler() callback and then calls frame(). The frame() function calls update() and render() to track the position of the boxes on the screen and render the boxes appropriately on the screen. Meanwhile, glview_loop() swaps the window surface to the screen to update the display. This cycle continues until the user closes the app and then glview_loop() invokes the finalize() callback function.

  • The event_handler() callback function determines what type of event has occurred, and responds accordingly. An event could be the user touches the screen or indicates that they want to end the app, or the device moves.
  • The update() function updates the positions of all of the blocks that are currently on the screen.
  • The render() function draws the blocks in their updated positions on the screen.

Handle events

We want the Falling Blocks app to be able to process and respond to events, such as when a user touches the screen to add a new block.

During the initialization of Falling Blocks, we requested events from the screen, navigator, and sensor events. Now, using the event_handler() callback function, we want to determine what type of event has occurred so we check these domains and invoke the appropriate handler function.

We first check if it is a screen event and if it is, we call screen_event_handler().

static void event_handler(bps_event_t *event, int domain, int code, void *data) {
    app_t *app = (app_t *)data;

    if (domain == screen_get_domain()) {
        screen_event_handler(app, event);
In screen_event_handler(), we extract the screen event from the BlackBerry Platform Services event that we received by invoking screen_event_get_event(). Screen events are defined in the Screen and Windowing Library, which allows you to interact directly with the screen on the device. We use more screen and windowing functions in the next step to retrieve screen event properties.
screen_event_t screen_event = screen_event_get_event(event);
We determine the type of screen event that occurred and the position of the event on the screen. Every screen event is associated with a set of properties. You can query these properties by using the screen_get_event_property_iv() function, which is part of screen.h. This library also lists other properties that you can query, so check there to find the ones that you're interested in for your own apps. Here, we determine the type of screen event and store it in event_type, and we determine the location where the event occurred and store the coordinates in pair.
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &event_type);
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pair);

BPS differentiates between a touch screen event (which occurs on a device) and a pointer event (which occurs on a simulator or when a USB mouse is attached to a device). Therefore, we must check for each type of event to make sure that our app works correctly on a device and on the simulator. For a pointer event, we want to add a block to the screen only when a user releases the mouse button. We use a debounce technique to accomplish this process. We detect the first mouse click event as a mouse button pressed. When the next mouse click event is detected, we interpret this event as a mouse button release. We add a block. In each case, we add a block to the screen, either when a user finishes touching the screen or when the user releases the mouse button.

if (event_type == SCREEN_EVENT_MTOUCH_RELEASE) {
    //This is touch screen event.
    add_cube(app, pair[0], pair[1]);
} else if (event_type == SCREEN_EVENT_POINTER) {
    //This is a mouse move event, it is applicable to a device with a
    //USB mouse or simulator
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS,
                                 &buttons);

    if ((buttons & SCREEN_LEFT_MOUSE_BUTTON) != 0 {
        //Left mouse button is pressed
        mouse_pressed = true;
    } else if (mouse_pressed) {
        //Left mouse button was released, add a cube
        add_cube(app, pair[0], pair[1]);
        mouse_pressed = false;
    }
}

Back in event_handler(), we check if the event is a navigator event. In this app, a navigator event occurs when a user swipes down from the top of the device. When this event happens, we clear all of the blocks from the screen.

} else if (domain == navigator_get_domain()) {
if (NAVIGATOR_SWIPE_DOWN == code) {
            app->num_boxes = 0;

Finally, we check if the event is a sensor event. If it is a sensor event, we retrieve the sensor values and change the direction and speed of the blocks accordingly. This function gets the current gravity values along the x, y, and z-axes of the device from the sensor hardware and updates the gravity values accordingly.

} else if (domain == sensor_get_domain()) {
        if (SENSOR_GRAVITY_READING == code) {
            float z, x, y;
            sensor_event_get_xyz(event, &x, &y, &z);
            app->gravity_x = -x;
            app->gravity_y = -y;
        }
    }
}

Update the app data

We want to make sure that the positions of the blocks change each time the event-processing loop is executed; they are supposed to be falling blocks, after all! The frame() callback updates and renders the graphics.

static void frame(void *data) {
    app_t *app = (app_t *)data;
    update(app);
    render(app->boxes, app->num_boxes);
}

The update() function performs the math required to move the blocks.

In update(), we adjust the positions of the blocks on the screen. For each block on the screen, the update() function calculates the block's new position based on the current orientation of the device (indicated by the gravity values). The blocks fall vertically, no matter what the orientation of the device is. We use some math to calculate the position (using the equation for a line in the form of y = mx + b), and include various cases for different circumstances. For example, we need separate cases for the intersection of a block with each edge of the screen. It's important to remember that update() doesn't actually draw the blocks in their new positions. The render() function does that step.

static void update(app_t *app) {
    //Update position of every cube
    int i;
    float m, b;
  ...
    for (i = 0; i < app->num_boxes; i++) {
        boxes[i].x += gravity_x * 5;
        boxes[i].y += gravity_y * 5;

        //y = mx + b
        if ((gravity_x > 0.05) || (gravity_x < -0.05)) {
            //General case, boxes are not falling vertically
            m = gravity_y / gravity_x;
            b = boxes[i].y - m * boxes[i].x;

            if ((boxes[i].x > width) && (gravity_x > 0.0)) {
                //Right edge
                if ((b >= 0) && (b <= height)) {
                    //Intersection with x = 0
                    boxes[i].x = 0;
                    boxes[i].y = b;
                } else if ((-b / m >= 0) && (-b / m <= width)) {
                    //Intersection with y = 0
                    boxes[i].x = -b / m;
                    boxes[i].y = 0;
                } else if (((height - b) / m >= 0)
                        && ((height - b) / m <= width)) {
                    //Intersection with y = APP_HEIGHT
                    boxes[i].x = (height - b) / m;
                    boxes[i].y = height;
                } else {
                    //Corner case
                    boxes[i].x = 0;
                    boxes[i].y = 0;
                }
            } else if ((boxes[i].x + MAX_SIZE < 0.0) && (gravity_x < 0.0)) {
                //Left edge
                //Perform similar intersection calculations here
    ...
             } else if ((boxes[i].y > height) && (gravity_y > 0.0)) {
                //Top edge
                //Perform similar intersection calculations here
    ...
            } else if ((boxes[i].y + MAX_SIZE <= 0.0) && (gravity_y < 0.0)) {
                //Bottom edge
               //Perform similar intersection calculations here
    ...
        } else {
            //Special case, boxes are falling (almost) vertically so we can't describe m = -+ infinity effectively
            if ((boxes[i].y > height) && (gravity_y > 0.0)) {
                //Top edge
                boxes[i].y = 0;
            } else if ((boxes[i].y + MAX_SIZE <= 0.0) && (gravity_y < 0.0)) {
                //Bottom edge
                boxes[i].y = height;
            }
        }
    }
}

Render the scene

Now that we've handled any events that can occur and updated the app data appropriately, all that's left is to draw the blocks in their appropriate positions on the screen.

In render(), we invoke glClear(). This function clears the screen to the background color that we specified when we initialized the Falling Blocks app.

glClear(GL_COLOR_BUFFER_BIT);

We provide the list of vertices of the blocks to OpenGL ES. We specify that each vertex consists of two floating-point values, and specify the list of vertices that we set up during initialization. OpenGL ES typically deals in 3-D coordinates, but we're rendering only in 2-D, so we don't need to worry about z coordinates.

glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);

We draw each block in the appropriate position and with the appropriate size on the window surface. For each block, we set the color of the block, translate the block to its position on the screen, and scale the block. Finally, we draw the block on the screen. This code represents a typical OpenGL ES rendering pass.

for (i = 0; i < num_boxes; i++) {
    glPushMatrix();

    glColor4f(boxes[i].color, 0.78f, 0, 1.0f);
    glTranslatef(boxes[i].x, boxes[i].y, 0.0f);
    glScalef(boxes[i].size, boxes[i].size, 1.0f);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glPopMatrix();
}

glDisableClientState(GL_VERTEX_ARRAY);

Back in main.c again, the glview_loop() function swaps the window surface to the screen to update the display. The glview_loop() repeats the process until the user closes the app.

Last modified: 2015-03-31



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

comments powered by Disqus