Initialize the application

Before we can display anything on the screen, we need to do a bit of initialization. We'll initialize the screen so that we can draw on it, and we'll also set up some initial data for the application.

In the main.c file, in main(), create a screen context. We use this context to initialize EGL for using a window surface. EGL is an interface between standard rendering APIs (like OpenGL ES) and the underlying windowing system. We also use this context to receive screen events.

screen_create_context(&screen_cxt, 0);

Initialize the BlackBerry Platform Services library. This allows our application to receive and process events. You must perform this step before calling any other BlackBerry Platform Services functions.

bps_initialize();

Initialize EGL using the screen context that we created. To do this, we use a utility function called bbutil_init_egl(). This function is part of bbutil.h, which was included automatically when we created the Good Citizen project. The bbutil.h file is a collection of functions that help us set up and work with EGL. These functions perform all of the EGL initialization and manipulation behind the scenes, so we can focus on the logic of our application. You can take a look at bbutil_init_egl() to see how you can initialize a native surface. If the EGL initialization doesn't succeed, we print an error to stderr, clean up the EGL surface by calling bbutil_terminate() (which is another utility function in bbutil.h), destroy the screen context, and close the application.

if (EXIT_SUCCESS != bbutil_init_egl(screen_cxt)) {
    fprintf(stderr, "bbutil_init_egl failed\n");
		  bbutil_terminate();
		  screen_destroy_context(screen_cxt);
		  return 0;
	}

Initialize the application data. We call initialize() to perform this initialization. If the initialization doesn't succeed, we clean up and close the application.

if (EXIT_SUCCESS != initialize()) {
    fprintf(stderr, "initialize failed\n");
		  bbutil_terminate();
		  screen_destroy_context(screen_cxt);
		  return 0;
}

In initialize(), we load the texture for the unselected radio button from a .png file, using bbutil_load_texture().

float tex_x = 1.0f, tex_y = 1.0f;
int size_x = 64, size_y = 64;

if (EXIT_SUCCESS
        != bbutil_load_texture("app/playbook/native/radio_btn_unselected.png",
                NULL, NULL, &tex_x, &tex_y, &radio_btn_unselected)) {
    fprintf(stderr, "Unable to load non-selected radio button texture\n");
}

Define the verticies of the unselected radio button to which the texture is applied.

radio_btn_unselected_vertices[0] = 0.0f;
radio_btn_unselected_vertices[1] = 0.0f;
radio_btn_unselected_vertices[2] = size_x;
radio_btn_unselected_vertices[3] = 0.0f;
radio_btn_unselected_vertices[4] = 0.0f;
radio_btn_unselected_vertices[5] = size_y;
radio_btn_unselected_vertices[6] = size_x;
radio_btn_unselected_vertices[7] = size_y;

Define the coodinates of the unselected radio button to which the texture is applied.

radio_btn_unselected_tex_coord[0] = 0.0f;
radio_btn_unselected_tex_coord[1] = 0.0f;
radio_btn_unselected_tex_coord[2] = tex_x;
radio_btn_unselected_tex_coord[3] = 0.0f;
radio_btn_unselected_tex_coord[4] = 0.0f;
radio_btn_unselected_tex_coord[5] = tex_y;
radio_btn_unselected_tex_coord[6] = tex_x;
radio_btn_unselected_tex_coord[7] = tex_y;

Repeat these definitions for the selected radio button.

if (EXIT_SUCCESS
  != bbutil_load_texture("app/playbook/native/radio_btn_selected.png", NULL,
  NULL, &tex_x, &tex_y, &radio_btn_selected)) {
    fprintf(stderr, "Unable to load selected radio button texture\n");
}

radio_btn_selected_vertices[0] = 0.0f;
radio_btn_selected_vertices[1] = 0.0f;
radio_btn_selected_vertices[2] = size_x;
radio_btn_selected_vertices[3] = 0.0f;
radio_btn_selected_vertices[4] = 0.0f;
radio_btn_selected_vertices[5] = size_y;
radio_btn_selected_vertices[6] = size_x;
radio_btn_selected_vertices[7] = size_y;

radio_btn_selected_tex_coord[0] = 0.0f;
radio_btn_selected_tex_coord[1] = 0.0f;
radio_btn_selected_tex_coord[2] = tex_x;
radio_btn_selected_tex_coord[3] = 0.0f;
radio_btn_selected_tex_coord[4] = 0.0f;
radio_btn_selected_tex_coord[5] = tex_y;
radio_btn_selected_tex_coord[6] = tex_x;
radio_btn_selected_tex_coord[7] = tex_y;

//Define the size of the radio buttons
button_size_x = (float) size_x;
button_size_y = (float) size_y;

Load the shadow texture and then specifiy its size and coordinates.

if (EXIT_SUCCESS
  != bbutil_load_texture("app/playbook/native/shadow.png", NULL, NULL,
  &tex_x, &tex_y, &shadow)) {
    fprintf(stderr, "Unable to load shadow texture\n");
}

shadow_size_x = (float) 512;
shadow_size_y = (float) 256;

shadow_tex_coord[0] = 0.0f;
shadow_tex_coord[1] = 0.0f;
shadow_tex_coord[2] = tex_x;
shadow_tex_coord[3] = 0.0f;
shadow_tex_coord[4] = 0.0f;
shadow_tex_coord[5] = tex_y;
shadow_tex_coord[6] = tex_x;
shadow_tex_coord[7] = tex_y;

Load the font type Myriad Pro Bold to use for our color menu and then determine the height of the menu for use later when we're determining which menu button is pressed. The function bbutil_load_font() generates the font symbols at the appropriate size for the screen of the BlackBerry PlayBook through the use of dpi (dots per inch).

int dpi = bbutil_calculate_dpi(screen_cxt);

if (dpi == EXIT_FAILURE) {
        fprintf(stderr, "Unable to calculate dpi\n");
        return EXIT_FAILURE;
}

font = bbutil_load_font(
        "/usr/fonts/font_repository/adobe/MyriadPro-Bold.otf", 15, dpi);
if (!font) {
    return EXIT_FAILURE;
}

float text_width, text_height;
bbutil_measure_text(font, "Color Menu", &text_width, &text_height);
menu_height = text_height + 10.0f + button_size_y * 4;

A save file is used to save information about whether or not the menu is displayed (menu_active) and which color is chosen (selected). This save file is used to restore these two items if the application is closed and then started again. Determine whether a save file exists. If it doesn't, initialize the application so that the menu is hidden and the cube color is red.

if (!read_from_file()) {
    selected = 3;
		  cube_color[0] = 1.0f;
		  cube_color[1] = 0.0f;
		  cube_color[2] = 0.0f;
		  cube_color[3] = 1.0f;

	  	menu_animation = 0.0f;
	  	menu_active = false;
	  	menu_show_animation = false;
		  menu_hide_animation = false;
}

Stepping briefly into the read_from_file() function, which is used to read saved data from disk, open the saved file as a binary object.

FILE *fp = fopen("data/save.dat", "rb");
if (!fp) {
    return false;
}

It's important to note that an application must use the data folder for files that are used for reading and writing. If you are using files that are deployed with the application, they must be copied from the deployment location, app/native, to data in order to write to them.

If there is a saved file in memory, load this file with all of the required specifications. The specifications include the color of the cube and whether the menu is active.

int rc = fscanf(fp, "%i %i", &selected, &menu_active);

if (rc == -1) {
	  	return false;
} else {
	  	if (selected == 0) {
	     		cube_color[0] = 1.0f;
			     cube_color[1] = 1.0f;
		     	cube_color[2] = 0.0f;
		     	cube_color[3] = 1.0f;
		  } else if (selected == 1) {
		     	cube_color[0] = 0.0f;
		     	cube_color[1] = 0.0f;
		     	cube_color[2] = 1.0f;
		     	cube_color[3] = 1.0f;
		  } else if (selected == 2) {
		     	cube_color[0] = 0.0f;
		     	cube_color[1] = 1.0f;
			     cube_color[2] = 0.0f;
			     cube_color[3] = 1.0f;
		  } else if (selected == 3) {
		     	cube_color[0] = 1.0f;
		     	cube_color[1] = 0.0f;
			     cube_color[2] = 0.0f;
			     cube_color[3] = 1.0f;
		  } else {
		  	   return false;
		  }
}

//If the menu was saved in the active state, display it
if (menu_active) {
    menu_animation = menu_height;
}

fclose(fp);

Back in initialize(), initialize the positions of assets on the screen with respect to the current size of our surface. Note that this function is used during orientation change handling later on and is explained later.

if (EXIT_FAILURE == resize(NULL)) {
    fprintf(stderr, "Initialize surface\n");
    return EXIT_FAILURE;
}

We specify common OpenGL parameters, such as smooth shading, setting the type and position of the light source in the scene, and enabling ploygon culling.

glShadeModel(GL_SMOOTH);
glClearColor(0.775f, 0.775f, 0.775f, 1.0f);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light_direction);

glEnable(GL_CULL_FACE);

return EXIT_SUCCESS;

Back in main(), request events from the screen and the navigator so that the application can process them. We're interested in receiving screen events (touch events to interact with the menu) and navigator events (swipe down events to toggle the menu, navigator orientation events, and navigator window active/inactive events). When we request screen and navigator events, we verify that the requests were successful. If not, we clean up and close the application.

if (BPS_SUCCESS != screen_request_events(screen_cxt)) {
    fprintf(stderr, "screen_request_events failed\n");
    bbutil_terminate();
    screen_destroy_context(screen_cxt);
    return 0;
}

if (BPS_SUCCESS != navigator_request_events(0)) {
    fprintf(stderr, "navigator_request_events failed\n");
    bbutil_terminate();
    screen_destroy_context(screen_cxt);
    return 0;
}

Indicate to the BlackBerry Platform Services that we are interested in receiving and processing navigator orientation events. This setting, in addition to the orientation settings in the bar-descriptor.xml file, define the orientation of your application on startup and behavior during runtime.

if (BPS_SUCCESS != navigator_rotation_lock(false)) {
    fprintf(stderr, "navigator_rotation_lock failed\n");
    bbutil_terminate();
    screen_destroy_context(screen_cxt);
    return 0;
}