Wearable native

The Widget Viewer demonstrates how to display widget applications using the widget service and widget viewer functions. Note that widget applications have to be installed before the viewer is started.

An Edje file is used to create the application layout. The layout displays widget icons on 1 or more pages.

The following figure illustrates the main screen of the (Circle) Widget Viewer.

Figure: (Circle) Widget Viewer main screen

(Circle) Widget Viewer

Source Files

You can create and view the sample application project, including the source files, in the IDE.

Table: Source files
File name Description
inc/data.h This file contains the functions that read the widget data.
inc/main.h This file contains the package name and the log declarations.
inc/view_defines.h This file contains definitions used by both the C and Edje code.
inc/view.h This file contains the functions used to create and interact with the GUI.
res/edje/main.edc This file contains the application layout.
src/data.c This file contains the functions used to interact with the widget service and widget viewer APIs. Functions defined here are used to read the widget data.
src/main.c This file contains the entry point of the application. The base callbacks used in a native application life-cycle are defined here.
src/view.c This file contains the functions used to create the GUI.

Implementation

Layout

The layout is created using the view_create() function. Also the widget system is initialized here using the _widget_viewer_init() function.

Eina_Bool
view_create(void)
{
    /* Create 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.w, &s_info.h);

    if (!_create_widget_view())
        return EINA_FALSE;

    if (!_widget_viewer_init())
        return EINA_FALSE;

    eext_object_event_callback_add(s_info.win, EEXT_CALLBACK_BACK, _win_hide_request_cb, NULL);
    eext_rotary_event_handler_add(_rotary_handler_cb, NULL);

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

    return EINA_TRUE;
}

If the window creation is successful, the _create_widget_view() function is invoked.

This function is used to load the main.edj file and initialize the widget icons. The callbacks allowing the user interaction are registered in the _create_layout() function.

Note that the total number of widgets installed on the device is also read using the data_get_widget_count() function. This is needed to calculate the page count considering that each page can display up to 7 icons. The page count is used to create the current page indicator.

static bool
_create_widget_view(void)
{
    s_info.layout = _create_layout();
    if (!s_info.layout) {
        dlog_print(DLOG_ERROR, LOG_TAG, "failed to create widgets view layout.");

        return false;
    }

    _initialize_pages();
    edje_object_message_handler_set(elm_layout_edje_get(s_info.layout), _edje_debug_message_cb, NULL);

    return true;
}

The following figure illustrates the application layout structure.

Figure: Application layout

Application layout

The layout elements marked as constant are created using the Edje file and are never modified during the application's life time.

After initialization, the first page is filled with widget icons. If the user changes the page, the icons are replaced by new ones. Note that the Evas_Object objects used to display the icons are created only at the start of the application. Unused icon positions, for example when there are too few widgets installed, are filled with the widget_container.png image. The first icon is selected and the appropriate widget name is displayed at the screen center.

User Interactions

There are several ways to interact with the application:

  • The rotary can be used to select an icon. When the first or last icon is selected, the rotary can be used to change the displayed page.

    To use the rotary, create the rotary callback handler:

    static Eina_Bool
    _rotary_handler_cb(void *data, Eext_Rotary_Event_Info *ev)
    {
        _set_selected_widget(ev);
        _page_changed();
        _set_highlighted_widget();
    
        return EINA_TRUE;
    }
    
  • Use the buttons to change the pages.

    Create the buttons using the Edje file:

    static void
    _widget_page_change_btn_clicked_cb(void *data, Evas_Object *obj, const char *emission, const char *source)
    {
        bool is_next = (bool)data;
        int last_page = s_info.total_widgets / WIDGETS_PER_PAGE;
        int new_page = s_info.current_page;
    
        if (s_info.total_widgets <= WIDGETS_PER_PAGE)
            return;
    
        if (!is_next) {
            if (new_page == 0) {
                new_page = last_page;
                s_info.widget_selected = s_info.total_widgets - 1;
            } else {
                new_page--;
                s_info.widget_selected = (new_page + 1) * WIDGETS_PER_PAGE - 1;
            }
        } else {
            if (new_page == last_page)
                new_page = 0;
            else
                new_page++;
    
            s_info.widget_selected = new_page * WIDGETS_PER_PAGE;
        }
    
        _set_page(new_page);
        _set_highlighted_widget();
    }
    

    Note that the buttons are not displayed when there is only one page used.

  • Click an icon to select it.

    Set a callback for the action:

    static void
    _icon_pressed_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
    {
        int icon_selected = (int)data;
        s_info.widget_selected = s_info.current_page * WIDGETS_PER_PAGE + icon_selected;
        _set_highlighted_widget();
    
        s_info.longpress_timer = ecore_timer_add(LONGPRESS_TIMEOUT, _longpress_timer_cb, NULL);
    }
    

    The _set_highlighted_widget() function is used to highlight the selected widget and update the selected widget's name.

    static void
    _set_highlighted_widget(void)
    {
        char *widget_name = NULL;
        Edje_Message_Int msg = {0,};
        int widget_on_page = s_info.widget_selected % WIDGETS_PER_PAGE;
    
        msg.val = widget_on_page;
        edje_object_message_send(elm_layout_edje_get(s_info.layout), EDJE_MESSAGE_INT, MSG_ID_WIDGET_SELECTED_SET, &msg);
    
        widget_name = data_get_name(s_info.widget_selected);
        elm_layout_text_set(s_info.layout, PART_WIDGET_NAME, widget_name);
        free(widget_name);
    }
    
  • Display the selected widget on longpress, or by tapping its name. An Ecore_Timer is used for the longpress.

    Set a callback for the action:

    static Eina_Bool
    _longpress_timer_cb(void *data)
    {
        s_info.longpress_timer = NULL;
        _show_widget_evas(s_info.widget_selected);
    
        return ECORE_CALLBACK_CANCEL;
    }
    

    To display the widget, use the _show_widget_evas() function.

    static void
    _show_widget_evas(int index)
    {
        char *widget_id = data_get_widget_id(index);
        if (!widget_id) {
            dlog_print(DLOG_WARN, LOG_TAG, "[%s:%d] widget_id == NULL", __FILE__, __LINE__);
    
            return;
        }
    
        s_info.widget = widget_viewer_evas_add_widget(s_info.layout, widget_id, NULL, WIDGET_VIEWER_EVAS_DEFAULT_PERIOD);
        if (!s_info.widget) {
            dlog_print(DLOG_ERROR, LOG_TAG, "[%s:%d] widget == NULL; error message: %s", __FILE__, __LINE__, get_error_message(get_last_result()));
    
            return;
        }
    
        s_info.is_widget_displayed = true;
        evas_object_resize(s_info.widget, s_info.w, s_info.h);
        evas_object_show(s_info.widget);
    }
    

    When a widget is going to be displayed, a new Evas_Object object is created using the widget_viewer_evas_add_widget() function. If the creation is successful, the new object is displayed on the whole screen. The widget can be hidden using the back button.

Edje File

The whole layout is created with an Edje file. Check the main.edc file for details. The widget icons use a SWALLOW part.

#define PART_ICON(num, rel_x, rel_y)
part
{
   name: PART_ICON_NAME##num;
   type: SWALLOW;
   description
   {
      state: STATE_DEFAULT 0.0;
      rel1
      {
         relative: 0.5+rel_x-(25.0/360.0) 0.5+rel_y-(25.0/360.0);
      }
      rel2
      {
         relative: 0.5+rel_x+(25.0/360.0) 0.5+rel_y+(25.0/360.0);
      }
      color: 255 0 0 255;
   }
}

The widget icon Evas_Object objects are created and arranged only once at the application initialization. Later the Evas_Object objects are updated with appropriate image files, such as when a page changes.

The page indicator is created using 5 IMAGE parts.

#define PART_PAGE_INDICATOR(num)
part
{
   name: PART_INDICATOR_NAME##num;
   type: IMAGE;
   description
   {
      state: STATE_DEFAULT 0.0;
      image
      {
         normal: IMAGE_INDICATOR_PAGE;
      }
      color: COLOR_PAGE_UNSELECTED_R COLOR_PAGE_UNSELECTED_G COLOR_PAGE_UNSELECTED_B  COLOR_PAGE_UNSELECTED_A;
   }
   description
   {
      state: STATE_SELECTED 0.0;
      inherit: STATE_DEFAULT;
      color: COLOR_PAGE_SELECTED_R COLOR_PAGE_SELECTED_G COLOR_PAGE_SELECTED_B COLOR_PAGE_SELECTED_A;
   }
}

The icon's highlight, the icon indicator, and the page indicator are controlled by embryo scripts.

Changing the Selected Icon

When the user changes the selected icon, an Edje message is sent to the layout file.

The message() function is invoked with the MSG_INT message type and the equal to MSG_ID_WIDGET_SELECTED_SET message ID. Next, the _set_current_widget() and _indicator_set_pos() functions are invoked.

The indicator position is set with a custom state. Its relative coordinates are set using the cos and sin functions. The highlight is also set with a custom state. First the position of the selected icon is read. Then the highlight image is moved to that position, but an offset value is added. To read the position of an icon part, it also has to be set to a custom state. None of the icon's parameters are modified.

public _set_current_widget(val)
{
    new icon = _get_icon(val);

    custom_state(PART:PART_HIGHLIGHT, STATE_DEFAULT, 0.0);
    custom_state(icon, STATE_DEFAULT, 0.0); /* Needed to read the icon's 'STATE_REL#' values */
    _set_rel(icon, STATE_REL1, STATE_REL1_OFFSET, -4);
    _set_rel(icon, STATE_REL2, STATE_REL2_OFFSET, 4);
    set_state(PART:PART_HIGHLIGHT, STATE_CUSTOM, 0.0);
}
public _indicator_set_pos(val)
{
    new Float:rot = ICON_POSITION_ANGLE_STEP * val;
    new Float:x = (cos(rot, DEGREES) * (180.0 - 7.0 - 5.0) + 180.0) / 360.0;
    new Float:y = (sin(rot, DEGREES) * (180.0 - 7.0 - 5.0) + 180.0) / 360.0;

    custom_state(PART:PART_POINTER, STATE_DEFAULT, 0.0);

    set_state_val(PART:PART_POINTER, STATE_REL1, x - (5.0 / 360.0), y - (5.0 / 360.0));
    set_state_val(PART:PART_POINTER, STATE_REL2, x + (5.0 / 360.0), y + (5.0 / 360.0));

    set_state(PART:PART_POINTER, STATE_CUSTOM, 0.0);
}

Creating the Page Indicator

After the application is initialized, a message is send to the Edje file with the MSG_INT type and MSG_ID_PAGE_COUNT ID. According to the value received, the required number of dots is displayed and placed at the appropriate positions, and the other dots are hidden. The maximum number of dots is 5. If there are more pages displayed, for example 8, the first dot is highlighted when the first page is displayed, the second dot for the second page, the third dot for pages 3 to 6, the fourth dot for the seventh page and the fifth dot for the last page. This is a standard mechanism used in the wearable device. The page indicator initialization is done using the _set_displayed_dots() function.

public _set_displayed_dots()
{
    new Float:dot_x[PAGE_INDICATOR_DOT_COUNT];
    new page_count = get_int(g_page_count);
    new dot;

    switch (page_count) {
    case 1:
        dot_x[0] = (180.0/360.0);
        dot_x[1] = (0.0/360.0);
        dot_x[2] = (0.0/360.0);
        dot_x[3] = (0.0/360.0);
        dot_x[4] = (0.0/360.0);
    
    case 2:
        dot_x[0] = (173.0/360.0);
        dot_x[1] = (187.0/360.0);
        dot_x[2] = (0.0/360.0);
        dot_x[3] = (0.0/360.0);
        dot_x[4] = (0.0/360.0);
    
    case 3:
        dot_x[0] = (165.0/360.0);
        dot_x[1] = (180.0/360.0);
        dot_x[2] = (195.0/360.0);
        dot_x[3] = (0.0/360.0);
        dot_x[4] = (0.0/360.0);
    
    case 4:
        dot_x[0] = (158.0/360.0);
        dot_x[1] = (173.0/360.0);
        dot_x[2] = (187.0/360.0);
        dot_x[3] = (202.0/360.0);
        dot_x[4] = (0.0/360.0);
    
    default:
        dot_x[0] = (151.0/360.0);
        dot_x[1] = (165.0/360.0);
        dot_x[2] = (180.0/360.0);
        dot_x[3] = (195.0/360.0);
        dot_x[4] = (209.0/360.0);
    }

    for (new i = 0; i < PAGE_INDICATOR_DOT_COUNT; ++i) {
        dot = _get_dot(i);
        custom_state(dot, STATE_DEFAULT, 0.0);

        if (i < page_count)
            _set_dot_state(dot, dot_x[i], (120.0/360.0));
        else
            set_state_val(dot, STATE_VISIBLE, 0);

        set_state(dot, STATE_CUSTOM, 0);
    }
}

Changing Pages

When a page is changed, a signal is sent to the Edje file with the MSG_INT type and MSG_ID_PAGE_SET ID. The indicator dots change color according to the currently selected page. The _unset_current_dot() function is used to restore the default color of the previously highlighted dot.

public _unset_current_dot()
{
    new dot = _get_dot(get_int(g_current_dot));

    custom_state(dot, STATE_CUSTOM, 0.0);
    set_state_val(dot, STATE_COLOR, COLOR_PAGE_UNSELECTED_R, COLOR_PAGE_UNSELECTED_G, COLOR_PAGE_UNSELECTED_B, COLOR_PAGE_UNSELECTED_A);
    set_state(dot, STATE_CUSTOM, 0);
}

The _set_selected_dot() function is used to highlight a new dot and globally store the index of the currently selected page.

public _set_selected_dot(num)
{
    new dot;
    new page_count = get_int(g_page_count);
    new dot_displayed = 0;
    new current_dot;

    if (page_count > 5)
        dot_displayed = 5;
    else
        dot_displayed = page_count;

    custom_state(dot, STATE_CUSTOM, 0.0);

    if (num < 2)
        current_dot = num ;
    else if (num == page_count - 2)
        current_dot = dot_displayed - 2;
    else if (num == page_count - 1)
        current_dot = dot_displayed - 1;
    else
        current_dot = 2;

    dot = _get_dot(current_dot);

    if (get_int(g_current_dot) != current_dot)
        set_int(g_current_dot, current_dot);

    set_state_val(dot, STATE_COLOR, COLOR_PAGE_SELECTED_R, COLOR_PAGE_SELECTED_G, COLOR_PAGE_SELECTED_B, COLOR_PAGE_SELECTED_A);
    set_state(dot, STATE_CUSTOM, 0);
}