Puzzle / src /

view.c

/*
 * Copyright (c) 2016 Samsung Electronics Co., Ltd
 *
 * Licensed under the Flora License, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://floralicense.org/license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <Elementary.h>
#include <efl_extension.h>
#include "view.h"
#include "view_defines.h"
#include "$(appName).h"

#define IMG_SMILE "smile.jpg"
#define IMG_CORRECT "correct.jpg"

#define PUZZLE_RATIO 0.6
#define MENU_RATIO 0.2
#define PUZZLE_SIZE_LEVEL_4 4
#define PUZZLE_SIZE_LEVEL_5 5
#define IMAGE_PREVIEW_SIZE 0.5
#define MAX_SHUFFLE_COUNT 70
#define PIECE_COUNT PUZZLE_SIZE_LEVEL_5 * PUZZLE_SIZE_LEVEL_5 + 1

typedef enum {NB_UP, NB_DOWN, NB_LEFT, NB_RIGHT} navi_button_e;

static struct view_info {
	Evas_Object *win;
	Evas_Object *conform;
	Evas_Object *radio_group;
	int radio_selector;
	Evas_Object *piece[PIECE_COUNT];
	int piece_pos[PIECE_COUNT];
	Evas* e;
	int full_image_width;
	int full_image_height;
	int origin_image_width;
	int origin_image_height;
	int puzzle_start_x;
	int puzzle_start_y;
	int white_piece;
	int size;
	int screen_width;
	int screen_height;
	int r;
	int g;
	int b;
	int a;
	int start;
	bool shuffling;
	int shuffle_count;
	int level;
} s_info = {
	.win = NULL,
	.conform = NULL,
	.radio_group = NULL,
	.radio_selector = 0,
	.piece = {NULL,},
	.piece_pos = {0,},
	.e = NULL,
	.full_image_width = 0,
	.full_image_height = 0,
	.origin_image_width = 0,
	.origin_image_height = 0,
	.puzzle_start_x = 0,
	.puzzle_start_y = 0,
	.white_piece = 0,
	.size = 0,
	.screen_width = 0,
	.screen_height = 0,
	.r = 0,
	.g = 0,
	.b = 0,
	.a = 0,
	.start = 0,
	.shuffling = 0,
	.shuffle_count = 0,
	.level = 0,
};

static void _delete_win_request_cb(void *data, Evas_Object *obj, void *event_info);
static void _layout_back_cb(void *data, Evas_Object *obj, void *event_info);
static void _popup_close_cb(void *data, Evas_Object *obj, void *event_info);
static void _popup_level_accept_cb(void *data, Evas_Object *obj, void *event_info);
static void _answer_icon_click_cb(void *data, Evas *e, Evas_Object *obj,  void *event_info);
static void _level_icon_click_cb(void* data, Evas *e, Evas_Object *obj, void *event_info);
static void _shuffle_icon_click_cb(void *data, Evas *e, Evas_Object *obj, void *event_info);
static void _navigation_button_cb(void *data, Evas_Object *obj, void *event_info);
static Eina_Bool _shuffle_cb(void *data);
static void _get_app_resource(const char *edj_file_in, char *edj_path_out);
static bool _create_main_layout(void);
static bool _create_menu(Evas_Object *parent);
static bool _create_navigation(Evas_Object *parent);
static void _create_puzzle(int level);
static void _move_puzzle(int offset);
static bool _check_puzzle_move(navi_button_e nb_type);
static void _change_size(int level);
static void _finished(void);


/**
 * @brief Creates essential objects: window, conformant and layout.
 */
Eina_Bool view_create(void *user_data)
{
	/* Create the window */
	s_info.win = view_create_win(PACKAGE);
	if (s_info.win == NULL) {
		dlog_print(DLOG_ERROR, LOG_TAG, "failed to create a window.");
		return EINA_FALSE;
	}

	elm_win_screen_size_get(s_info.win, NULL, NULL, &s_info.screen_width, &s_info.screen_height);

	/* Create the conformant */
	s_info.conform = view_create_conformant_without_indicator(s_info.win);
	if (s_info.conform == NULL) {
		dlog_print(DLOG_ERROR, LOG_TAG, "failed to create a conformant");
		return EINA_FALSE;
	}

	if (!_create_main_layout())
		return EINA_FALSE;

	/* Show the window after main view is set up */
	evas_object_show(s_info.win);

	return EINA_TRUE;
}

/**
 * @brief Creates a basic window named package.
 * @param[in] pkg_name Name of the window
 */
Evas_Object *view_create_win(const char *pkg_name)
{
	Evas_Object *win = NULL;

	/*
	 * Window
	 * Create and initialize elm_win.
	 * elm_win is mandatory to manipulate the window.
	 */
	win = elm_win_util_standard_add(pkg_name, pkg_name);
	elm_win_conformant_set(win, EINA_TRUE);
	elm_win_autodel_set(win, EINA_TRUE);
	elm_win_indicator_mode_set(win, ELM_WIN_INDICATOR_HIDE);
	elm_win_indicator_opacity_set(win, ELM_WIN_INDICATOR_OPAQUE);

	evas_object_smart_callback_add(win, "delete,request", _delete_win_request_cb, NULL);

	return win;
}

/**
 * @brief Creates a layout object for parent object based on provided EDJE script.
 * @param[in] parent The parent object for layout object.
 * @param[in] edj_file_name The relative path to the layout EDJE script.
 * @param[in] edj_group The name of the group to be loaded from the EDJE script.
 * @param[in] part_name The EDJE part's name where the layout is to be set.
 * @return The function returns layout object if it was created successfully,
 * otherwise 'NULL' is returned.
 */
Evas_Object *view_create_layout(Evas_Object *parent, const char *edj_file_name, const char *group_name, const char *part_name)
{
	char edj_path[PATH_MAX] = {0, };
	Evas_Object *layout = NULL;

	_get_app_resource(edj_file_name, edj_path);

	layout = elm_layout_add(parent);
	if (!layout) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_layout_add() failed.");
		return NULL;
	}

	if (!elm_layout_file_set(layout, edj_path, group_name)) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_layout_file_set() failed.");
		return NULL;
	}

	evas_object_size_hint_weight_set(layout, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
	eext_object_event_callback_add(layout, EEXT_CALLBACK_BACK, _layout_back_cb, NULL);

	if (!part_name)
		elm_object_content_set(parent, layout);
	else
		elm_object_part_content_set(parent, part_name, layout);

	evas_object_show(layout);

	return layout;
}

/**
 * @brief Creates a conformant without indicator for wearable app.
 * @param[in] win The object to which you want to set this conformant
 * Conformant is mandatory for base GUI to have proper size
 */
Evas_Object *view_create_conformant_without_indicator(Evas_Object *win)
{
	/*
	 * Conformant
	 * Create and initialize elm_conformant.
	 * elm_conformant is mandatory for base GUI to have proper size
	 * when indicator or virtual keypad is visible.
	 */
	Evas_Object *conform = NULL;

	if (win == NULL) {
		dlog_print(DLOG_ERROR, LOG_TAG, "window is NULL.");
		return NULL;
	}

	conform = elm_conformant_add(win);
	evas_object_size_hint_weight_set(conform, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
	elm_win_resize_object_add(win, conform);

	evas_object_show(conform);

	return conform;
}

/**
 * @brief Creates an icon object and sets it to the parent object.
 * The icon displays an standard image specified by icon_type.
 * @param[in] parent The parent object for icon object.
 * @param[in] icon_type The icon type name.
 * @param[in] part_name The EDJE part's name where the icon is to be set.
 * @return The function returns icon object if it was created successfully,
 * otherwise 'NULL' is returned.
 */
Evas_Object *view_create_icon(Evas_Object *parent, const char *icon_type, const char *part_name)
{
	Evas_Object *icon = elm_icon_add(parent);
	if (!icon) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_icon_add() failed.");
		return NULL;
	};

	if (!elm_icon_standard_set(icon, icon_type)) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_icon_standard_set() failed. No '%s' icon.", icon_type);
		return NULL;
	}

	elm_object_part_content_set(parent, part_name, icon);
	evas_object_show(icon);

	return icon;
}

/**
 * @brief Creates an image object and sets it to the parent object.
 * @param[in] parent The parent object for image object.
 * @param[in] file_name The name of the image file to be displayed.
 * @param[in] part_name The EDJE part's name where the image is to be set.
 * @param[in] width The width of the image in pixels.
 * @param[in] height The height of the image in pixels.
 * @return The function returns image object if it was created successfully,
 * otherwise 'NULL' is returned.
 */
Evas_Object *view_create_image(Evas_Object *parent, const char *file_name, const char *part_name, int width, int height)
{
	char img_path[PATH_MAX] = {0, };
	Evas_Object *image = elm_image_add(parent);
	if (!image) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_image_add() failed.");
		return NULL;
	};

	_get_app_resource(file_name, img_path);

	if (!elm_image_file_set(image, img_path, NULL)) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_image_file_set() failed.");
		return NULL;
	}

	elm_image_aspect_fixed_set(image, EINA_TRUE);

	evas_object_size_hint_min_set(image, width, height);
	evas_object_size_hint_max_set(image, width, height);

	elm_object_part_content_set(parent, part_name, image);
	evas_object_show(image);

	return image;
}

/**
 * @brief Creates a button object for the parent object.
 * @param[in] parent The parent object for button object.
 * @param[in] caption The caption to be displayed on the button.
 * @param[in] part_name The part name where the button is to be set.
 * @param[in] on_click_cb The callback function's handler to be invoked on button click.
 * @param[in] data The data to be passed to the callback function.
 * @return The function returns button object if it was created successfully,
 * otherwise 'NULL' is returned.
 */
Evas_Object *view_create_button(Evas_Object *parent, const char *caption, const char *part_name, Evas_Smart_Cb on_click_cb, void *data)
{
	Evas_Object *button = elm_button_add(parent);
	if (!button) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_button_add() failed.");
		return NULL;
	}

	evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
	evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);
	elm_object_text_set(button, caption);
	elm_object_part_content_set(parent, part_name, button);
	evas_object_smart_callback_add(button, "clicked", on_click_cb, data);
	evas_object_show(button);

	return button;
}

/**
 * @brief Creates a popup window with 'OK' button set.
 * @param[in] title The popup window title.
 * @param[in] button1 The first button caption.
 * @param[in] on_click1_cb The first button's callback function invoked on click event.
 * @param[in] button2 The second button caption.
 * @param[in] on_click2_cb The second button's callback function invoked on click event.
 * @return This function returns popup window object if it was successfully created,
 * otherwise 'false' is returned.
 */
Evas_Object *view_create_popup(const char *title, const char *button1, Evas_Smart_Cb on_click1_cb, const char *button2, Evas_Smart_Cb on_click2_cb)
{
	Evas_Object *button;
	Evas_Object *popup = elm_popup_add(s_info.win);
	if (!popup) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_popup_add() failed.");
		return NULL;
	}

	elm_popup_align_set(popup, ELM_NOTIFY_ALIGN_FILL, 1.0);
	elm_object_part_text_set(popup, "title,text", title);

	if (button1) {
		button = view_create_button(popup, button1, "button1", on_click1_cb, (void *)popup);
		if (!button) {
			evas_object_del(popup);
			return NULL;
		}
	}

	if (button2) {
		button = view_create_button(popup, button2, "button2", on_click2_cb, (void *)popup);
		if (!button) {
			evas_object_del(popup);
			return NULL;
		}
	}

	evas_object_show(popup);

	return popup;
}

/**
 * @brief Creates a radio button within a group of radio buttons and sets it to the parent object.
 * @param[in] parent The parent object for radio button.
 * @param[in] part_name The part name where the radio is to be set.
 * @param[in] v_pointer The pointer to the selected item's id.
 * @return This function returns radio button object if it was successfully created,
 * otherwise 'false' is returned.
 */
Evas_Object *view_create_radio(Evas_Object *parent, const char *part_name, int *v_pointer)
{
	static int radio_id = 0;
	Evas_Object *radio = elm_radio_add(parent);
	if (!radio) {
		dlog_print(DLOG_ERROR, LOG_TAG, "elm_radio_add() failed.");
		return NULL;
	}

	if (!s_info.radio_group) {
		radio_id = 0;
		s_info.radio_group = radio;
		elm_radio_value_pointer_set(radio, v_pointer);
	} else {
		elm_radio_group_add(radio, s_info.radio_group);
	}

	elm_radio_state_value_set(radio, radio_id++);
	evas_object_size_hint_align_set(radio, EVAS_HINT_FILL, EVAS_HINT_FILL);
	evas_object_size_hint_weight_set(radio, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
	elm_object_part_content_set(parent, part_name, radio);
	evas_object_show(radio);

	return radio;
}

/**
 * @brief Destroys window and frees its resources.
 */
void view_destroy(void)
{
	if (s_info.win == NULL)
		return;

	evas_object_del(s_info.win);
}

/**
 * @brief Internal callback function invoked when the main window needs to be destroyed.
 * @param[in] data The user data passed to the evas_object_smart_callback_add() function.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _delete_win_request_cb(void *data, Evas_Object *obj, void *event_info)
{
	ui_app_exit();
}

/**
 * @brief Internal callback function invoked when the HW Back button is pressed.
 * @param[in] data The user data passed to the eext_object_event_callback_add() function.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _layout_back_cb(void *data, Evas_Object *obj, void *event_info)
{
	/* Let window go to hide state. */
	elm_win_lower(s_info.win);
}

/**
 * @brief Internal callback function invoked when the 'Close' button is pressed
 * while popup window is visible.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _popup_close_cb(void *data, Evas_Object *obj, void *event_info)
{
	s_info.radio_group = NULL;
	evas_object_del((Evas_Object *)data);
}

/**
 * @brief Internal callback function invoked when the 'OK' button is pressed
 * while popup window is visible.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _popup_level_accept_cb(void *data, Evas_Object *obj, void *event_info)
{
	if (s_info.level != s_info.radio_selector) {
		if (s_info.radio_selector == 0)
			_change_size(PUZZLE_SIZE_LEVEL_4);
		else if (s_info.radio_selector == 1)
			_change_size(PUZZLE_SIZE_LEVEL_5);

		s_info.level = s_info.radio_selector;
	}

	_popup_close_cb(data, obj, event_info);
}

/**
 * @brief Internal callback function invoked when the 'answer' icon is pressed.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] e The evas surface of the object.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _answer_icon_click_cb(void *data, Evas *e, Evas_Object *obj,  void *event_info)
{
	Evas_Object *popup;
	int width = 0;

	popup = view_create_popup("Answer", "Close", _popup_close_cb, NULL, NULL);
	if (!popup)
		return;

	elm_win_screen_size_get(s_info.win, NULL, NULL, &width, NULL);

	if (!view_create_image(popup, IMG_SMILE, "default", width * IMAGE_PREVIEW_SIZE, width * IMAGE_PREVIEW_SIZE)) {
		evas_object_del(popup);
		return;
	}

	evas_object_show(popup);
}

/**
 * @brief Internal callback function invoked when the 'level' icon is pressed.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] e The evas surface of the object.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _level_icon_click_cb(void* data, Evas *e, Evas_Object *obj, void *event_info)
{
	Evas_Object *popup;
	Evas_Object *layout;

	popup = view_create_popup("Level", "OK", _popup_level_accept_cb, "Close", _popup_close_cb);
	if (!popup)
		return;

	layout = view_create_layout(popup, EDJ_MAIN, GRP_POPUP, "default");
	if (!layout) {
		evas_object_del(popup);
		return;
	}

	view_create_radio(layout, PART_POPUP_ITEM_1, &s_info.radio_selector);
	view_create_radio(layout, PART_POPUP_ITEM_2, &s_info.radio_selector);

	evas_object_show(popup);
}

/**
 * @brief Internal callback function invoked when the 'shuffle' icon is pressed.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] e The evas surface of the object.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _shuffle_icon_click_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
	Ecore_Animator *animator;

	if (s_info.shuffling)
		return;

	s_info.shuffle_count = 0;
	s_info.shuffling = true;

	animator = ecore_animator_add(_shuffle_cb, NULL);
}

/**
 * @brief Internal callback function invoked when one of the navigation buttons is pressed.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
static void _navigation_button_cb(void *data, Evas_Object *obj, void *event_info)
{
	navi_button_e nb_type = (navi_button_e)data;

	if (!_check_puzzle_move(nb_type))
		return;

	switch (nb_type) {
	case NB_UP:
		_move_puzzle(s_info.size);
		break;
	case NB_DOWN:
		_move_puzzle(-s_info.size);
		break;
	case NB_LEFT:
		_move_puzzle(1);
		break;
	case NB_RIGHT:
		_move_puzzle(-1);
		break;
	default:
		dlog_print(DLOG_WARN, LOG_TAG, "Unknown navigation button type.");
	}
}

/**
 * @brief Internal callback function invoked when the mouse button is pressed
 * over the puzzle piece.
 * This function performs selected piece highlighting.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] e The evas surface of the object.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
void _mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
	Evas_Object *piece = obj;
	int pos = (int)evas_object_data_get(piece, "position");
	int r, g, b, a;

	if (pos == (int)evas_object_data_get(s_info.piece[s_info.white_piece], "position"))
		return;

	evas_object_color_get(piece, &r, &g, &b, &a);
	evas_object_color_set(piece, r, g, b, 200);

	evas_object_show(piece);
}

/**
 * @brief Internal callback function invoked when the mouse button is released
 * from the puzzle piece.
 * This function performs selected piece movement to the white space.
 * @param[in] data The user data passed to the callback attachment function.
 * @param[in] e The evas surface of the object.
 * @param[in] obj The object invoking this callback function.
 * @param[in] event_info The structure containing the information on this event.
 */
void _mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
	Evas_Object *piece = obj;
	int pos = (int)evas_object_data_get(piece, "position");

	if (pos == (int)evas_object_data_get(s_info.piece[s_info.white_piece], "position"))
		return;

	evas_object_color_set(piece, 255, 255, 255, 255);

	if (pos - s_info.size == s_info.white_piece)
		_navigation_button_cb((void *)NB_UP, NULL, NULL);
	else if (pos - 1 == s_info.white_piece && ((s_info.white_piece + 1) % s_info.size))
		_navigation_button_cb((void *)NB_LEFT, NULL, NULL);
	else if (pos + 1 == s_info.white_piece && (s_info.white_piece % s_info.size))
		_navigation_button_cb((void *)NB_RIGHT, NULL, NULL);
	else if (pos + s_info.size == s_info.white_piece)
		_navigation_button_cb((void *)NB_DOWN, NULL, NULL);

	evas_object_show(piece);
}

/**
 * @brief Internal callback function invoked on pieces shuffling animation.
 * @param[in] data The user data passed to the callback attachment function.
 */
static Eina_Bool _shuffle_cb(void *data)
{
	navi_button_e nb_type = (navi_button_e)(rand() % 4);
	s_info.shuffle_count++;

	if (!_check_puzzle_move(nb_type))
		s_info.shuffle_count--;
	else
		_navigation_button_cb((void *)nb_type, NULL, NULL);

	if (s_info.shuffle_count < MAX_SHUFFLE_COUNT) {
		return ECORE_CALLBACK_RENEW;
	} else {
		s_info.shuffling = false;
		return ECORE_CALLBACK_CANCEL;
	}
}

/**
 * @brief Internal function which creates fully qualified path to the provided resource file.
 * @param[in] edj_file_in The file name.
 * @param[out] edj_path_out The fully qualified path to the edj_file_in file.
 */
static void _get_app_resource(const char *edj_file_in, char *edj_path_out)
{
	char *res_path = app_get_resource_path();
	if (res_path) {
		snprintf(edj_path_out, PATH_MAX, "%s%s", res_path, edj_file_in);
		free(res_path);
	}
}

/**
 * @brief Internal function which creates the main layout.
 * @return This function returns 'true' if the main layout was created successfully,
 * otherwise 'false' is returned.
 */
static bool _create_main_layout(void)
{
	Evas_Object *layout = NULL;

	layout = view_create_layout(s_info.conform, EDJ_MAIN, GRP_MAIN, "elm.swallow.content");
	if (!layout)
		return false;

	s_info.e = evas_object_evas_get(layout);
	if (!s_info.e)
		return false;

	if (!_create_menu(layout))
		return false;

	if (!_create_navigation(layout))
		return false;

	_create_puzzle(PUZZLE_SIZE_LEVEL_4);

	return true;
}

/**
 * @brief Internal function which creates the top menu layout.
 * The menu consists of three icons (buttons): answer preview, level selection, puzzle shuffle.
 * @return This function returns 'true' if the menu layout was created successfully,
 * otherwise 'false' is returned.
 */
static bool _create_menu(Evas_Object *parent)
{
	Evas_Object *layout;
	Evas_Object *answer;
	Evas_Object *level;
	Evas_Object *shuffle;

	layout = view_create_layout(parent, EDJ_MAIN, GRP_MENU, PART_MAIN_MENU);
	if (!layout)
		return false;

	answer = view_create_icon(layout, "no_photo", PART_MENU_ANSWER);
	if (!answer)
		return false;

	level = view_create_icon(layout, "file", PART_MENU_LEVEL);
	if (!level)
		return false;

	shuffle = view_create_icon(layout, "refresh", PART_MENU_SHUFFLE);
	if (!shuffle)
		return false;

	evas_object_event_callback_add(answer, EVAS_CALLBACK_MOUSE_UP, _answer_icon_click_cb, NULL);
	evas_object_event_callback_add(level, EVAS_CALLBACK_MOUSE_UP, _level_icon_click_cb, NULL);
	evas_object_event_callback_add(shuffle, EVAS_CALLBACK_MOUSE_UP, _shuffle_icon_click_cb, NULL);

	return true;
}

/**
 * @brief Internal function which creates the navigation layout.
 * The navigation consists of four buttons for puzzle pieces moving in four directions.
 * @return This function returns 'true' if the navigation layout was created successfully,
 * otherwise 'false' is returned.
 */
static bool _create_navigation(Evas_Object *parent)
{
	Evas_Object *layout = view_create_layout(parent, EDJ_MAIN, GRP_NAVI, PART_MAIN_NAVI);
	if (!layout)
		return false;

	return (view_create_button(layout, "UP", PART_NAVI_UP, _navigation_button_cb, (void *)NB_UP) &&
			view_create_button(layout, "DOWN", PART_NAVI_DOWN, _navigation_button_cb, (void *)NB_DOWN) &&
			view_create_button(layout, "LEFT", PART_NAVI_LEFT, _navigation_button_cb, (void *)NB_LEFT) &&
			view_create_button(layout, "RIGHT", PART_NAVI_RIGHT, _navigation_button_cb, (void *)NB_RIGHT));
}

/**
 * @brief Internal function which creates the puzzle image area.
 */
static void _create_puzzle(int level)
{
	char file_path[PATH_MAX] = {0,};
	int x, y;
	int extract_region_width, extract_region_height;
	int puzzle_width, puzzle_height;
	int i;
	int ret;

	_get_app_resource(IMG_SMILE, file_path);

	s_info.full_image_width = s_info.screen_width - (6 + 6);
	s_info.full_image_height = (s_info.screen_height * PUZZLE_RATIO) - (6 + 6);
	s_info.puzzle_start_x = 6;
	s_info.puzzle_start_y = (s_info.screen_height * MENU_RATIO) + 6;

	for (i = 0; i < PIECE_COUNT; i++) {
		s_info.piece[i] = evas_object_image_filled_add(s_info.e);
		s_info.piece_pos[i] = i;
		evas_object_image_file_set(s_info.piece[i], file_path, NULL);
		ret = evas_object_image_load_error_get(s_info.piece[i]);
		if (ret != EVAS_LOAD_ERROR_NONE)
			dlog_print(DLOG_ERROR, LOG_TAG, "Failed to load image");

		evas_object_data_set(s_info.piece[i], "position", (void *)i);
	}

	evas_object_image_size_get(s_info.piece[PIECE_COUNT - 1], &s_info.origin_image_width, &s_info.origin_image_height);

	extract_region_width = s_info.origin_image_width / level;
	extract_region_height = s_info.origin_image_height / level;
	puzzle_width = s_info.full_image_width / level;
	puzzle_height = s_info.full_image_height / level;

	for (y = 0; y < level; y++)
		for (x = 0; x < level; x++) {
			evas_object_move(s_info.piece[y * level + x], s_info.puzzle_start_x + x * (2 + puzzle_width), s_info.puzzle_start_y + y * (2 + puzzle_height));
			evas_object_image_load_region_set(s_info.piece[y * level + x], x * extract_region_width, y * extract_region_height, extract_region_width, extract_region_height);
			evas_object_resize(s_info.piece[y * level + x], puzzle_width, puzzle_height);

			evas_object_event_callback_add(s_info.piece[y * level + x], EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, NULL);
			evas_object_event_callback_add(s_info.piece[y * level + x], EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, NULL);

			if (y == level - 1 && x == level - 1) {
				evas_object_color_get(s_info.piece[y * level + x], &s_info.r, &s_info.g, &s_info.b, &s_info.a);
				evas_object_color_set(s_info.piece[y * level + x], s_info.r, s_info.g, s_info.b, 0);

				s_info.white_piece = y * level + x;
			}

			evas_object_show(s_info.piece[y * level + x]);
		}

	s_info.size = level;
	s_info.start = 0;
}

/**
 * @brief Internal function which moves the puzzle piece.
 * @param[in] offset The number of cells to move the current piece. In order to
 * specify a valid value, the puzzle image must be considered as a continuous
 * list (array) of cells. To move the current cell:
 * - down/up - the offset equals to +/-row_length;
 * - right/left - the offset equals to +/-1.
 */
static void _move_puzzle(int offset)
{
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int temp;
	int white_piece = s_info.white_piece;

	evas_object_image_load_region_get(s_info.piece[white_piece + offset], &x1, &y1, &w1, &h1);
	evas_object_image_load_region_get(s_info.piece[white_piece], &x2, &y2, &w2, &h2);

	evas_object_image_load_region_set(s_info.piece[white_piece + offset], x2, y2, w2, h2);
	evas_object_image_load_region_set(s_info.piece[white_piece], x1, y1, w1, h1);

	evas_object_color_set(s_info.piece[white_piece], s_info.r, s_info.g, s_info.b, s_info.a);
	evas_object_color_set(s_info.piece[white_piece + offset], s_info.r, s_info.g, s_info.b, 0);

	evas_object_show(s_info.piece[white_piece]);
	evas_object_show(s_info.piece[white_piece + offset]);

	temp = s_info.piece_pos[white_piece];
	s_info.piece_pos[white_piece] = s_info.piece_pos[white_piece + offset];
	s_info.piece_pos[white_piece + offset] = temp;

	s_info.white_piece = white_piece + offset;

	if (s_info.piece_pos[s_info.white_piece] == s_info.size * s_info.size - 1 && s_info.start == 1)
		_finished();
}

/**
 * @brief Internal function which checks whether the current puzzle can be moved
 * with the provided button type.
 * @param[in] nb_type The type of the navigation button to check against.
 * @return This function returns 'true' if the current piece can be moved using
 * the provided button type, otherwise 'false' is returned.
 */
static bool _check_puzzle_move(navi_button_e nb_type)
{
	switch (nb_type) {
	case NB_UP:
		return (s_info.white_piece + s_info.size < s_info.size * s_info.size);
	case NB_DOWN:
		return (s_info.white_piece - s_info.size >= 0);
	case NB_LEFT:
		return ((s_info.white_piece + 1) % s_info.size > 0);
	case NB_RIGHT:
		return (s_info.white_piece % s_info.size > 0);
	default:
		return false;
	}
}

/**
 * @brief Internal function which changes the puzzle size and recalculates all the
 * parameters and coordinates of each puzzle piece.
 * @param[in] level The number of pieces along one edge.
 */
static void _change_size(int level)
{
	char file_path[PATH_MAX] = {0,};
	int x, y;
	int extract_region_width, extract_region_height;
	int puzzle_width, puzzle_height;
	int i;

	_get_app_resource(IMG_SMILE, file_path);

	for (i = 0; i < PIECE_COUNT; i++) {
		evas_object_color_set(s_info.piece[i], s_info.r, s_info.g, s_info.b, 0);
		evas_object_show(s_info.piece[i]);
		evas_object_move(s_info.piece[i], 0, 0);
	}

	extract_region_width = s_info.origin_image_width / level;
	extract_region_height = s_info.origin_image_height / level;
	puzzle_width = s_info.full_image_width / level;
	puzzle_height = s_info.full_image_height / level;

	for (y = 0; y < level; y++)
		for (x = 0; x < level; x++) {
			evas_object_color_set(s_info.piece[y * level + x], s_info.r, s_info.g, s_info.b, 255);
			evas_object_image_file_set(s_info.piece[y * level + x], file_path, NULL);

			evas_object_move(s_info.piece[y * level + x], s_info.puzzle_start_x + x * (2 + puzzle_width), s_info.puzzle_start_y + y * (2 + puzzle_height));
			evas_object_image_load_region_set(s_info.piece[y * level + x], x * extract_region_width, y * extract_region_height, extract_region_width, extract_region_height);
			evas_object_resize(s_info.piece[y * level + x], puzzle_width, puzzle_height);

			evas_object_event_callback_add(s_info.piece[y * level + x], EVAS_CALLBACK_MOUSE_DOWN, _mouse_down_cb, NULL);
			evas_object_event_callback_add(s_info.piece[y * level + x], EVAS_CALLBACK_MOUSE_UP, _mouse_up_cb, NULL);

			if (y == (level - 1) && x == (level - 1)) {
				evas_object_color_get(s_info.piece[y * level + x], &s_info.r, &s_info.g, &s_info.b, &s_info.a);
				evas_object_color_set(s_info.piece[y * level + x], s_info.r, s_info.g, s_info.b, 0);

				s_info.white_piece = y * level + x;
			}

			evas_object_show(s_info.piece[y * level + x]);
		}

	s_info.size = level;
	s_info.start = 0;
}

/**
 * @brief Internal function which validates the pieces arrangement correctness
 * and displays the final popup window if the validation is passed.
 */
static void _finished(void)
{
	Evas_Object *popup = NULL;
	int width = 0;
	int i;

	for (i = 0; i < s_info.size * s_info.size; i++)
		if (s_info.piece_pos[i] != i)
			return;

	popup = view_create_popup("Correct !!!", "OK", _popup_close_cb, NULL, NULL);
	if (!popup)
		return;

	elm_win_screen_size_get(s_info.win, NULL, NULL, &width, NULL);

	if (!view_create_image(popup, IMG_CORRECT, "default", width * IMAGE_PREVIEW_SIZE, width * IMAGE_PREVIEW_SIZE)) {
		evas_object_del(popup);
		return;
	}

	evas_object_show(popup);
	s_info.start = 0;
}