pebble/devsite/source/_posts/2014-06-18-Your-First-Watchface.md
2025-02-24 18:58:29 -08:00

9.1 KiB

title author excerpt
Pebble SDK Tutorial (Part 2) Chris Lewis This is the second in a series of articles aimed at helping developers new and experienced begin creating their own watchapps. In this section we will be using the TickTimerService to display the current time, which is the basis of all great watchfaces. After this, we will use GBitmaps and BitmapLayers to improve the aesthetics of our watchface.

##Your First Watchface

###Previous Tutorial Parts Pebble SDK Tutorial (Part 1): Your First Watchapp

###Introduction

This is the second in a series of articles aimed at helping developers new and experienced begin creating their own watchapps. In this section we will be using the TickTimerService to display the current time, which is the basis of all great watchfaces. After this, we will use GBitmaps and BitmapLayers to improve the aesthetics of our watchface. By the end of this section, our app will look like this:

final

###Getting Started

To begin creating this watchface, we will be using the source code from the last post as a starting point, which you can import into CloudPebble as a new project here.

As you may have noticed, the current watchapp is started from the Pebble main menu, and not on the watchface carousel like other watchfaces. To change this, go to 'Settings' in your CloudPebble project and change the 'App Kind' to 'Watchface'. This will cause the app to behave in the same way as any other watchface. If you were using the native SDK, this change would be done in the appinfo.json file.

Once this is done, we will modify the main C file to prepare it for showing the time. Remove the call to text_layer_set_text() to prevent the watchface showing anything irrelevant before the time is shown. For reference, window_load() should now look like this:

void window_load(Window *window)
{
  //We will add the creation of the Window's elements here soon!
  g_text_layer = text_layer_create(GRect(0, 0, 144, 168));
  text_layer_set_background_color(g_text_layer, GColorClear);
  text_layer_set_text_color(g_text_layer, GColorBlack);
 
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
}

###Telling the Time

Now we can use the TextLayer we created earlier to display the current time, which is provided to us once we register a TickHandler with the system. The first step to do this is to create an empty function in the format dictated by the TickHandler documentation page. This is best placed before it will be used, which is above init():

void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{

}

This function is called by the system at a fixed tick rate depending on the TimeUnits value supplied when we register the handler. Let's do this now in init() before window_stack_push(), using the MINUTE_UNIT value to get an update every minute change:

tick_timer_service_subscribe(MINUTE_UNIT, (TickHandler)tick_handler);

Now that we have subscribed to the TickTimerService, we can add code to the tick_handler() function to use the time information provided in the tick_time parameter to update the TextLayer. The data stored within this structure follows the ctime struct tm format. This means that we can access the current hour by using tick_time->tm_hour and the current minute by using tick_time->tm_min, for example. Instead, we will use a function called strftime() that uses the tick_time parameter to write a time-formatted string into a buffer we provide, called buffer. Therefore the new tick handler will look like this:

void tick_handler(struct tm *tick_time, TimeUnits units_changed)
{
  //Allocate long-lived storage (required by TextLayer)
  static char buffer[] = "00:00";
  
  //Write the time to the buffer in a safe manner
  strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
  
  //Set the TextLayer to display the buffer
  text_layer_set_text(g_text_layer, buffer);
}

Now every time a minute passes tick_handler() will create a new string in the buffer and display it in the TextLayer. This is the basis of every watchface. However, the text contained in the TextLayer is difficult to read because the default system font is very samll, so we will change some of the layout properties to better suit its purpose. Modify window_load() to add the relevant TextLayer functions like so:

void window_load(Window *window)
{
  //Create the TextLayer
  g_text_layer = text_layer_create(GRect(0, 59, 144, 50));
  text_layer_set_background_color(g_text_layer, GColorClear);
  text_layer_set_text_color(g_text_layer, GColorBlack);
  
  //Improve the layout to be more like a watchface
  text_layer_set_font(g_text_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
  text_layer_set_text_alignment(g_text_layer, GTextAlignmentCenter);
 
  layer_add_child(window_get_root_layer(window), text_layer_get_layer(g_text_layer));
}

Now the watchface should look more like a classic watchface with a larger font and centered text:

largetext

###Adding Bitmaps The next logical way to improve the watchface is to add some complementary images. These take the form of GBitmaps, and are referenced by a RESOURCE_ID specified in CloudPebble Settings or appinfo.json when working with the native SDK. The first bitmap we wil add will be a smart border for the TextLayer, shown for you to use below:

frame

To add this image to our project in CloudPebble, click "Add New" next to Resources, navigate to your stored copy of the image above and give it an ID such as "FRAME", then click Save.

Once this is done, we can add it to the watchface. The first step is to declare two new global items: A GBitmap to hold the loaded image and a BitmapLayer to show it. Add these to the top of the file, alongside the existing global variables:

GBitmap *g_frame_bitmap;
BitmapLayer *g_frame_layer;

The next step is to allocate the GBitmap, show it using the BitmapLayer and add it as a child of the main Window. Layers are drawn in the order they are added to their parent, so be sure to add the BitmapLayer before the TextLayer. This will ensure that the time is drawn on top of the image, and not the other way around:

//Create and add the image
g_frame_bitmap = gbitmap_create_with_resource(RESOURCE_ID_FRAME);
g_frame_layer = bitmap_layer_create(GRect(7, 56, 129, 60));
bitmap_layer_set_bitmap(g_frame_layer, g_frame_bitmap);
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(g_frame_layer));

Once again, remember to add calls to _destroy() for each element to free memory in window_unload():

void window_unload(Window *window)
{
  //We will safely destroy the Window's elements here!
  text_layer_destroy(g_text_layer);

  gbitmap_destroy(g_frame_bitmap);
  bitmap_layer_destroy(g_frame_layer);
}

With these steps completed, a re-compile of your project should yield something similar to that shown below:

frameadded

The TextLayer is now surrounded by a crisp frame border. It's not amazing, but it's the start on a journey to a great watchface!

###Conclusion There you have it: turning your simple watchapp into a basic watchface. In summary:

  1. Modify the App Kind to be a watchface instead of a watchapp.
  2. Add a subscription to the TickTimerService to get the current time.
  3. Modify the TextLayer layout to better suit the task of telling the time.
  4. Add an image resource to the project
  5. Display that image using a combination of GBitmap and BitmapLayer.

From there you can add more images, change the text font and size and more to further improve the look of the watchface. In future posts you will learn how to use Timers and Animations to make it even better!

###Source Code Just like last time, the full source code file can be found here and directly imported into CloudPebble as a new project. If your code doesn't compile, have a look at this and see where you may have gone wrong.

Send any feedback or questions to @PebbleDev or @Chris_DL.