Pebble: работа со статической графикой на примере создания 7-segment watchface

404ea727b7b84d8eb42841b90d584ff0.png Все приложения для часов Pebble делятся на две категории watchapp — просто приложения, и watchface — приложения «часы», которые исходя из названия являются лицом устройства. Отличие «фейсов» — отсутствие реакции на хардварные кнопки, так как «UP» и «DOWN» используются для циклического переключения между установленными watchface.Но, наверное, в силу низкого разрешения экрана 144×168 px, найти органично вписывающийся в дизайн часов ватчфейс, который при этом выполняет основную свою функцию — отсчет времени, довольно непросто.Как мне кажется лучше всего на таком экране смотрятся цифры в стиле семисегментных индикаторов.Ниже, подробнее о том, как добавить в свой watchface элегантного минимализма, индивидуальности и уникальных фишек.Итак, немного картинок, обрывков кода и в итоге ссылка на готовый проект.Создание watchface, структура и построение проекта подробно описано в соответствующем разделе документации Build Your Own Watchface [1]. Не буду повторятся, а сразу перейду к

ОсобенностиЧто будет отличать наше приложение от полутора десятков из Examples и от сотен с mypebblefaces: Обо всем по порядку:7-segment шрифт Для отображения цифр понадобятся два набора, основной большой (цифра 20×38 px), для времени: b8105fe12bce470daf90eebf00b64f57.png и дополнительный маленький (цифра 8×16 px), для даты и секунд: ea4b99a396b743c88eb197ca8470104e.png Оба набора отрисованы в графическом редакторе, в виде двухцветного png-файла.Подключаем их как ресурсы в appinfo.json: «resources»: { «media»: [ { «type»: «png», «name»: «DIGITS», «file»: «images/digits.png» }, { «type»: «png», «name»: «DIGITS_MIDI», «file»: «images/digits_midi.png» } ] Графический фреймворк описан в документации [2]. Нас интересует раздел, касающейся работы с растровыми изображениями [3].Подготовительные действия для работы с растром, создание из ресурса вынесем в отдельную функцию: /*…*/

static GBitmap *bmp_digits; static GBitmap *bmp_digits_midi; /*…*/

static void load_resources () { bmp_digits = gbitmap_create_with_resource (RESOURCE_ID_DIGITS); bmp_digits_midi = gbitmap_create_with_resource (RESOURCE_ID_DIGITS_MIDI); } /*…*/

static void window_load (Window *window) { load_resources (); } не забудем освободить ресурсы: static void destroy_resources () { gbitmap_destroy (bmp_digits); gbitmap_destroy (bmp_digits_midi); } /*…*/

static void window_unload (Window *window) { destroy_resources (); } Наборы цифр считаны из ресурсов и созданы в виде растра в памяти, теперь необходима функция отрисовки отдельной цифры, при вызове функции указываем графический контекст, на котором будем рисовать, исходный набор из которого мы должны «выдрать» изображение и порядковый номер изображения в наборе: /* ctx — графический контекст; sources — исходное изображение; bounces — координаты и размер изображения для отрисовки; number — порядковый номер изображения в наборе. */

static void draw_picture (GContext* ctx, GBitmap **sources, GRect bounces, int number) { GPoint origin = bounces.origin; bounces.origin = GPoint (bounces.size.w*number, 0); GBitmap* temp = gbitmap_create_as_sub_bitmap (*sources, bounces); bounces.origin = origin; graphics_draw_bitmap_in_rect (ctx, temp, bounces); gbitmap_destroy (temp); } и для примера, чтобы нарисовать троечку по координатам (10, 0) в контексте: draw_picture (ctx, &bmp_digits, GRect (10, 0, 20, 38), 3); к содержаниюДва экрана Так как у нас будет два независимых экрана, каждый со своим наполнением, реализуем их в виде отдельных слоев, размером с экран часов: /*…*/ static Layer *standby_layer; static Layer *info_layer; /*…*/

static void window_load (Window *window) { Layer *window_layer = window_get_root_layer (window); GRect bounds = layer_get_bounds (window_layer);

load_resources ();

standby_layer = layer_create (bounds); layer_add_child (window_layer, standby_layer); info_layer = layer_create (bounds); layer_add_child (window_layer, info_layer); } Переключение между экранами вынесем в обработчик сервиса «Tick Timer»: /*…*/ int current_screen = 0; /*…*/ static void tick_handler (struct tm *tick_time, TimeUnits units_changed) { // Каждую минуту помечаем «standby» для отрисовки if (units_changed & MINUTE_UNIT) { layer_mark_dirty (standby_layer); };

switch (current_screen) { case 0: // Если слой «standby» скрыт, делаем его видимым и убираем «info» if (layer_get_hidden (standby_layer)) { layer_set_hidden (info_layer, true); layer_set_hidden (standby_layer, false); }; break; case 1: layer_mark_dirty (info_layer); // Если слой «info» скрыт, делаем его видимым и убираем «standby» if (layer_get_hidden (info_layer)) { layer_set_hidden (standby_layer, true); layer_set_hidden (info_layer, false); // В зависимости от настройки запускаем таймер возврата на «standby» if (settings.s_auto) { standby_timer = app_timer_register (30000, timer_callback, NULL); }; }; break; }; }

static void init (void) { /*…*/ tick_timer_service_subscribe (SECOND_UNIT, tick_handler); } Вот и подошли к тому, что нам уже надо вырисовывать контент на экранах. Для примера код для отображения контента на экране ожидания — цифровой циферблат.Для начала зададим функцию отрисовки для слоя «standby_layer», которая автоматически вызывается, когда это необходимо: static void window_load (Window *window) { standby_layer = layer_create (bounds); layer_add_child (window_layer, standby_layer); layer_set_update_proc (standby_layer, update_standby); } и реализуем отрисовку контента: update_standby static void update_standby (Layer *layer, GContext* ctx) { GRect bounds = layer_get_bounds (layer);

// Цвет фона — черный graphics_context_set_fill_color (ctx, GColorBlack); // Режим композитинга — инвернтный graphics_context_set_compositing_mode (ctx, GCompOpAssignInverted); // Заливаем слой graphics_fill_rect (ctx, bounds, 0, GCornerNone);

time_t temp = time (NULL); struct tm *tick_time = localtime (&temp);

int hour_dicker = tick_time→tm_hour/10; int hour_unit = tick_time→tm_hour%10;

int min_dicker = tick_time→tm_min/10; int min_unit = tick_time→tm_min%10;

// Рисуем цифры draw_picture (ctx, &bmp_digits, GRect (20, 55, 20, 38), hour_dicker); draw_picture (ctx, &bmp_digits, GRect (42, 55, 20, 38), hour_unit);

draw_picture (ctx, &bmp_digits, GRect (78, 55, 20, 38), min_dicker); draw_picture (ctx, &bmp_digits, GRect (100, 55, 20, 38), min_unit);

// Рисуем разделитель graphics_context_set_fill_color (ctx, GColorWhite); GRect frame = (GRect) { .origin = GPoint (bounds.size.w/2–4, 63), .size = GSize (4, 4) }; graphics_fill_rect (ctx, frame, 0, GCornerNone); frame = (GRect) { .origin = GPoint (bounds.size.w/2–4, 81), .size = GSize (4, 4) }; graphics_fill_rect (ctx, frame, 0, GCornerNone); } Результат: c8a99887bbdb47b7a7ce879a2e21e419.pngаналогично, используя draw_picture рисуем информационный экран, подробнее в исходниках.Результат: 7b86acb0c1b0458c86f84262a12daf7c.pngк содержаниюПереключение между экранами Для переключения экранов задействуем встроенный акселерометр [4]. Для этого подпишемся на «Tap Event Service»: static void tap_handler (AccelAxisType axis, int32_t direction) { current_screen = ! current_screen; }

static void init (void) { /*…*/

tick_timer_service_subscribe (SECOND_UNIT, tick_handler); accel_tap_service_subscribe (tap_handler); } к содержаниюПереход в «standby» Для автоматического перехода на экран ожидания воспользуемся таймером [4]. /*…*/ AppTimer *standby_timer = NULL; /*…*/

static void timer_callback () { current_screen = 0; }

static void tick_handler (struct tm *tick_time, TimeUnits units_changed) { /*…*/ case 1: layer_mark_dirty (info_layer); // Если слой «info» скрыт, делаем его видимым и убираем «standby» if (layer_get_hidden (info_layer)) { layer_set_hidden (standby_layer, true); layer_set_hidden (info_layer, false); // В зависимости от настройки запускаем таймер возврата на «standby» if (settings.s_auto) { standby_timer = app_timer_register (30000, timer_callback, NULL); }; }; break; /*…*/ } к содержаниюСостояние батарейки и bluetooth Для отображения состояния батарейки [6] создадим ресурс с изображениями, соответствующими десяткам процентов (с такой точностью API отдает величину заряда): 69cfca49f0fb4f2994e95b1078567be2.png «resources»: { «media»: [ /*…*/ { «type»: «png», «name»: «BATTERY», «file»: «images/battery.png» }, /*…*/ ] } И рисуем на соответствующем экране: /*…*/ BatteryChargeState charge_state = battery_state_service_peek (); int bat_percent = charge_state.charge_percent/10; if (charge_state.is_charging) { bat_percent = 110/10; }; draw_picture (ctx, &bmp_battery, GRect (0, 0, 8, 15), bat_percent); /*…*/ Состояние bluetooth [7] отображаем соответствующей иконкой: «resources»: { «media»: [ /*…*/ { «type»: «png», «name»: «BT», «file»: «images/bluetooth.png» }, /*…*/ ] } if (bluetooth_connection_service_peek ()) { draw_picture (ctx, &bmp_bt, GRect (0, 0, 8, 15), 0); }; к содержанию

Итог: watchface, который основное время не раздражает избыточной информацией, читаем и довольно смотрибелен, при желании делится расширенной информацией.

Для заинтересовавшихся: Код проекта на BitbucketПриложение в Pebble App Store

1. Pebble Developers // Build Your Own Watchface2. Pebble Developers // Graphics3. Pebble Developers // Graphics Types4. Pebble Developers // Detecting Acceleration5. Pebble Developers // Timer6. Pebble Developers // Measuring Battery Level7. Pebble Developers // Managing Bluetooth Events

© Habrahabr.ru