Create the main application loop

Now that we've completed the initialization for our application, it's time to start coding the main body of the application. We create a main application loop where we simply call three functions over and over again until the user directs the application to close:

  • The handle_events() function processes any change in screen or navigator state data, determines whether an event has occurred, determines what the event is, and responds accordingly.
  • The update() function updates the angle of the rotating cube, performs the menu scrolling animation, and saves the current state to file.
  • The render() function draws the background, menu, and cube (in its updated color) on the screen.

while (!shutdown) {

    handle_events();

    update();

    render();
}

Handle events

We want the Good Citizen application to be able to process and respond to events, such as when a user touches menu items to change the color of the cube, so we'll take care of this first in handle_events().

Using a for loop, request the next available event from the BlackBerry Platform Services. We pass 0 to bps_get_event() to indicate that this function should return immediately (even if no new events are available) instead of blocking. This means that we poll the BlackBerry Platform Services for new events each time the main application loop executes and that we empty the event queue on every iteration. This avoids lag between user actions and the visible response.

bps_event_t *event = NULL;
rc = bps_get_event(&event, 0);

assert (rc == BPS_SUCCESS);

Determine what type of event occurred. The BlackBerry Platform Services categorizes events into domains, so we check to see which domain the event is part of. During the initialization of Good Citizen, we requested events from both the screen and the navigator, so we check both of these domains and invoke the appropriate handler function.

if (event) {
    int domain = bps_event_get_domain(event);
      
    if (domain == screen_get_domain()) {
        handleScreenEvent(event);
    } else if (domain == navigator_get_domain()) {
        handleNavigatorEvent(event);
    }
}

In handleScreenEvent(), extract the screen event from the generic BlackBerry Platform Services event that we received by invoking screen_event_get_event(). This function is part of a set of libscreen functions, which allows you to interact directly with the screen on the device. We'll use more libscreen functions in the next step to retrieve screen event properties.

screen_event_t screen_event = screen_event_get_event(event);

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 applications. Here, we determine the type of screen event that occurred and store it in screen_val, and we determine the location that the event occurred and store the coordinates in pair.

screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &screen_val);
screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_SOURCE_POSITION, pair);

Determine whether screen_val refers to a touch event or a mouse pointer event. The Native SDK differentiates between a touch screen event (which occurs on a tablet) and a mouse pointer event (which occurs on a BlackBerry Tablet Simulator or when a USB mouse is attached to a tablet), so we need to check for each type of event to make sure that our application works on both a tablet and the simulator. In the case of a mouse pointer event, we want to change the color of the cube only when the user releases the mouse button. We use a debounce technique to accomplish this, meaning that after we detect the first mouse click event, we change the color when we don't detect a mouse click event. In each case, we change the color of the cube, either when a user finishes touching the screen or when the user releases the mouse button.

if (screen_val == SCREEN_EVENT_MTOUCH_RELEASE) {
    //This is touch screen event
    handleClick(pair[0], pair[1]);
}

else if (screen_val == 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) {
        //Left mouse button is pressed
        mouse_pressed = true;
    } 

    else {
        if (mouse_pressed) {
            //Left mouse button was released, handle left click
            handleClick(pair[0], pair[1]);
            mouse_pressed = false;
        }
    }
}

The handleClick() function processes both simulator and mouse events. This function takes the screen position of the click and determines the color chosen. It saves this preference to a file and then exits.

void handleClick(int x, int y) { 
    if (menu_active) {
        if ((y > menu_height - 4 * button_size_y)
                && (y < menu_height - 3 * button_size_y) && (x > 0)
			             && (x < button_size_x)) {
			         selected = 3;
			         cube_color[0] = 1.0f;
		 	        cube_color[1] = 0.0f;
			         cube_color[2] = 0.0f;
			         cube_color[3] = 1.0f;

		      } else if ((y > menu_height - 3 * button_size_y)
				            && (y < menu_height - 2 * button_size_y) && (x > 0)
			            	&& (x < button_size_x)) {
			         selected = 2;
			         cube_color[0] = 0.0f;
		         	cube_color[1] = 1.0f;
		 	        cube_color[2] = 0.0f;
			         cube_color[3] = 1.0f;

		      } else if ((y > menu_height - 2 * button_size_y)
				            && (y < menu_height - button_size_y) && (x > 0)
				            && (x < button_size_x)) {
			         selected = 1;
			         cube_color[0] = 0.0f;
			         cube_color[1] = 0.0f;
			         cube_color[2] = 1.0f;
			         cube_color[3] = 1.0f;

		      } else if ((y > menu_height - button_size_y) && (y < menu_height)
			            	&& (x > 0) && (x < button_size_x)) {
		         	selected = 0;
		         	cube_color[0] = 1.0f;
		         	cube_color[1] = 1.0f;
		         	cube_color[2] = 0.0f;
			         cube_color[3] = 1.0f;

		      } else {
			         menu_hide_animation = true;
			         menu_show_animation = false;
			         menu_active = false;
		      }

//Save current state to a file
save_to_file();
	}

The save_to_file() function allows the user to save the current state of the application to a binary file on the file system. This file is opened when the application is restarted.

FILE *fp = fopen("data/save.dat", "wb");

if (!fp) {
    return;
}

fprintf(fp, "%i %i", selected, menu_active);

fclose(fp);

Back in handle_events(), if the event is a navigator event, determine the type of navigator event that occurred by calling handleNavigatorEvent(). Navigator events have codes associated with them, which are listed in the navigator.h library, so we retrieve the code for the event by using bps_event_get_code(). When a user changes the orientation of the device, the navigator sends a NAVIGATOR_ORIENTATION_CHECK event, for which we call navigator_orientation_check_response(). This indicates to the navigator that we want to handle the orientation change. The navigator then sends a NAVIGATOR_ORIENTATION event, for which we call resize() to perform the actual change. Note that if false was passed to navigator_orientation_check_response(), no NAVIGATOR_ORIENTATION event is sent.

The remainder of the navigator events handle the cases where a user swipes down from the top of the bezel (we call a function to open the menu), closes the application (we set a flag that indicates that our application should exit out of the main application loop), or makes the application window inactive (we run in a loop that consumes all events except for the NAVIGATOR_WINDOW_ACTIVE event, which causes the loop to exit and go back to normal processing).

static void handleNavigatorEvent(bps_event_t *event) {
    int rc;
	   bps_event_t *activation_event = NULL;

    switch (bps_event_get_code(event)) {
    case NAVIGATOR_ORIENTATION_CHECK:
        //Signal navigator that we intend to resize
        navigator_orientation_check_response(event, true);
        break;
    case NAVIGATOR_ORIENTATION:
        if (EXIT_FAILURE == resize(event)) {
            shutdown = true;
        }
        break;
    case NAVIGATOR_SWIPE_DOWN:
        menu_show_animation = true;
        menu_hide_animation = false;
        break;
    case NAVIGATOR_EXIT:
        shutdown = true;
        break;
    case NAVIGATOR_WINDOW_INACTIVE:
        //Wait for NAVIGATOR_WINDOW_ACTIVE event
        for (;;) {
            rc = bps_get_event(&activation_event, -1);
            assert(rc == BPS_SUCCESS);

            if (bps_event_get_code(activation_event)
                    == NAVIGATOR_WINDOW_ACTIVE) {
                break;
            }
        }
        break;
    }
}

In resize(), do the following:
  1. Rotate the screen to the current angle of the device, as determined by the orientation sensor.
    if (event) {
        int angle = navigator_event_get_orientation_angle(event);
    
            //Let bbutil rotate current screen surface to this angle
            if (EXIT_FAILURE == bbutil_rotate_screen_surface(angle)) {
                fprintf(stderr, "Unable to handle orientation change\n");
                return EXIT_FAILURE;
            }
    }
  2. Obtain the width and height of the EGL surface that was created using the initialization functions in bbutil.h. We'll use these values to position the cube and shadow on the screen. We also determine whether any errors occurred while we obtained the width and height values.
    eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
    eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);
    
    EGLint err = eglGetError();
    if (err != 0x3000) {
        fprintf(stderr, "Unable to query egl surface dimensions\n");
    		  return EXIT_FAILURE;
    }
    
    width = (float) surface_width;
    height = (float) surface_height;
  3. Determine whether the device is in landscape or portrait mode and then adjust the positions of the shadows and cube accordingly. The vertices of the shadow array are adjusted according to the position and size of the shadow.

    if (width > height) {
        shadow_pos_x = 365.0f;
    		  shadow_pos_y = 0.0f;
    
    		  cube_pos_x = 2.9f;
    		  cube_pos_y = 0.3f;
    		  cube_pos_z = -20.0f;
    } 
    else {
        shadow_pos_x = 70.0f;
    		  shadow_pos_y = 10.0f;
    
    		  cube_pos_x = 0.5f;
    		  cube_pos_y = -4.1f;
    		  cube_pos_z = -30.0f;
    }
    
    shadow_vertices[0] = shadow_pos_x;
    shadow_vertices[1] = shadow_pos_y;
    shadow_vertices[2] = shadow_pos_x + shadow_size_x;
    shadow_vertices[3] = shadow_pos_y;
    shadow_vertices[4] = shadow_pos_x;
    shadow_vertices[5] = shadow_pos_y + shadow_size_y;
    shadow_vertices[6] = shadow_pos_x + shadow_size_x;
    shadow_vertices[7] = shadow_pos_y + shadow_size_y;
  4. Update positions based on the new data and then render the objects to the screen. The render() call is required to force an update to the libscreen window parameters and for the navigator to display something on the screen for the orientation change animation. After this is done, tell the navigator that the rotation is complete.
    update();
    
    if (event) {
        render();
    
        navigator_done_orientation(event);
    }

Update the application data

We want to make sure that the angle of the cube changes and the scrolling menu is animated each time the event-processing loop is executed. The update() function modifies the application data so that these changes are reflected in the next render pass. This function also saves the current state to file.

void update() {
    angle = fmod((angle + 1.0f), 360.0 );

        if (menu_show_animation) {
            if (menu_animation < menu_height) {
                menu_animation += 7.0f;
            } else {
                menu_show_animation = false;
                menu_active = true;

                //Save current state to a file
                save_to_file();
            }

        } else if (menu_hide_animation) {
            if (menu_animation > 0.0f) {
                menu_animation -= 7.0f;

            } else {
                menu_hide_animation = false;
            }
        }

        pos_y = height - menu_animation;
 }

Render the scene

Now that we've handled any events that occur and updated the application data appropriately, all that's left is to render the background, menu, and cube on the screen.

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

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Provide the list of vertices of the cube 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. We render the 2-D content first (the background, shadow, and menu), then the 3-D content (the cube). It is worth noting that the shadow is actually a 2-D sprite that's positioned under the spot where the 3-D cube appears.

enable_2d();

glEnable(GL_TEXTURE_2D);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glVertexPointer(2, GL_FLOAT, 0, shadow_vertices);
glTexCoordPointer(2, GL_FLOAT, 0, shadow_tex_coord);
glBindTexture(GL_TEXTURE_2D, shadow);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

The function called above, enable_2d(), initializes OpenGL ES for 2-D rendering to render the background.

glViewport(0, 0, (int) width, (int) height);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glOrthof(0.0f, width / height, 0.0f, 1.0f, -1.0f, 1.0f);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glScalef(1.0f / height, 1.0f / height, 1.0f);

Back in render(), if the menu is displayed, update the radio buttons accordingly and render the text labels.

if (menu_active || menu_show_animation || menu_hide_animation) {
    glTranslatef(pos_x, pos_y, 0.0f);

    for (i = 0; i < 4; i++) {
        if (i == selected) {
				        glVertexPointer(2, GL_FLOAT, 0, radio_btn_selected_vertices);
				        glTexCoordPointer(2, GL_FLOAT, 0, radio_btn_selected_tex_coord);
				        glBindTexture(GL_TEXTURE_2D, radio_btn_selected);
			    } else {
				       glVertexPointer(2, GL_FLOAT, 0, radio_btn_unselected_vertices);
				       glTexCoordPointer(2, GL_FLOAT, 0,
					 	    radio_btn_unselected_tex_coord);
				       glBindTexture(GL_TEXTURE_2D, radio_btn_unselected);
			    }

			    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
			    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
		    	glTranslatef(0.0f, 60.0f, 0.0f);
    }

		  bbutil_render_text(font, "Color Menu", 10.0f, 10.0f, 0.35f, 0.35f, 0.35f, 1.0f);
    bbutil_render_text(font, "Red", 70.0f, -40.0f, 0.35f, 0.35f, 0.35f, 1.0f);
    bbutil_render_text(font, "Green", 70.0f, -100.0f, 0.35f, 0.35f, 0.35f, 1.0f);
    bbutil_render_text(font, "Blue", 70.0f, -160.0f, 0.35f, 0.35f, 0.35f, 1.0f);
    bbutil_render_text(font, "Yellow", 70.0f, -220.0f, 0.35f, 0.35f, 0.35f, 1.0f);
}

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glDisable(GL_TEXTURE_2D);

Draw the cube in the appropriate position.

enable_3d();
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_DEPTH_TEST);

glTranslatef(cube_pos_x, cube_pos_y, cube_pos_z);

glRotatef(30.0f, 1.0f, 0.0f, 0.0f);
glRotatef(15.0f, 0.0f, 0.0f, 1.0f);
glRotatef(angle, 0.0f, 1.0f, 0.0f);

glColor4f(cube_color[0], cube_color[1], cube_color[2], cube_color[3]);

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);

glVertexPointer(3, GL_FLOAT, 0, cube_vertices);
glNormalPointer(GL_FLOAT, 0, cube_normals);

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
glDrawArrays(GL_TRIANGLE_STRIP, 8, 4);
glDrawArrays(GL_TRIANGLE_STRIP, 12, 4);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 4);
glDrawArrays(GL_TRIANGLE_STRIP, 20, 4);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

glDisable(GL_LIGHTING);
glDisable(GL_LIGHT0);
glDisable(GL_COLOR_MATERIAL);
glDisable(GL_DEPTH_TEST);

The enable_3d() function above initializes OpenGL ES for 3-D rendering, to render the cube.

glViewport(0, 0, (int) width, (int) height);

GLfloat aspect_ratio = width / height;

GLfloat fovy = 45;
GLfloat zNear = 1.0f;
GLfloat zFar = 1000.0f;

GLfloat top = tan(fovy * 0.0087266462599716478846184538424431f) * zNear;
GLfloat bottom = -top;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glFrustumf(aspect_ratio * bottom, aspect_ratio * top, bottom, top, zNear, zFar);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity()

Back in render(), invoke bbutil_swap() to swap the window surface to the screen. This function is part of bbutil.h and invokes the EGL function eglSwapBuffers() to display our content on the screen.

bbutil_swap();