Pebble Watch Face SDK Tutorial #5: Animations, Images and Fonts

Links to Previous Sections:

Here are links to the previous sections of this tutorial. Read them if you haven’t to get up to speed!

Part #1: Beginner’s Primer to the C Language

Part #2: Applying the Primer to the Pebble SDK

Part #3: Setting Up a Ubuntu Environment for Development

Part #4: Anatomy of your First Watch Face

Introduction

In this section we will be looking at some more advanced features of the Pebble SDK you can apply to your watch faces. These are animations, images and fonts.

Animations allow you to create smooth movement of Layers on screen. Images can  enhance the look and feel of your watch face but come with a few constraints. Fonts offer the easiest real customisation of the text elements of the watch face, most importantly those that tell the time!

As each new feature type is introduced, I’ll explain the conditions and limitations, the functions used to implement the them as well as sample functions I’ve written as ‘wrappers’ to the lines of code you may find yourself writing together over and over again, to make things simpler.

The Resource Map

First, a key part of any watch face that uses outside resources such as bitmap images and fonts is the Resource Map file. This is a JSON file that tells the compiler where all the resources you have used in your watch face are, so it can include them in the install package.

Whereas your source file lives in /src/, the resource map lives in /resources/src/. The resources themselves are easiest located along side, for example, an image might go in /resources/src/images/ and a font in /resources/src/fonts/.

The exact syntax of each resource referenced in this file can be found here, but the form is easy to copy or mimic for each new resource you add, with a couple of example below:

JSON example

Don’t worry if you can’t make complete sense of straight away!

In the source file side of things, you must initialise the resources you plan to use in the ‘handle_init’ function before you can use them. This is shown below:

Resource init

Make sure the APP_RESOURCES name matches that in the versionDefName in the JSON file itself.

This function call allows you to use the resources as named in the JSON file when functions ask you for the RESOURCE_ID.

Animations

Animations allow a static watch face to come alive and become more expressive. An example of an implementation of these can be found in my Split Horizon: Seconds Edition watch face. Blocks slide in from the top to mark the 15, 30, 45 and 60 second marks, and two half-screen-sized InverterLayers come in and out again to reveal the new time when the minute ticks over.

To do all this, you need to use a structure called PropertyAnimation. When using this to animate a Layer of your choice, you must do a number of steps in a certain order.

  1. First, initalise the PropertyAnimation with the PropertyAnimation structure, the Layer to be moved and start and end locations (GRects)
  2. Set the duration of the animation (in milliseconds) and the curve type (Easing in or out for example)
  3. Schedule the animation. As soon as this is called, the animation will start pretty much instantaneously.

As promised earlier, here is a function that neatly wraps up all this into one function call you can use to make the main function easier to read (Click the image to see it better):

AnimateLayer

An extra stage you can take to make the animations more complex is to use the delay feature. With a second tick handler, the fastest you can have animations start is on each second tick. But with the delay feature, you can add, say, a 500 ms delay and have one animation start half a second after the first. Here is another wrapper function, see if you can spot the difference:

AnimateLayerLater

The final note on this is that at the moment the PropertyAnimation can only animate properties of a Layer. There are details in the API documentation the show you how to implement an animation for pretty much anything, but it is beyond the scope of this tutorial series.

Images (Bitmaps)

Another way to add detail to your watch  face is to include some images. Some traditional watch faces use a bitmap background with hands or text time drawn on top for added effect.

The recommended format of images to be used is a 2-colour (black and white in other words) ‘.png’ file that is less than the size of the screen (common sense, to save space). Here is an example image:

BNW PNG 1

Due to the black and white nature of these images, shades of gray are impossible. The next best thing however is to use a technique known as ‘dithering’, which alternates black and white pixels to emulate a shade of gray from a distance.

BNW PNG 2

BNW PNG 3

Getting the balance right with dithering is all trial and error, but I find that 60% or 80% is a good value, depending on the nature of the image.

Now that you have your image, place it in the right directory. You can choose this but the path must be mentioned relative to the project root folder (The folder containing the /src/ and /resources/ directories). Once this is done, add a reference to it in the JSON file, with a memorable name. An example is below:

png def

The ‘type’ is the file type. The ‘defName’ is the name you will use in the source file. The ‘file’ is the path of the resource file relative to the JSON file.

The next step is to make sure you are initialising the Resources in your main source file, as shown in the ‘The Resource Map’ section above.

Finally, call the requisite functions from the API documentation to initialise and place your image on the watch face. Again, here is an annotated wrapper function as an example:

setImage

Fonts

Finally we come to fonts. These are slightly easier to use than bitmaps, but still require the proper declaration in the JSON, initialising app resources etc.

A font file to be used on a watch face should be a ‘.ttf’ TrueType font. Many are available for free from sites on the web. Once you have one you like (You can install it and test it out in Word or the system font viewer), place it in the correct folder. Again, you can choose this, but it must be written precisely in the JSON declaration. Here is an example:

def font

It is important to note that the point size of the font to be used is declared using the ‘_XX’ at the end of  the defName in the JSON file. See the example image above for just that (This will be font size 30). Therefore you can have almost as many font sizes as you like from just one font file. You do not need to include multiple copies of the same ‘.ttf’ file for each size font you want to use.

Once this is done, and the app resources have been initialised as mentioned in the last two sections, you can use it as a font in a TextLayer of your choosing in the watch face source file. Here is how that is done (This is another long one, click it to see it clearly):

setFont

Conclusion

So there you have animations, images and fonts. Go wild. But not too wild, because there are file size limits on the install packages (‘.pbw’ files), although I’ve yet to hit them in normal use.

Next Time

In the next part I will be detailing how to perform simple communications with a connected Android app. I’m afraid that due to available devices and iOS costs, Android will be the only perspective offered. Sorry!

If you are unfamiliar with Java and/or Android app development, speak up either here or on my General Discussion thread and I will fill in the gap with a Java and Android Primer!

Advertisements
22 comments
  1. Do you have an example of these in dropbox like you did in the previous step of the tutorial?

    • bonsitm said:

      Hi Al,

      Do you require the code segments as text or example watch faces that use them?

      I can supply both 🙂

      • connoryfgr said:

        Could you please send me the examples of an animated pebble watchface? All of it! Email me please at: connorjewiss.pebble@gmail.com

        Thanks!

      • bonsitm said:

        You can find a few examples of animated watchfaces on my GitHub page ‘C-D-Lewis’. If you are unable to find them, let me know and I will send code more directly.

  2. Sam said:

    I cannot get the image to work, whenever I load up my watchface on my pebble it crashes my device.

    • bonsitm said:

      Hi Sam, are there any compiler warnings? Can I see your source code?

  3. wmarbut said:

    These have been great articles! Thanks!

    • bonsitm said:

      Thanks for the feedback! Glad you enjoyed.

  4. hi, i have one question…. the method put image in watchface is the same for put image in a app?

    • bonsitm said:

      Indeed it is! There is little difference between the two types of watch apps at this time.

      • Thanks for your answer, this is my first pebble development and does not show me the picture you have any idea that can be?

      • bonsitm said:

        Congrats on getting started! If you share your code I could have a look?

  5. #include “pebble_os.h”
    #include “pebble_app.h”
    #include “pebble_fonts.h”
    #include “resource_ids.auto.h”

    #define MY_UUID { 0x5E, 0x92, 0xA9, 0xD2, 0x2F, 0xDA, 0x45, 0xBC, 0x83, 0x60, 0x75, 0x1E, 0x55, 0x72, 0x21, 0xCF }
    PBL_APP_INFO(MY_UUID, “CallDialer”, “Lobozoldick”, 1,0, RESOURCE_ID_IMAGE_MENU_ICON,APP_INFO_STANDARD_APP /* App version */);

    Window window;

    //BitmapLayer backgroundImage;
    //const int IMAGE_RESOURCE_IDS[1] = {RESOURCE_ID_IMAGE_BACKGROUND};
    //BmpContainer image_containers;

    BmpContainer container;
    int WIDTH = 144;
    int HEIGHT = 168;

    TextLayer textLayer;

    //Especial method
    void setImage(BmpContainer *container, const int resourceId, GRect bounds){
    //Deinit old
    //layer_remove_from_aprent(&container->layer.layer);
    //bmp_deinit_container(container);

    //Init new
    bmp_init_container(resourceId, container);

    //set layer bounds
    layer_set_frame(&container->layer.layer, bounds);

    //Add to window
    layer_add_child(&window.layer, &container->layer.layer);

    }

    // Modify these common button handlers

    void up_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
    (void)recognizer;
    (void)window;

    }

    void down_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
    (void)recognizer;
    (void)window;

    }

    void select_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
    (void)recognizer;
    (void)window;

    text_layer_set_text(&textLayer, “Segunda Página”);
    }

    void select_long_click_handler(ClickRecognizerRef recognizer, Window *window) {
    (void)recognizer;
    (void)window;

    }

    // This usually won’t need to be modified

    void click_config_provider(ClickConfig **config, Window *window) {
    (void)window;

    config[BUTTON_ID_SELECT]->click.handler = (ClickHandler) select_single_click_handler;

    config[BUTTON_ID_SELECT]->long_click.handler = (ClickHandler) select_long_click_handler;

    config[BUTTON_ID_UP]->click.handler = (ClickHandler) up_single_click_handler;
    config[BUTTON_ID_UP]->click.repeat_interval_ms = 100;

    config[BUTTON_ID_DOWN]->click.handler = (ClickHandler) down_single_click_handler;
    config[BUTTON_ID_DOWN]->click.repeat_interval_ms = 100;

    }

    // Standard app initialisation

    void handle_init(AppContextRef ctx) {
    (void)ctx;

    window_init(&window, “CallDialer”);
    window_stack_push(&window, true /* Animated */);

    //Layer *root = window_get_root_layer(&window);

    //call method insert image
    setImage(&container, RESOURCE_ID_IMAGE_BACKGROUND, GRect(0,0,WIDTH,HEIGHT));
    text_layer_init(&textLayer, window.layer.frame);
    text_layer_set_text(&textLayer, “Página Inicial”);
    text_layer_set_font(&textLayer, fonts_get_system_font(FONT_KEY_GOTHAM_30_BLACK));
    layer_add_child(&window.layer, &textLayer.layer);

    // Attach our desired button functionality
    window_set_click_config_provider(&window, (ClickConfigProvider) click_config_provider);
    }

    void pbl_main(void *params) {
    PebbleAppHandlers handlers = {
    .init_handler = &handle_init
    };
    app_event_loop(params, &handlers);
    }

    • bonsitm said:

      See my reply to your code post.

      • yeaaaaaa, yes that was it, my problem was that I put this simple instruction.

        Really sorry, I’m sorry, but I will try noticing the best way for the next time.

      • bonsitm said:

        Do not worry! Making mistakes is the best way of learning! Glad I could help fix the problem! 🙂

  6. one more question bonsitm…. you know where you read about how to identify the distance between the watch and the ipohne, which when you leave a defined amount of meters the watch vibrates?

    • bonsitm said:

      I’m not sure how you’d go about that. You could use a regular signal which causes a vibration if it is not received on time.

      • What happens is I want to create an app to help me remember that if I leave the iphone over 3 meters with a vibration alert me.

      • bonsitm said:

        I do understand, but I don’t know how it can be done. You can try and implement the method I just described.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: