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 GBitmap
s and BitmapLayer
s to
improve the aesthetics of our watchface. By the end of this section, our app
will look like this:
###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:
###Adding Bitmaps
The next logical way to improve the watchface is to add some complementary
images. These take the form of GBitmap
s, 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:
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
. Layer
s 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:
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:
- Modify the App Kind to be a watchface instead of a watchapp.
- Add a subscription to the
TickTimerService
to get the current time. - Modify the
TextLayer
layout to better suit the task of telling the time. - Add an image resource to the project
- Display that image using a combination of
GBitmap
andBitmapLayer
.
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 Timer
s and Animation
s 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.