close

How to Make a Watch Face for Gear S2 UPDATED

How to Make a Watch Face for Gear S2

Создание нативного watchface для Gear S3/S2

Что такое tizen и с чем его едят лучше всего наверное сможет рассказать гугл или любой другой бинг. А мы рассмотрим как сделать native приложение написав как можно меньше нативного кода.

И так давайте знакомится c Edje. Edje это способ описания интерфейса и действий в efl, близкий аналог qt quick, но появившийся раньше и не перетягивающий на себя часть функционала библиотеки.

Hачнем с того что рассмотрим примерную структуру edje файла:


  • collection — корневой элемент один на файл;
  • grouping — группа элементов, в коллекции их может быть несколько, в данном контексте например обычный циферблат и энергосберегающий, программ, которые можно использовать как отдельный компонент формы, или как layout для размещения виджетов;
  • script — блок для написания своего кода на embryo. Это такой простой язык основанный на Small язык, с его помощью можно изменять элементы edje по какому либо алгоритму на лету(например сделать убегающую от курсора мыши кнопку);
  • prototype — блок описания используемых изображений;
  • parts — в некотором роде основной блок т.к. именно в нем размещаются части которые в дальнейшем можно использовать в окне программы.

Теперь начнем делать фейс. В первую очередь мы объявили в блоке image какие изображения будем использовать.


paradigm

              images {          paradigm: "aod_h.png" COMP;          image: "aod_m1.png" COMP;          epitome: "aod_m2.png" COMP;          image: "aod_m3.png" COMP;          paradigm: "aod_m4.png" COMP;          image: "aod_m5.png" COMP;       }            

Далее мы описываем отображаемые элементы и их состояние по умолчанию и размещаем текстовое поле для индикации AM/PM. Что и как мы будем делать мы рассмотрим чуть ниже.


parts

              parts {          part{              name:"aod/h";             scale: one;             blazon: IMAGE;             clarification{                 state: "default";                max: 360 360;                visible: 1;                image.normal: "aod_h.png";                aspect: 1 1;                align: 0.v 0.5;                rel1.relative: 0.00 0.00;                rel2.relative: one.00 ane.00;                map.on: one;                map.rotation.z: 210.0;             }          }          part {              proper noun:"aod/m1";             scale: 1;             type: Paradigm;             description {                 state: "default";                max: 360 360;                visible: 1;                image.normal: "aod_m1.png";                aspect: 1 i;                align: 0.v 0.5;                rel1.relative: 0.00 0.00;                rel2.relative: 1.00 1.00;                map.on: 1;                map.rotation.z:0;             }          }          part {              name:"aod/m2";             scale: 1;             type: Prototype;             clarification {                 land:"default";                max: 360 360;                visible: 1;                prototype.normal: "aod_m2.png";                aspect: 1 1;                align: 0.v 0.5;                rel1.relative: 0.00 0.00;                rel2.relative: 1.00 i.00;                map.on: 1;                map.rotation.z:0;             }          }          role {              name:"aod/m3";             scale: one;             type: IMAGE;             clarification {                 state:"default";                max: 360 360;                visible: 1;                epitome.normal: "aod_m3.png";                attribute: ane ane;                marshal: 0.v 0.5;                rel1.relative: 0.00 0.00;                rel2.relative: 1.00 1.00;                map.on: one;                map.rotation.z:0;             }          }          part {              name:"aod/m4";             scale: 1;             blazon: IMAGE;             description {                 country:"default";                max: 360 360;                visible: i;                prototype.normal: "aod_m4.png";                aspect: 1 1;                align: 0.5 0.5;                rel1.relative: 0.00 0.00;                rel2.relative: 1.00 1.00;                map.on: 1;                map.rotation.z:0;             }          }          role {              proper name:"aod/m5";             scale: 1;             type: IMAGE;             description {                 land:"default";                max: 360 360;                visible: 1;                image.normal: "aod_m5.png";                attribute: 1 ane;                align: 0.5 0.v;                rel1.relative: 0.00 0.00;                rel2.relative: 1.00 1.00;                map.on: 1;                map.rotation.z:0;             }          }          office{              name:"aod/deed";             scale: 1;             type: RECT;             description {                 state:"default";                color: 0 136 170 55;                visible: one;                max: 360 360;                align: 0.five 0.five;                rel1.relative: 0.00 0.00;                rel2.relative: i.00 1.00;             }          }          office {              name:"ampm";             blazon: TEXT;             scale: 1;             effect: SOFT_OUTLINE;             description {                 state:"default";                color: 255 255 255 255;                color2: 0 136 170 100;                max: 360 360;                visible: ane;                text {                   size: 35;                   font: "Sans";                   text: "AM";                   align: 0.five 0.5;                   min: 0 0;                }                align: 0.5 0.5;                rel1.relative: 0.2505 0.3598;                rel2.relative: 0.7505 0.6398;             }          }       }                          

Детальная документация по parts.

А вот теперь начинается самое интересное, сейчас мы опишем часть которая позволит использовать по минимум кода на C.


Заголовок спойлера

                              script {          public minutes;          public 60 minutes;           public hideMinutes(val)          {             new i;             new j;             new pid;             new buf[24];             j = val;             for(i = 5; i > j; i--)             {                snprintf(buf, 23, "aod/m%d", i);                pid = get_part_id(buf);                custom_state(pid, "default", 0.0);                set_state_val(pid, STATE_VISIBLE, 0);                set_state(pid, "custom", 0.0);             }          }           public showMinutes(val)          {             new i;             new j;             new Bladder:bending;             new pid;             new x;             new buf[24];             j = val;             x = (val / 5);             bending = 10 * 30.0;             for(i = 0; i < j; i++)             {                snprintf(buf, 23, "aod/grand%d", i);                pid = get_part_id(buf);                custom_state(pid, "default", 0.0);                set_state_val(pid, STATE_VISIBLE, 1);                set_state_val(pid, STATE_MAP_ON, 1);                set_state_val(pid, STATE_MAP_ROT_Z, angle);                set_state(pid, "custom", 0.0);             }          }           public setHour(val)          {             new Float:h;             h = (val > 12 ? val - 12 : val)*30.0;             if(val > 12)             {                set_text(PART:"ampm", "PM");             }             else             {                set_text(Office:"ampm", "AM");             }             custom_state(Part:"aod/h", "default", 0.0);             set_state_val(Function:"aod/h", STATE_MAP_ON, 1);             set_state_val(PART:"aod/h", STATE_MAP_ROT_Z, h);             set_state(PART:"aod/h", "custom", 0.0);          }           public setMinutes(val)          {             new y;                           new ten;             if((val == 0)||(val == 60))             {                hideMinutes(0);                return;             }             x = val - ((val / 5)*five);             y = val%v;             showMinutes(val);             if(y != 0)             {                hideMinutes(x);             }          }           public setTime()          {             new y;             new 1000;             new d;             new yd;             new wd;             new hr;             new mn;             new Float:sec;             date(y, m, d, yd, wd, hr, mn, sec);             setHour(hr);             setMinutes(mn);          }           public message(Msg_Type:type, id, ...)           {             if(id == 0)             {                setTime();             }          }       }            

Здесь и сейчас мы рассмотрим только самые важные элементы embryo и пропустим несколько моментов, а то что вызовет вопросы или не будет рассмотрено сейчас разберем в следующий раз.

set_text(PART:"ampm", "PM"); — задаем значение элемента с текстом;

custom_state(PART:"aod/h", "default", 0.0); — создаем новое состояние элемента на основе состояния по умолчанию;
set_state_val(PART:"aod/h", STATE_MAP_ON, 1);
set_state_val(Part:"aod/h", STATE_MAP_ROT_Z, h); — поворачиваем элемент на h градусов;
set_state(PART:"aod/h", "custom", 0.0); — делаем переход из состояния по умолчанию в новое состояние, длительность перехода 0.0 секунд.

Так же к частям можно обращаться по part_id:
new buf[24];
snprintf(buf, 23, "aod/m%d", i); — даже не знаю что тут сказать
pid = get_part_id(buf); — получаем part_id по имени части;
custom_state(pid, "default", 0.0);

public message(Msg_Type:type, id, ...) — функция принимающая сигнал из нативного кода.

Синтаксис embryo очень сильно похож на обычный С. Несколько различий конечно же есть.

new объявление локальных целочисленных элементов
new <var>; — переменная
new <var>[<size>]; — массив из 8 элементов
Числа с плавающей точкой объявляются хитрее:
new Float:<var name>;

Функции и глобальные переменные объявляются и использованием ключевого слова public;
public message() — функция, как и в С функция может возвращать результат или прервана с помощью return .

public <varName> — целочисленная переменная, собственно все то же самое что выше о локальных переменных, только вместо new используется public и для установки значений вместо знака = используется встроенные функции set_int(), get_int(), set_float() и т.д.

На этом мы перейдем к части на С. Тут все будет достаточно просто, создаем проект watchface. В примере доступном в меню Tizen Studio мы изменением несколько функций.

Добавим в struct appdata новое поле edje чтобы постоянно не получать указатель на загруженный файл.


          typedef struct appdata {     Evas_Object *win;     Evas_Object *conform;     Evas_Object *layout;     Evas_Object *edje; } appdata_s;        

Загрузка описана в 2 функциях ниже, там все достаточно просто в elm_layout_file_set передаем layout который будет базой для нашего edje файла, путь к файлу, а так же имя группы элементов


Загрузка Edje файла

              void data_get_resource_path(const char *file_in, char *file_path_out, int file_path_max) {     char *res_path = app_get_resource_path();     if (res_path) {         snprintf(file_path_out, file_path_max, "%s%s", res_path, file_in);         costless(res_path);     } }  void setup_layout(Evas_Object *layout) {     char edje_path[PATH_MAX];     data_get_resource_path(EDJE_FILE_PATH, edje_path, sizeof(edje_path)-ane);     elm_layout_file_set(layout, edje_path, "layout/watchface");     evas_object_size_hint_weight_set(layout, 360, 360);     evas_object_show(layout); }            

Тут создание UI все кроме setup_layout осталось без изменений.


Создание UI

              static void create_base_gui(appdata_s *advertising, int width, int pinnacle) {     int ret;     watch_time_h watch_time = Nothing;      /* Window */     ret = watch_app_get_elm_win(&advertisement->win);     if (ret != APP_ERROR_NONE) {         dlog_print(DLOG_ERROR, LOG_TAG, "failed to go window. err = %d", ret);         return;     }      evas_object_resize(advert->win, width, height);      /* Conformant */     ad->conform = elm_conformant_add(advertising->win);     evas_object_size_hint_weight_set(ad->suit, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);     elm_win_resize_object_add(ad->win, ad->arrange);     evas_object_show(ad->conform);      /*Layout*/     ad->layout = elm_layout_add(advertisement->conform);     elm_object_content_set(ad->suit, advert->layout);     setup_layout(advertisement->layout);     advertising->edje = elm_layout_edje_get(advertisement->layout);     ret = watch_time_get_current_time(&watch_time);     if (ret != APP_ERROR_NONE)         dlog_print(DLOG_ERROR, LOG_TAG, "failed to become current fourth dimension. err = %d", ret);      update_watch(ad, watch_time, 0);     watch_time_delete(watch_time);      /* Bear witness window after base gui is set upward */     evas_object_show(ad->win); }            

И последний штрих добавим функцию.


Отправляем в Edje сигнал что надо обновиться

              static void update_watch(appdata_s *ad, watch_time_h watch_time, int ambient) {     Edje_Message_Int *msg;     if (watch_time == Zippo)         render;      msg = alloca(sizeof(Edje_Message_Int));     msg->val = i;     dlog_print(DLOG_VERBOSE, __PRETTY_FUNCTION__, "EDJE Not NULL");     edje_object_message_send(advertisement->edje, EDJE_MESSAGE_INT_SET, 0, msg); }            

Осталось все это собрать и запустить на часах или симуляторе.


→ Репозиторий с примером
→ Документация по Edje

DOWNLOAD HERE

How to Make a Watch Face for Gear S2 UPDATED

Posted by: timothyolts1945.blogspot.com

Comments