Classic Watch Sample Overview

The Classic Watch sample application demonstrates how to create a circular watch face, which consists of moving hands.

The following figure illustrates the main screen of the Classic Watch.

Figure: Classic Watch screen

Classic Watch screen

Source Files

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

Table: Source files
File name Description
edje/images/ This directory contains the image files used in the main.edc file.
inc/classicwatch.h This file contains information and definition of the variables and functions used in the C files, especially in the main.c file.
inc/data.h This file contains information and definition of the variables and functions used in the C files, especially in the data.c file.
inc/view.h This file contains information and definition of the variables and functions used in the C files, especially in the view.c file.
res/edje/main.edc This file is for the UI and contains the style, images, and position of the sample application.
res/image/ This directory contains the image files used in the C files.
src/data.c This file contains the functions for retrieving and making data for the application.
src/main.c This file contains the functions related to the application life-cycle, callback functions, and view control.
src/view.c This file contains the functions for implementing the views and handling events.

Implementation

Application Layout

To create the basic application layout:

  1. Create the basic layout using the _create_base_gui() function.

    The window object (win) is essential to make the application UI and the bg object is the main background image of the application.

    static void
    _create_base_gui(int width, int height)
    {
        /* Get the window object */
        ret = watch_app_get_elm_win(&win);
        if (ret != APP_ERROR_NONE) {
            dlog_print(DLOG_ERROR, LOG_TAG, "failed to get window. err = %d", ret);
    
            return;
        }
        evas_object_resize(win, width, height);
        evas_object_show(win);
    
        /* Get the background image file path */
        data_get_resource_path(IMAGE_BG, bg_path, sizeof(bg_path));
    
        /* Create the background */
        bg = view_create_bg(win, bg_path, width, height);
        if (bg == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to create a bg");
    
            return;
        }
    }
    
  2. Create the chronograph layout using the .edj (.edc) file and make parts for the clock hands:
    /* Get the edje file path */
    data_get_resource_path(EDJ_FILE, edj_path, sizeof(edj_path));
    
    /* Create a layout to display the moon phase in the watch */
    module_moonphase_layout = view_create_module_layout(bg, edj_path, "layout_module_moonphase");
    if (module_moonphase_layout) {
        view_set_module_property(module_moonphase_layout, 60, 60, MODULE_MOONPHASE_SIZE, MODULE_MOONPHASE_SIZE);
        view_set_module_moonphase_layout(module_moonphase_layout);
    }
    
    /* Create a layout to display the day number in the watch */
    module_day_layout = view_create_module_layout(bg, edj_path, "layout_module_day");
    if (module_day_layout) {
        view_set_module_property(module_day_layout, BASE_WIDTH - MODULE_DAY_NUM_SIZE - MODULE_DAY_NUM_RIGHT_PADDING,
                                 (BASE_HEIGHT / 2) - (MODULE_DAY_NUM_SIZE / 2), MODULE_DAY_NUM_SIZE, MODULE_DAY_NUM_SIZE);
        view_set_module_day_layout(module_day_layout);
    }
    
    /* Create s layout to display the calendar module (month) */
    module_month_layout = view_create_module_layout(bg, edj_path, "layout_module_month");
    if (module_month_layout) {
        view_set_module_property(module_month_layout, 52, 175, MODULE_MONTH_SIZE, MODULE_MONTH_SIZE);
        view_set_module_month_layout(module_month_layout);
    }
    
    /* Create s layout to display the calendar module (day) */
    module_weekday_layout = view_create_module_layout(bg, edj_path, "layout_module_weekday");
    if (module_weekday_layout) {
        view_set_module_property(module_weekday_layout, 52 + MODULE_MONTH_SIZE, 175, MODULE_WEEKDAY_SIZE, MODULE_WEEKDAY_SIZE);
        view_set_module_weekday_layout(module_weekday_layout);
    }
    
    /* Create hands and shadow hands to display in the watch */
    hands_module_month_shadow = _create_parts(PARTS_TYPE_HANDS_MODULE_MONTH_SHADOW);
    evas_object_data_set(bg, "__HANDS_MODULE_MONTH_SHADOW__", hands_module_month_shadow);
    hands_module_month = _create_parts(PARTS_TYPE_HANDS_MODULE_MONTH);
    evas_object_data_set(bg, "__HANDS_MODULE_MONTH__", hands_module_month);
    
    hands_module_weekday_shadow = _create_parts(PARTS_TYPE_HANDS_MODULE_WEEKDAY_SHADOW);
    evas_object_data_set(bg, "__HANDS_MODULE_WEEKDAY_SHADOW__", hands_module_weekday_shadow);
    hands_module_weekday = _create_parts(PARTS_TYPE_HANDS_MODULE_WEEKDAY);
    evas_object_data_set(bg, "__HANDS_MODULE_WEEKDAY__", hands_module_weekday);
    
    hands_sec_shadow = _create_parts(PARTS_TYPE_HANDS_SEC_SHADOW);
    evas_object_data_set(bg, "__HANDS_SEC_SHADOW__", hands_sec_shadow);
    hands_sec = _create_parts(PARTS_TYPE_HANDS_SEC);
    evas_object_data_set(bg, "__HANDS_SEC__", hands_sec);
    
    hands_min_shadow = _create_parts(PARTS_TYPE_HANDS_MIN_SHADOW);
    evas_object_data_set(bg, "__HANDS_MIN_SHADOW__", hands_min_shadow);
    hands_min = _create_parts(PARTS_TYPE_HANDS_MIN);
    evas_object_data_set(bg, "__HANDS_MIN__", hands_min);
    
    hands_hour_shadow = _create_parts(PARTS_TYPE_HANDS_HOUR_SHADOW);
    evas_object_data_set(bg, "__HANDS_HOUR_SHADOW__", hands_hour_shadow);
    hands_hour = _create_parts(PARTS_TYPE_HANDS_HOUR);
    evas_object_data_set(bg, "__HANDS_HOUR__", hands_hour);
    
  3. In the _create_parts() function, the parts are created according to the predefined size and position in the classicwatch.h file:
    /*
       @brief Create parts of watch
       @param[in] type Parts type
    */
    static Evas_Object*
    _create_parts(parts_type_e type)
    {
        Evas_Object *parts = NULL;
        Evas_Object *bg = NULL;
        char *parts_image_path = NULL;
        int x = 0;
        int y = 0;
        int w = 0;
        int h = 0;
    
        /* Get the background */
        bg = view_get_bg();
    
        /* Get the information about the part */
        parts_image_path = data_get_parts_image_path(type);
        data_get_parts_position(type, &x, &y);
        w = data_get_parts_width_size(type);
        h = data_get_parts_height_size(type);
    
        /* Create the part object */
        parts = view_create_parts(bg, parts_image_path, x, y, w, h);
        if (parts == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to create parts : %d", type);
        }
    
        free(parts_image_path);
    
        /* Set opacity to shadow hands */
        if (type == PARTS_TYPE_HANDS_HOUR_SHADOW ||
            type == PARTS_TYPE_HANDS_MIN_SHADOW ||
            type == PARTS_TYPE_HANDS_SEC_SHADOW) {
            view_set_opacity_to_parts(parts);
        }
    
        return parts;
    }
    

    You can adjust the size and position of the parts through the defined variables in the classicwatch.h file.

    The shadow padding is needed to add reality to the watch view. The shadow hands are pivoted with the synchronized time hands. The pivot position can be changed.

    /* Layout */
    #define BASE_WIDTH 360
    #define BASE_HEIGHT 360
    
    #define MODULE_MOONPHASE_SIZE 240
    #define MODULE_DAY_NUM_SIZE 52
    #define MODULE_DAY_NUM_RIGHT_PADDING 22
    #define MODULE_MONTH_SIZE 128
    #define MODULE_WEEKDAY_SIZE MODULE_MONTH_SIZE
    
    #define HANDS_SEC_WIDTH 30
    #define HANDS_SEC_HEIGHT 360
    #define HANDS_MIN_WIDTH 30
    #define HANDS_MIN_HEIGHT 360
    #define HANDS_HOUR_WIDTH 30
    #define HANDS_HOUR_HEIGHT 360
    #define HANDS_MODULE_CALENDAR_WIDTH 20
    #define HANDS_MODULE_CALENDAR_HEIGHT 128
    
    #define HANDS_SEC_SHADOW_PADDING 9
    #define HANDS_MIN_SHADOW_PADDING 4
    #define HANDS_HOUR_SHADOW_PADDING 4
    #define HANDS_MODULE_CALENDAR_PADDING 4
    

Clock Hands

To create the moving clock hands:

  1. The app_time_tick() function is called at least once per second. The watch applications can get the current time from the watch_time_h handle.
    /*
       @brief Called at each second. This callback is not called while the app is paused or the device is in ambient mode
       @param[in] watch_time The watch time handle. watch_time is not available after returning this callback. It is freed by the framework
       @param[in] user_data The user data to be passed to the callback functions
    */
    void
    app_time_tick(watch_time_h watch_time, void* user_data)
    {
        int hour = 0;
        int min = 0;
        int sec = 0;
        int year = 0;
        int month = 0;
        int day = 0;
        int day_of_week = 0;
    
        watch_time_get_hour(watch_time, &hour);
        watch_time_get_minute(watch_time, &min);
        watch_time_get_second(watch_time, &sec);
        watch_time_get_day(watch_time, &day);
        watch_time_get_month(watch_time, &month);
        watch_time_get_year(watch_time, &year);
        watch_time_get_day_of_week(watch_time, &day_of_week);
    
        _set_time(hour, min, sec);
        _set_date(day, month, day_of_week);
        _set_moonphase(day, month, year);
    }
    
  2. The _set_time() and _set_date() functions calculate the position of each hand and finally redraw the hands to the calculated positions:
    /*
       @brief Set time in the watch
       @pram[in] hour The hour number
       @pram[in] min The min number
       @pram[in] sec The sec number
    */
    static void
    _set_time(int hour, int min, int sec)
    {
        Evas_Object *bg = NULL;
        Evas_Object *hands = NULL;
        Evas_Object *hands_shadow = NULL;
        double degree = 0.0f;
    
        bg = view_get_bg();
        if (bg == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get bg");
    
            return;
        }
    
        /* Rotate hands in the watch */
        degree = sec * SEC_ANGLE;
        hands = evas_object_data_get(bg, "__HANDS_SEC__");
        view_rotate_hand(hands, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2));
        hands_shadow = evas_object_data_get(bg, "__HANDS_SEC_SHADOW__");
        view_rotate_hand(hands_shadow, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2) + HANDS_SEC_SHADOW_PADDING);
    
        if (s_info.cur_min != min) {
            degree = min * MIN_ANGLE;
            hands = evas_object_data_get(bg, "__HANDS_MIN__");
            view_rotate_hand(hands, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2));
            hands_shadow = evas_object_data_get(bg, "__HANDS_MIN_SHADOW__");
            view_rotate_hand(hands_shadow, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2) + HANDS_MIN_SHADOW_PADDING);
            s_info.cur_min = min;
        }
    
        if (s_info.cur_hour != hour) {
            degree = (hour * HOUR_ANGLE) + data_get_plus_angle(min);
            hands = evas_object_data_get(bg, "__HANDS_HOUR__");
            view_rotate_hand(hands, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2));
            hands_shadow = evas_object_data_get(bg, "__HANDS_HOUR_SHADOW__");
            view_rotate_hand(hands_shadow, degree, (BASE_WIDTH / 2), (BASE_HEIGHT / 2) + HANDS_HOUR_SHADOW_PADDING);
            s_info.cur_hour = hour;
        }
    }
    
    /*
       @brief Set date in the watch
       @pram[in] day The day number
       @pram[in] month The month number
       @pram[in] day_of_week The day of week number
    */
    static void
    _set_date(int day, int month, int day_of_week)
    {
        Evas_Object *bg = NULL;
        Evas_Object *module_day_layout = NULL;
        Evas_Object *hands = NULL;
        Evas_Object *hands_shadow = NULL;
        double degree = 0.0f;
        char txt_day_num[32] = {0,};
    
        /* Set day in the watch */
        if (s_info.cur_day != day) {
            module_day_layout = view_get_module_day_layout();
            snprintf(txt_day_num, sizeof(txt_day_num), "%d", day);
            view_set_text(module_day_layout, "txt.day.num", txt_day_num);
            s_info.cur_day = day;
        }
    
        bg = view_get_bg();
        if (bg == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get bg");
    
            return;
        }
    
        /* Rotate hands in the watch */
        if (s_info.cur_month != month) {
            degree = month * MONTH_ANGLE;
            hands = evas_object_data_get(bg, "__HANDS_MODULE_MONTH__");
            view_rotate_hand(hands, degree, (BASE_WIDTH / 2) - (HANDS_MODULE_CALENDAR_WIDTH / 2) - 54,
                             175 + (HANDS_MODULE_CALENDAR_HEIGHT / 2));
            hands_shadow = evas_object_data_get(bg, "__HANDS_MODULE_MONTH_SHADOW__");
            view_rotate_hand(hands_shadow, degree, (BASE_WIDTH / 2) - (HANDS_MODULE_CALENDAR_WIDTH / 2) - 54,
                             175 + (HANDS_MODULE_CALENDAR_HEIGHT / 2) + HANDS_MODULE_CALENDAR_PADDING);
            s_info.cur_month = month;
        }
    
        if (s_info.cur_weekday != day_of_week) {
            degree = (day_of_week - 1) * WEEKDAY_ANGLE;
            hands = evas_object_data_get(bg, "__HANDS_MODULE_WEEKDAY__");
            view_rotate_hand(hands, degree, (BASE_WIDTH / 2) + (HANDS_MODULE_CALENDAR_WIDTH / 2) + 54,
                             175 + (HANDS_MODULE_CALENDAR_HEIGHT / 2));
            hands_shadow = evas_object_data_get(bg, "__HANDS_MODULE_WEEKDAY_SHADOW__");
            view_rotate_hand(hands_shadow, degree, (BASE_WIDTH / 2) + (HANDS_MODULE_CALENDAR_WIDTH / 2) + 54,
                             175 + (HANDS_MODULE_CALENDAR_HEIGHT / 2) + HANDS_MODULE_CALENDAR_PADDING);
            s_info.cur_weekday = day_of_week;
        }
    }
    

Moon Phases

The moon phase is implemented to rotate the moon image part in an EDC file by changing a degree.

To implement the moon phase:

  1. Call the _set_moonphase() function in the app_time_tick() function to set the degree of the moon phase.

    First, calculate the degree of the moon phase. If the degree is changed, the moon image is rotated. The degree is calculated based on the current day, month, and year. In this sample, the moon phases are provided only from 2016 to 2020. If you want to make your watch application based on this sample, consider how you handle the moon phase beyond the year 2020.

    /*
       @brief Set moon phase in the watch
       @pram[in] day The day number
       @pram[in] month The month number
       @pram[in] year The year number
    */
    static void
    _set_moonphase(int day, int month, int year)
    {
        float angle = 0.0f;
    
        angle = data_get_moonphase(day, month, year);
        if (s_info.cur_moonphase == angle) {
            return;
        }
        s_info.cur_moonphase = angle;
    
        dlog_print(DLOG_INFO, LOG_TAG, "Moon phase degree : %.2f", angle);
    
        view_rotate_moonphase(angle);
    }
    
  2. After getting the degree of the moon phase, call the view_rotate_moonphase() function to rotate the moon phase image that is implemented in the EDC file.

    Edje messages are used for communication between the C code and the EDC file. With messages, you can communicate values, such as strings, float numbers, and integer numbers.

    /*
       @brief Rotate hands of the watch
       @param[in] degree The degree you want to rotate
    */
    void
    view_rotate_moonphase(float degree)
    {
        Evas_Object *module_moonphase_layout = NULL;
        Evas_Object *edj_obj = NULL;
        Edje_Message_Float msg;
    
        module_moonphase_layout = view_get_module_moonphase_layout();
        if (module_moonphase_layout == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get moon phase layout");
    
            return;
        }
    
        edj_obj = elm_layout_edje_get(module_moonphase_layout);
        if (edj_obj == NULL) {
            dlog_print(DLOG_ERROR, LOG_TAG, "Failed to get edje object");
    
            return;
        }
    
        msg.val = degree;
    
        edje_object_message_send(edj_obj, EDJE_MESSAGE_FLOAT, 1, &msg);
    }
    
  3. The script that rotates the moon image part is implemented in the programs block in the EDC file. The script is called when an incoming message is detected.

    If the type and id are matched, the Z axis of the img.nightsky_02 part is changed.

    programs
    {
       script
       {
          public message(Msg_Type:type, id, ...)
          {
             if ((type == MSG_FLOAT) && (id == 1))
             {
                new Float:angle = getfarg(2);
                custom_state(PART:"img.nightsky_02", "default", 0.0);
                set_state_val(PART:"img.nightsky_02", STATE_MAP_ROT_Z, angle);
                set_state(PART:"img.nightsky_02", "custom", 0.0);
             }
          }
       }
    } /* programs end */