📜 ⬆️ ⬇️

Pebble: working with static graphics on the example of creating a 7-segment watchface

All applications for Pebble watches are divided into two categories of watchapp - just applications, and watchface - applications "watches", which, as the name suggests, are the face of the device. The difference between “faces” is the lack of response to hardkey buttons, since “UP” and “DOWN” are used to cycle through the installed watchfaces .
But, probably, due to the low resolution of the screen 144x168 px, it is quite difficult to find a watchfly that fits perfectly into the design of the watch, which at the same time performs its main function - counting time.
It seems to me that the best in this screen look the numbers in the style of seven-segment indicators.
See below for details on how to add elegant minimalism, personality and unique features to your watch face .
So, a few pictures, code snippets and as a result, a link to the finished project.


The creation of the watchface, the structure and the construction of the project are described in detail in the corresponding section of the Build Your Own Watchface documentation [1] . I will not repeat, but immediately go to

Special features


What will distinguish our application from a dozen and a half of Examples and from hundreds with mypebblefaces :

First things first:
')

7-segment font


To display the numbers, you need two sets, the main one is large (figure 20x38px), for the time:

and extra small (figure 8x16px), for the date and seconds:

Both sets are drawn in a graphic editor, in the form of a two-color png-file.
We connect them as resources in appinfo.json :
"resources": { "media": [ { "type": "png", "name": "DIGITS", "file": "images/digits.png" }, { "type": "png", "name": "DIGITS_MIDI", "file": "images/digits_midi.png" } ] 

A graphical framework is described in the documentation [2] . We are interested in the section on working with raster images [3] .
Preparatory actions for working with a raster, the creation of a resource will be put into a separate function:
 /*...*/ 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(); } 

Do not forget to free up resources:
 static void destroy_resources() { gbitmap_destroy(bmp_digits); gbitmap_destroy(bmp_digits_midi); } /*..*/ static void window_unload(Window *window) { destroy_resources(); } 

Digits are read from resources and created as a raster in memory, now we need a function of drawing a single digit, when calling a function, we specify the graphic context in which we will draw, the initial set from which we must “tear out” the image and the sequence number of the image in the set:
 /* 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); } 

and for example, to draw C grade at the coordinates (10, 0) in context:
  draw_picture(ctx, &bmp_digits, GRect(10, 0, 20, 38), 3); 

to content

Two screens


Since we will have two independent screens, each with its own content, we implement them as separate layers, the size of a clock screen:
 /*..*/ 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); } 

Switching between screens will be transferred to the “Tick Timer” service handler:
 /*..*/ 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); } 

So we come to the fact that we already need to draw content on the screens. For example, the code for displaying content on the idle screen is a digital dial.
To begin with, let's set the rendering function for the “standby_layer” layer, which is automatically called when necessary:
 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); } 

and implement content rendering:
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); } 


Result:

similarly, using draw_picture we draw an information screen, in more detail in source codes .
Result:

to content

Switch between screens


To switch screens, use the built-in accelerometer [4] . To do this, subscribe to the "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); } 

to content

Transition to "standby"


To automatically go to the idle screen, use the timer [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; /*..*/ } 

to content

Battery and bluetooth status


To display the state of the battery [6], let's create a resource with images corresponding to tens of percent (the API gives the charge with such accuracy):

 "resources": { "media": [ /*..*/ { "type": "png", "name": "BATTERY", "file": "images/battery.png" }, /*..*/ ] } 

And draw on the appropriate screen:
  /*..*/ 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); /*..*/ 

The state of bluetooth [7] is displayed with the corresponding icon:
 "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); }; 


to content

Bottom line: the watchface, which most of the time is not annoying with redundant information, is readable and quite watchable, if desired, shares extended information.

For those interested:
Bitbucket project code
App on Pebble App Store

1. Pebble Developers // Build Your Own Watchface
2. Pebble Developers // Graphics
3. Pebble Developers // Graphics Types
4. Pebble Developers // Detecting Acceleration
5. Pebble Developers // Timer
6. Pebble Developers // Measuring Battery Level
7. Pebble Developers // Managing Bluetooth Events

Source: https://habr.com/ru/post/245297/


All Articles