
The Paint sample application demonstrates how you can use the EFL Evas API to draw the following rectangle, polygon, and line shapes.
The following figure illustrates the application main view.
Figure: Paint screen

You can:
- Clear the drawing area
- Select an object already in the drawing area
- Draw using the freehand, rectangle, circle, or line tool
- Remove the selected object
- Show and hide the colorselector panel
The following figure illustrates the structure of the user interface.
Figure: Paint UI layout structure

The application workflow can be divided into 2 logical blocks - startup and drawing - described in the following figure.
Figure: Application workflow

Implementation
Type Definitions
The appdata_s is the main data structure used as a container for the application data:
typedef struct
{
Evas_Object *win; // Application main window
Evas_Object *layout; // Application layout
Evas_Object *conform; // Application conformant
Evas_Object *draw_area; // Drawing area
Evas_Object *color_selector_panel; // Colorselector box
icon_data_s icon_data[IMAGE_POOL_SIZE]; // Icon data used in the toolbar widget
} appdata_s;
The s_info structure contains the data necessary for drawing the shapes:
static struct
{
Evas_Object *win; // Application main window
Evas_Object *layout; // Application layout
Evas_Object *draw_area; // Drawing area rectangle
object_t *current_object; // Currently drawn object
object_t *selected; // Currently selected object
Evas_Object *selection_frame; // Frame surrounding the selected object
Evas *evas; // Application's Evas canvas
int r1; // Red component of the color from the MAIN colorselector
int g1; // Green component of the color from the MAIN colorselector
int b1; // Blue component of the color from the MAIN colorselector
int a1; // Alpha component of the color from the MAIN colorselector
int r2; // Red component of the color from the FILL colorselector
int g2; // Green component of the color from the FILL colorselector
int b2; // Blue component of the color from the FILL colorselector
int a2; // Alpha component of the color from the FILL colorselector
Evas_Coord_Point start; // Position in which the cursor was pressed
Evas_Coord_Point curr; // Current cursor position
Evas_Coord_Point prev; // Previous cursor position
mode_type_t mode; // Application mode (such as rectangle drawing)
mode_type_t prev_mode; // Previous mode the application was in
bool mouse_pressed; // Flag indicating whether the mouse button is pressed
Eina_List *objects; // List of drawn objects
int win_layer; // Evas_Layer of the main window
} s_info
The object_t structure contains the type of the drawn object and an Evas_Object pointer list of objects it is built with:
typedef struct
{
mode_type_t type; // Object type (such as LINE or CIRCLE)
Eina_List *parts; // List containing the object parts
} object_t;
The icon_data_s structure contains the data used by the toolbar icons:
typedef struct
{
const char *file_path; // Path to the icon image file
const char *tooltip; // Icon tooltip text
mode_type_t mode; // Application mode associated with the toolbar item
Elm_Object_Item *item; // Object added to the toolbar
} icon_data_s;
The mode_type_t enum contains the modes used by the application. The enum values are also used as indexes of the toolbar items.
typedef enum
{
CLEAN = 0, // Clear all (application never uses this mode)
SELECT, // Object selection
FREEHAND, // Freehand drawing
RECTANGLE, // Rectangle drawing
CIRCLE, // Circle drawing
LINE, // Line drawing
COLOR_SELECTOR, // Show and hide the colorselector panel (application never uses this mode)
REMOVE, // Remove the selected object (application never uses this mode)
} mode_type_t;
Application Initialization
The entire application life-cycle is implemented in the paint.c file, using the common Tizen application structure:
int
main(int argc, char *argv[])
{
appdata_s ad = {{0,},};
// Variable declaration and initialization
event_callback.create = app_create;
event_callback.terminate = app_terminate;
event_callback.pause = app_pause;
event_callback.resume = app_resume;
event_callback.app_control = app_control;
// Event handler assignment
ret = ui_app_main(argc, argv, &event_callback, &ad);
// Error handling
return ret;
}
The Paint sample application is implemented using the MVC design pattern. Its initialization is done within the app_create() callback function where the user interface creation and application data initialization is triggered using the create_base_gui() function.
static bool
app_create(void *data)
{
// Hook to take necessary actions before main event loop starts
// Initialize UI resources and application data
// If this function returns true, the main loop of application starts
// If this function returns false, the application is terminated
appdata_s *ad = (appdata_s *)data;
// Error handling
return create_base_gui(ad);
}
When the application terminates, the app_terminate() callback function frees the allocated resources. The deletion of the main window also deletes all of its children.
static void
app_terminate(void *data)
{
// Release all resources
appdata_s *ad = (appdata_s *)data;
// Error handling
evas_object_del(ad->win);
}
View
The entire application layout is implemented using EDJE scripts. All the top level swallows are designed for EFL Elementary UI component embedding. The following EDJE swallow - EFL Elementary UI component relations and assigned functionalities are used (for more information, see the Paint UI layout structure figure):
- elm_toolbar (application toolbar):
- Sets the drawing mode the application is in
- Selects an object
- Removes the selected object
- Shows and hides the colorselector panel
- Clears the drawing area
- Evas_Object (drawing area rectangle): Area where the user can perform the drawing
The following table defines the code snippets that create the UI layout.
| Code snippet | Figure |
|---|---|
| The main layout is defined in the paint.edc file: | |
collections
{
group
{
name: "main";
parts
{
// Toolbar part
part
{
name: PART_TOOLBAR;
type: SWALLOW;
// PART_TOOLBAR height equals 10% of total window height
// The toolbar created with toolbar_create() function is here
}
// Drawing area part
part
{
name: PART_DRAW_AREA;
// PART_DRAW_AREA is under the PART_TOOLBAR and covers the rest of the window
// Rectangle created with draw_area_create() is here
}
}
}
}
|
|
Based on the layout defined with the EDJE scripts, the application interface is created with the create_base_gui() function. The function takes 1 parameter: a pointer to the structure containing the application data (appdata_s). The functions responsible for creating the GUI are invoked in this function.
Eina_Bool
create_base_gui(appdata_s *ad)
{
ad->win = app_win_create();
// Error handling
ad->conform = conformant_create(ad->win);
// Error handling
ad->layout = layout_create(ad->conform, ad);
// Error handling
if (!toolbar_create(ad->layout, ad))
// Error handling
if (!color_selector_panel_create(ad->win, ad->color_selector))
// Error handling
if (!win_evas_get(ad->win))
// Error handling
if (!selection_frame_create(ad))
// Error handling
if (!draw_area_create(ad->layout))
// Error handling
return EINA_TRUE;
}
The following table defines the base view creation details.
| Description | Code snippet | Figure |
|---|---|---|
| app_win_create():
Creates the application main window (ad->win). |
static Evas_Object*
app_win_create(void)
{
Evas_Object *win = NULL;
win = elm_win_util_standard_add(PACKAGE, PACKAGE);
// Error handling
// Standard window setup
return win;
}
|
|
| conformant_create():
Creates the conformant and inserts it into the main window (ad->conform). |
static Evas_Object*
conformant_create(Evas_Object *win)
{
Evas_Object *conform = NULL;
// Error handling
conform = elm_conformant_add(win);
// Error handling
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;
}
|
|
| layout_create():
Creates the main layout by loading the main group from the EDJE layout (paint.edj file), and embeds it into the ad->layout container. The newly created layout is then added as the content to the conformant. |
static Evas_Object*
layout_create(Evas_Object *conform, appdata_s *ad)
{
char path[PATH_MAX];
Evas_Object *layout = NULL;
// Error handling
app_get_resource(EDJ_FILE, path, (int)PATH_MAX);
layout = elm_layout_add(conform);
// Error handling
if (!elm_layout_file_set(layout, path, GRP_MAIN))
// Error handling
evas_object_size_hint_weight_set(layout, EVAS_HINT_EXPAND,
EVAS_HINT_EXPAND);
ecore_event_handler_add(ECORE_EVENT_KEY_DOWN, key_press_cb, ad);
elm_object_content_set(conform, layout);
s_info.layout = ad->layout;
return layout;
}
|
|
| toolbar_create():
Creates an elm_toolbar widget and adds it to the layout that was created earlier. The toolbar is then filled using the data from ad->icon_data. |
static Eina_Bool
toolbar_create(Evas_Object *layout, appdata_s *ad)
{
Evas_Object* toolbar = NULL;
int i = 0;
// Error handling
toolbar = elm_toolbar_add(layout);
// Error handling
// Toolbar items appending
elm_toolbar_homogeneous_set(toolbar, EINA_TRUE);
// Toolbar is expanded so that all of its items fit inside
elm_toolbar_shrink_mode_set(toolbar, ELM_TOOLBAR_SHRINK_EXPAND);
elm_layout_content_set(layout, PART_TOOLBAR, toolbar);
return EINA_TRUE;
}
|
|
| color_selector_create():
Creates 2 elm_colorselector instances. They are not used as content of a different UI component. |
static Evas_Object*
color_selector_create(Evas_Object *win)
{
Evas_Object *cs = elm_colorselector_add(win);
// Error handling
evas_object_layer_set(cs, EVAS_LAYER_MAX);
evas_object_smart_callback_add(cs, COLOR_SELECTOR_COLOR_SELECTED,
colorselector_color_set_cb, NULL);
return cs;
}
|
|
| win_evas_get():
Retrieves the Evas of the window (ad->win). |
static Eina_Bool
win_evas_get(Evas_Object *win)
{
s_info.evas = evas_object_evas_get(win);
// Error handling
return EINA_TRUE;
}
|
|
| selection_frame_create():
Creates the selection frame image object. This object is later used to highlight the selected object. |
static Eina_Bool
selection_frame_create(appdata_s *ad)
{
char path[PATH_MAX] = {0,};
// Error handling
s_info.selection_frame = evas_object_image_filled_add(s_info.evas);
// Error handling
// Preparing the frame
return EINA_TRUE;
}
|
|
| draw_area_create():
Creates the drawing area object. This is a rectangle which is used as the drawing background. |
static Eina_Bool
draw_area_create(Evas_Object *layout)
{
// Error handling
s_info.draw_area = evas_object_rectangle_add(s_info.evas);
// Error handling
// Size setting
// Callback invoked when the mouse button is pressed
evas_object_event_callback_add(s_info.draw_area, EVAS_CALLBACK_MOUSE_DOWN,
mouse_down_cb, NULL);
// Callback invoked when the mouse button is released
evas_object_event_callback_add(s_info.draw_area, EVAS_CALLBACK_MOUSE_UP,
mouse_up_cb, NULL);
// Callback invoked when the mouse cursor is moved
// while the mouse button is pressed
evas_object_event_callback_add(s_info.draw_area, EVAS_CALLBACK_MOUSE_MOVE,
mouse_move_cb, NULL);
elm_layout_content_set(layout, PART_DRAW_AREA, s_info.draw_area);
return EINA_TRUE;
}
|
|
Callbacks
The following figure illustrates how various callbacks are invoked when mouse action takes place.
Figure: Mouse callback order

The mouse callbacks are added to the ad->draw_area component so that any action outside the drawing area (like a click on the toolbar) is ignored.
The following table defines the mouse callback details.
| Description | Code snippet |
|---|---|
|
mouse_down_cb():
Called when the user taps the ad->draw_area element. The tap position is stored in the s_info.start structure and the s_info.pressed flag is set. The flag is required because of the callback call order (see the above image). The functions called by the callback are invoked based on the mode set by the ad->toolbar element. |
static void
mouse_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
Evas_Event_Mouse_Down *eemd = (Evas_Event_Mouse_Down *)event_info;
// Error handling
s_info.mouse_pressed = true;
s_info.start.x = eemd->canvas.x;
s_info.start.y = eemd->canvas.y;
s_info.prev.x = eemd->canvas.x;
s_info.prev.y = eemd->canvas.y;
s_info.curr.x = eemd->canvas.x;
s_info.curr.y = eemd->canvas.y;
item_unselect();
if (s_info.mode == SELECT)
{
select_clicked_item();
}
else
{
item_unselect();
}
}
|
| mouse_move_cb():
Called whenever the user moves the cursor. Since the callback is connected to the ad->draw_area element, the function is not called if the cursor is moved outside its boundaries. The callback is not invoked when the mouse button is released. Based on the mode set using the ad->toolbar element, appropriate functions are called. The callback also sets the values of the current (s_info.current) and previous (s_info.previous) cursor positions. |
static void
mouse_move_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
int x = 0, y = 0, w = 0, h = 0;
Evas_Event_Mouse_Move *eemm = (Evas_Event_Mouse_Move *)event_info;
// Error handling
evas_object_geometry_get(obj, &x, &y, &w, &h);
s_info.prev.x = s_info.curr.x;
s_info.prev.y = s_info.curr.y;
if (eemm->cur.canvas.x < x)
{
s_info.curr.x = x;
}
else if (eemm->cur.canvas.x > x + w)
{
s_info.curr.x = x + w;
}
else
{
s_info.curr.x = eemm->cur.canvas.x;
}
if (eemm->cur.canvas.y < y)
{
s_info.curr.y = y;
}
else if (eemm->cur.canvas.y > y + h)
{
s_info.curr.y = y + h;
}
else
{
s_info.curr.y = eemm->cur.canvas.y;
}
switch (s_info.mode)
{
case FREEHAND:
freehand_update();
break;
case RECTANGLE:
rect_update();
break;
case CIRCLE:
circle_update();
break;
case LINE:
line_update();
break;
case SELECT:
object_move();
break;
}
}
|
|
mouse_up_cb():
Called when the mouse button is released. The s_info.mouse_pressed flag is unset, and the s_info.current_object (recently drawn) object is set as the selected object. |
static void
mouse_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
{
s_info.mouse_pressed = false;
selected_item_set(s_info.current_object);
s_info.current_object = NULL;
}
|
The following table defines the toolbar callback details.
| Description | Code snippet |
|---|---|
| toolbar_clear_clicked_cb():
Called when the user wants to clear the drawing area. The area is cleared by deleting all the previously drawn objects and their parts (evas_object_delete()). |
static void
toolbar_clear_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
draw_area_clear();
}
static void
draw_area_clear(void)
{
Eina_List *l = NULL;
Evas_Object *obj = NULL;
EINA_LIST_FOREACH(s_info.objects, l, obj)
{
// Error handling
evas_object_del(obj);
}
s_info.objects = eina_list_free(s_info.objects);
evas_object_hide(s_info.selection_frame);
}
|
| toolbar_colorselector_show_cb():
Called when the user wants to select a new color. The colorselectors are shown, but the toolbar's colorselector item is not set as selected. |
static void
toolbar_colorselector_show_cb(void *data, Evas_Object *obj, void *event_info)
{
appdata_s *ad = (appdata_s *)data;
// Error handling
elm_toolbar_item_selected_set(ad->icon_data[s_info.mode].item, EINA_TRUE);
color_selector_panel_visibility_set(ad, EINA_TRUE);
}
|
| toolbar_selected_item_del_cb():
Called when the user wants to deleted a selected item. The currently selected (s_info.selected) object and all of its parts are removed. |
static void
toolbar_selected_item_del_cb(void *data, Evas_Object *obj, void *event_info)
{
Eina_List *l = NULL;
Evas_Object *part = NULL;
// Error handling
elm_toolbar_item_selected_set(ad->icon_data[s_info.mode].item, EINA_TRUE);
// Error handling
EINA_LIST_FOREACH(s_info.selected->parts, l, part)
{
// Error handling
dlog_print(DLOG_INFO, LOG_TAG, "Removing part of type: %s", evas_object_type_get(part));
evas_object_del(part);
}
s_info.objects = eina_list_remove(s_info.objects, s_info.selected);
free(s_info.selected);
item_unselect();
}
|
The drawing and selection modes are described in Drawing.
Drawing
While the user is drawing, you must control the colorselectors, base shapes, and selections.
Colorselectors
The application uses 2 colorselectors:
- MAIN
Used to set the color of all the parts of the line and freehand shapes, and the boards of the rectangle and circle objects.
When the MAIN colorselector field is clicked, the s_info.r1, s_info.g1, s_info.b1, and s_info.a1 values are set.
- FILL
Used to control the color of the fill part of the rectangle and circle objects.
When the FILL colorselector field is clicked, the s_info.r2, s_info.g2, s_info.b2, and s_info.a2 values are set.
The values set with the colorselectors are used to set the color of the created or selected objects.
The color_set() function is called when the user changes the active color of one of the colorselectors. If an object is selected (s_info.selected != NULL), the color of its parts is also updated.
static void
color_set(color_selector_t cs_num, int r, int g, int b, int a)
{
Eina_List *l = NULL;
Evas_Object *part = NULL;
if (cs_num == COLOR_SELECTOR_MAIN)
{
s_info.r1 = r;
s_info.g1 = g;
s_info.b1 = b;
s_info.a1 = a;
}
else
{
s_info.r2 = r;
s_info.g2 = g;
s_info.b2 = b;
s_info.a2 = a;
}
// Error handling
if (s_info.selected->type == LINE || s_info.selected->type == FREEHAND)
{
EINA_LIST_FOREACH(s_info.selected->parts, l, part)
{
// Error handling
evas_object_color_set(part, s_info.r1, s_info.g1, s_info.b1, s_info.a1);
}
}
else
{
if (cs_num == COLOR_SELECTOR_MAIN)
{
part = eina_list_nth(s_info.selected->parts, 0);
}
else
{
part = eina_list_nth(s_info.selected->parts, 1);
}
evas_object_color_set(part, r, g, b, a);
}
}
Base Shapes
Every object that appears on the canvas is made from 1 or more rectangle, polygon, or line parts.
The drawing is performed inside the mouse_move_cb() function. If the s_info.current_object value is equal to NULL, a new object_t variable is created and added to the s_info.objects list. Usually, this happens in the first mouse_move_cb() call after the mouse_down_cb() call.
Depending on the drawing mode, new Evas_Objects are created or modified (with every mouse_move_cb() call) and added to the s_info.current_object structure as drawn object's parts. The object is modified (parts are added or resized) until the user releases the mouse button. When it happens, the newly created object is marked as selected (s_info.selected = s_info.current_object).
The following table defines the update functions for different base shapes.
| Description | Code snippet |
|---|---|
| freehand_update():
Called by the mouse_move_cb() callback. With every mouse_move_cb() call (except the one before mouse_down_cb()), an Evas_Object line is created and added to the object_t object as a part. When the new part is created, the s_info.prev and s_info.curr values are used as its start and end points. The start point's vertical position value has to be lower than the end point's vertical position value. |
static void
freehand_update(void)
{
Evas_Object *part = NULL;
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
// Error handling
if (!s_info.current_object)
{
s_info.current_object = object_create(FREEHAND);
s_info.objects = eina_list_append(s_info.objects,
s_info.current_object);
}
part = evas_object_line_add(s_info.evas);
// Error handling
object_part_add(s_info.current_object, part);
if (s_info.prev.y <= s_info.curr.y)
{
x1 = s_info.prev.x;
x2 = s_info.curr.x;
y1 = s_info.prev.y;
y2 = s_info.curr.y;
}
else
{
x2 = s_info.prev.x;
x1 = s_info.curr.x;
y2 = s_info.prev.y;
y1 = s_info.curr.y;
}
evas_object_color_set(part, s_info.r1, s_info.g1,
s_info.b1, s_info.a1);
evas_object_line_xy_set(part, x1, y1, x2, y2);
dlog_print(DLOG_INFO, LOG_TAG, "Updating line at: [%d, %d] - [%d, %d]", x1, y1, x2, y2);
evas_object_repeat_events_set(part, true);
evas_object_show(part);
evas_object_layer_set(part, s_info.win_layer + 1);
}
|
| rect_update():
Creates a rectangle object. Rectangle is made with 2 parts: a frame and the fill. After the parts are created, the frame part is moved to the appropriate position. The fill part's initial position is translated (from the frame part's initial position) by OBJECT_BORDER in both horizontal and vertical directions. Each time the mouse_move_cb() callback is called, the frame and fill parts are resized accordingly. To calculate the width and the height of the frame part, subtract the mouse cursor coordinates (x2 = s_info.start.x < s_info.curr.x; y2 = s_info.start.y < s_info.curr.y;) from the start point coordinates. In case of the fill part, the size is further reduced by 2 * OBJECT_BORDER.
|
static void
rect_update(void)
{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
Evas_Object *part = NULL;
// Error handling
if (s_info.start.x < s_info.curr.x)
{
x1 = s_info.start.x;
x2 = s_info.curr.x - s_info.start.x;
}
else
{
x1 = s_info.curr.x;
x2 = s_info.start.x - s_info.curr.x;
}
if (s_info.start.y < s_info.curr.y)
{
y1 = s_info.start.y;
y2 = s_info.curr.y - s_info.start.y;
}
else
{
y1 = s_info.curr.y;
y2 = s_info.start.y - s_info.curr.y;
}
part = eina_list_nth(s_info.current_object->parts, 0);
evas_object_move(part, x1, y1);
evas_object_resize(part, x2, y2);
part = eina_list_nth(s_info.current_object->parts, 1);
evas_object_move(part, x1 + OBJECT_BORDER,
y1 + OBJECT_BORDER);
evas_object_resize(part, x2 - (OBJECT_BORDER*2),
y2 - (OBJECT_BORDER*2));
}
|
|
circle_update():
Creates and resizes a circle object based on user actions. The circle object is created from 2 parts: border and fill. Since the Evas API does not provide a circle, the Evas_Object polygon is used instead. The s_info.start value is used as the center of the circle (ellipse). Based on the difference between the s_info.start and the s_info.curr coordinates, the horizontal and vertical radius is calculated. The polygon is made out of 360 points placed on the canvas in appropriate positions calculated with the sin() and cos() functions. To update the size of the circle (at every mouse_move_cb() call), all of its points are removed and created again in new positions. The fill parts are added the same way, but the fill radius is smaller by OBJECT_BORDER. |
static void
circle_update(void)
{
Evas_Object *part = NULL;
int radius_x = 0;
int radius_y = 0;
int center_x = 0;
int center_y = 0;
// Error handling
if (!s_info.current_object)
{
s_info.current_object = object_create(CIRCLE);
circle_part_create(s_info.current_object, s_info.r1,
s_info.g1, s_info.b1, s_info.a1);
circle_part_create(s_info.current_object, s_info.r2,
s_info.g2, s_info.b2, s_info.a2);
s_info.objects = eina_list_append(s_info.objects,
s_info.current_object);
}
radius_x = (s_info.curr.x - s_info.start.x) / 2;
radius_y = (s_info.curr.y - s_info.start.y) / 2;
center_x = s_info.start.x + radius_x;
center_y = s_info.start.y + radius_y;
radius_x = abs(radius_x);
radius_y = abs(radius_y);
part = eina_list_nth(s_info.current_object->parts, 0);
circle_part_update(part, radius_x, radius_y, center_x, center_y);
part = eina_list_nth(s_info.current_object->parts, 1);
circle_part_update(part, radius_x - OBJECT_BORDER,
radius_y - OBJECT_BORDER, center_x, center_y);
}
|
| line_update():
Created from 2 points, where the beginning point's vertical position value has to be the smaller. Based on that, the s_info.start and s_info.curr structures are used. The points are updated at every mouse_move_cb() callback call. |
static void
line_update(void)
{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
// Error handling
if (!s_info.current_object)
{
line_create();
return;
}
if (s_info.start.y <= s_info.curr.y)
{
x1 = s_info.start.x;
x2 = s_info.curr.x;
y1 = s_info.start.y;
y2 = s_info.curr.y;
}
else
{
x2 = s_info.start.x;
x1 = s_info.curr.x;
y2 = s_info.start.y;
y1 = s_info.curr.y;
}
evas_object_line_xy_set(current_object_first_part_get(),
x1, y1, x2, y2);
}
|
Selections
Objects are selected on 2 occasions:
- When the user finishes drawing a new object.
- When the user taps a part of an already drawn object when the application mode is set to SELECT.
When an object is selected, the s_info.selection_frame (Evas_Object image) is shown and its size and position are adjusted so that the frame surrounds all of the object parts. The object_move() function is called by the mouse_move_cb() callback. The function calculates the offset of the mouse position and translates all the object parts by that same distance.
When the user clicks the _info.selection_frame and there is no other object in that position, the current selection remains active.









