mirror of
https://github.com/google/pebble.git
synced 2025-03-20 19:01:21 +00:00
238 lines
9.1 KiB
Markdown
238 lines
9.1 KiB
Markdown
|
---
|
||
|
# Copyright 2025 Google LLC
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
title: Pebble SDK Tutorial (Part 2)
|
||
|
author: Chris Lewis
|
||
|
excerpt: |
|
||
|
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](/blog/2014/06/10/Pebble-SDK-Tutorial-1/)
|
||
|
|
||
|
###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](https://gist.github.com/C-D-Lewis/a911f0b053260f209390) from the
|
||
|
last post as a starting point, which you can import into
|
||
|
[CloudPebble]({{site.links.cloudpebble}}) as a new project
|
||
|
[here](https://cloudpebble.net/ide/gist/a911f0b053260f209390).
|
||
|
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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()`:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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](http://www.cplusplus.com/reference/ctime/tm/). 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:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
```c
|
||
|
//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()`:
|
||
|
|
||
|
```c
|
||
|
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:
|
||
|
|
||
|
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 ``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](https://gist.github.com/C-D-Lewis/17eb11ab355950595ca2) and directly
|
||
|
imported into CloudPebble
|
||
|
[as a new project](https://cloudpebble.net/ide/gist/17eb11ab355950595ca2). 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](http://twitter.com/PebbleDev) or
|
||
|
[@Chris_DL](http://twitter.com/Chris_DL).
|
||
|
|
||
|
|