Paint Sample Overview

Mobile native

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

Paint screen Paint screen with a drawing

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

Paint UI layout structure Paint UI widget structure

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

Figure: Application workflow

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.

Table: UI layout code snippets and figures
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
         }
      }
   }
}

Main layout

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.

Table: Base view creation code snippets and figures
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;
}

Win layout

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;
}

Conformant layout

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;
}

Main 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;
}

Toolbar layout

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;
}

Colorselector layout

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;
}

Application Evas layout

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;
}

Selection frame layout

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;
}

Drawing area layout

Callbacks

The following figure illustrates how various callbacks are invoked when mouse action takes place.

Figure: Mouse callback order

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.

Table: Mouse callbacks
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.

Table: Toolbar callbacks
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.

Table: Base shape updates
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.

Rectangle drawing

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.