mirror of
https://github.com/google/pebble.git
synced 2025-05-21 02:45:00 +00:00
Import the pebble dev site into devsite/
This commit is contained in:
parent
3b92768480
commit
527858cf4c
1359 changed files with 265431 additions and 0 deletions
475
devsite/source/tutorials/watchface-tutorial/part1.md
Normal file
475
devsite/source/tutorials/watchface-tutorial/part1.md
Normal file
|
@ -0,0 +1,475 @@
|
|||
---
|
||||
# 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.
|
||||
|
||||
layout: tutorials/tutorial
|
||||
tutorial: watchface
|
||||
tutorial_part: 1
|
||||
|
||||
title: Build Your Own Watchface in C
|
||||
description: A guide to making a new Pebble watchface with the Pebble C API
|
||||
permalink: /tutorials/watchface-tutorial/part1/
|
||||
menu_section: tutorials
|
||||
generate_toc: true
|
||||
platform_choice: true
|
||||
---
|
||||
|
||||
In this tutorial we'll cover the basics of writing a simple watchface with
|
||||
Pebble's C API. Customizability is at the heart of the Pebble philosophy, so
|
||||
we'll be sure to add some exciting features for the user!
|
||||
|
||||
When we are done this section of the tutorial, you should end up with a brand
|
||||
new basic watchface looking something like this:
|
||||
|
||||
{% screenshot_viewer %}
|
||||
{
|
||||
"image": "/images/getting-started/watchface-tutorial/1-time.png",
|
||||
"platforms": [
|
||||
{"hw": "aplite", "wrapper": "steel-black"},
|
||||
{"hw": "basalt", "wrapper": "time-red"},
|
||||
{"hw": "chalk", "wrapper": "time-round-rosegold-14"}
|
||||
]
|
||||
}
|
||||
{% endscreenshot_viewer %}
|
||||
|
||||
|
||||
## First Steps
|
||||
|
||||
So, let's get started!
|
||||
|
||||
^CP^ Go to [CloudPebble]({{ site.links.cloudpebble }}) and click 'Get Started'
|
||||
to log in using your Pebble account, or create a new one if you do not already
|
||||
have one. Next, click 'Create' to create a new project. Give your project a
|
||||
suitable name, such as 'Tutorial 1' and leave the 'Project Type' as 'Pebble C
|
||||
SDK', with a 'Template' of 'Empty project', as we will be starting from scratch
|
||||
to help maximize your understanding as we go.
|
||||
|
||||
^LC^ Before you can start the tutorial you will need to have the Pebble SDK
|
||||
installed. If you haven't done this yet, go to our [download page](/sdk) to grab
|
||||
the SDK and follow the instructions to install it on your machine. Once you've
|
||||
done that you can come back here and carry on where you left off.
|
||||
|
||||
^LC^ Once you have installed the SDK, navigate to a directory of your choosing
|
||||
and run `pebble new-project watchface` (where 'watchface' is the name of your
|
||||
new project) to start a new project and set up all the relevant files.
|
||||
|
||||
^CP^ Click 'Create' and you will see the main CloudPebble project screen. The
|
||||
left menu shows all the relevant links you will need to create your watchface.
|
||||
Click on 'Settings' and you will see the name you just supplied, along with
|
||||
several other options. As we are creating a watchface, change the 'App Kind' to
|
||||
'Watchface'.
|
||||
|
||||
^LC^ In an SDK project, all the information about how an app is configured (its
|
||||
name, author, capabilities and resource listings etc) is stored in a file in the
|
||||
project root directory called `package.json`. Since this project will be a
|
||||
watchface, you will need to modify the `watchapp` object in this file to reflect
|
||||
this:
|
||||
|
||||
<div class="platform-specific" data-sdk-platform="local">
|
||||
{% highlight {} %}
|
||||
"watchapp": {
|
||||
"watchface": true
|
||||
}
|
||||
{% endhighlight %}
|
||||
</div>
|
||||
|
||||
The main difference between the two kinds are that watchfaces serve as the
|
||||
default display on the watch, with the Up and Down buttons allowing use of the
|
||||
Pebble timeline. This means that these buttons are not available for custom
|
||||
behavior (Back and Select are also not available to watchfaces). In contrast,
|
||||
watchapps are launched from the Pebble system menu. These have more capabilities
|
||||
such as button clicks and menu elements, but we will come to those later.
|
||||
|
||||
^CP^ Finally, set your 'Company Name' and we can start to write some code!
|
||||
|
||||
^LC^ Finally, set a value for `companyName` and we can start to write some code!
|
||||
|
||||
|
||||
## Watchface Basics
|
||||
|
||||
^CP^ Create the first source file by clicking 'Add New' on the left menu,
|
||||
selecting 'C file' as the type and choosing a suitable name such as 'main.c'.
|
||||
Click 'Create' and you will be shown the main editor screen.
|
||||
|
||||
^LC^ Our first source file is already created for you by the `pebble` command
|
||||
line tool and lives in the project's `src` directory. By default, this file
|
||||
contains sample code which you can safely remove, since we will be starting from
|
||||
scratch. Alternatively, you can avoid this by using the `--simple` flag when
|
||||
creating the project.
|
||||
|
||||
Let's add the basic code segments which are required by every watchapp. The
|
||||
first of these is the main directive to use the Pebble SDK at the top of the
|
||||
file like so:
|
||||
|
||||
```c
|
||||
#include <pebble.h>
|
||||
```
|
||||
|
||||
After this first line, we must begin with the recommended app structure,
|
||||
specifically a standard C `main()` function and two other functions to help us
|
||||
organize the creation and destruction of all the Pebble SDK elements. This helps
|
||||
make the task of managing memory allocation and deallocation as simple as
|
||||
possible. Additionally, `main()` also calls ``app_event_loop()``, which lets the
|
||||
watchapp wait for system events until it exits.
|
||||
|
||||
^CP^ The recommended structure is shown below, and you can use it as the basis
|
||||
for your own watchface file by copying it into CloudPebble:
|
||||
|
||||
^LC^ The recommended structure is shown below, and you can use it as the basis
|
||||
for your main C file:
|
||||
|
||||
```c
|
||||
#include <pebble.h>
|
||||
|
||||
static void init() {
|
||||
|
||||
}
|
||||
|
||||
static void deinit() {
|
||||
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
init();
|
||||
app_event_loop();
|
||||
deinit();
|
||||
}
|
||||
```
|
||||
|
||||
To add the first ``Window``, we first declare a static pointer to a ``Window``
|
||||
variable, so that we can access it wherever we need to, chiefly in the `init()`
|
||||
and `deinit()` functions. Add this declaration below `#include`, prefixed with
|
||||
`s_` to denote its `static` nature (`static` here means it is accessible only
|
||||
within this file):
|
||||
|
||||
```c
|
||||
static Window *s_main_window;
|
||||
```
|
||||
|
||||
The next step is to create an instance of ``Window`` to assign to this pointer,
|
||||
which we will do in `init()` using the appropriate Pebble SDK functions. In this
|
||||
process we also assign two handler functions that provide an additional layer of
|
||||
abstraction to manage the subsequent creation of the ``Window``'s sub-elements,
|
||||
in a similar way to how `init()` and `deinit()` perform this task for the
|
||||
watchapp as a whole. These two functions should be created above `init()` and
|
||||
must match the following signatures (the names may differ, however):
|
||||
|
||||
```c
|
||||
static void main_window_load(Window *window) {
|
||||
|
||||
}
|
||||
|
||||
static void main_window_unload(Window *window) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
With this done, we can complete the creation of the ``Window`` element, making
|
||||
reference to these two new handler functions that are called by the system
|
||||
whenever the ``Window`` is being constructed. This process is shown below, and
|
||||
takes place in `init()`:
|
||||
|
||||
```c
|
||||
static void init() {
|
||||
// Create main Window element and assign to pointer
|
||||
s_main_window = window_create();
|
||||
|
||||
// Set handlers to manage the elements inside the Window
|
||||
window_set_window_handlers(s_main_window, (WindowHandlers) {
|
||||
.load = main_window_load,
|
||||
.unload = main_window_unload
|
||||
});
|
||||
|
||||
// Show the Window on the watch, with animated=true
|
||||
window_stack_push(s_main_window, true);
|
||||
}
|
||||
```
|
||||
|
||||
A good best-practice to learn at this early stage is to match every Pebble SDK
|
||||
`_create()` function call with the equivalent `_destroy()` function to make sure
|
||||
all memory used is given back to the system when the app exits. Let's do this
|
||||
now in `deinit()` for our main ``Window`` element:
|
||||
|
||||
```c
|
||||
static void deinit() {
|
||||
// Destroy Window
|
||||
window_destroy(s_main_window);
|
||||
}
|
||||
```
|
||||
|
||||
We can now compile and run this watchface, but it will not show anything
|
||||
interesting yet. It is also a good practice to check that our code is still
|
||||
valid after each iterative change, so let's do this now.
|
||||
|
||||
|
||||
## First Compilation and Installation
|
||||
|
||||
^CP^ To compile the watchface, make sure you have saved your C file by clicking
|
||||
the 'Save' icon on the right of the editor screen and then proceed to the
|
||||
'Compilation' screen by clicking the appropriate link on the left of the screen.
|
||||
Click 'Run Build' to start the compilation process and wait for the result.
|
||||
Hopefully the status should become 'Succeeded', meaning the code is valid and
|
||||
can be run on the watch.
|
||||
|
||||
^LC^ To compile the watchface, make sure you have saved your project files and
|
||||
then run `pebble build` from the project's root directory. The installable
|
||||
`.pbw` file will be deposited in the `build` directory. After a successful
|
||||
compile you will see a message reading `'build' finished successfully`. If there
|
||||
are any problems with your code, the compiler will tell you which lines are in
|
||||
error so you can fix them.
|
||||
|
||||
In order to install your watchface on your Pebble, first
|
||||
[setup the Pebble Developer Connection](/guides/tools-and-resources/developer-connection/).
|
||||
Make sure you are using the latest version of the Pebble app.
|
||||
|
||||
^CP^ Click 'Install and Run' and wait for the app to install.
|
||||
|
||||
^LC^ Install the watchapp by running `pebble install`, supplying your phone's IP
|
||||
address with the `--phone` flag. For example: `pebble install
|
||||
--phone 192.168.1.78`.
|
||||
|
||||
<div class="platform-specific" data-sdk-platform="local">
|
||||
{% markdown {} %}
|
||||
> Instead of using the --phone flag every time you install, set the PEBBLE_PHONE environment variable:
|
||||
> `export PEBBLE_PHONE=192.168.1.78` and simply use `pebble install`.
|
||||
{% endmarkdown %}
|
||||
</div>
|
||||
|
||||
Congratulations! You should see that you have a new item in the watchface menu,
|
||||
but it is entirely blank!
|
||||
|
||||
{% screenshot_viewer %}
|
||||
{
|
||||
"image": "/images/getting-started/watchface-tutorial/1-blank.png",
|
||||
"platforms": [
|
||||
{"hw": "aplite", "wrapper": "steel-black"},
|
||||
{"hw": "basalt", "wrapper": "time-red"},
|
||||
{"hw": "chalk", "wrapper": "time-round-rosegold-14"}
|
||||
]
|
||||
}
|
||||
{% endscreenshot_viewer %}
|
||||
|
||||
Let's change that with the next stage towards a basic watchface - the
|
||||
``TextLayer`` element.
|
||||
|
||||
|
||||
## Showing Some Text
|
||||
|
||||
^CP^ Navigate back to the CloudPebble code editor and open your main C file to
|
||||
continue adding code.
|
||||
|
||||
^LC^ Re-open your main C file to continue adding code.
|
||||
|
||||
The best way to show some text on a watchface or watchapp
|
||||
is to use a ``TextLayer`` element. The first step in doing this is to follow a
|
||||
similar procedure to that used for setting up the ``Window`` with a pointer,
|
||||
ideally added below `s_main_window`:
|
||||
|
||||
```c
|
||||
static TextLayer *s_time_layer;
|
||||
```
|
||||
|
||||
This will be the first element added to our ``Window``, so we will make the
|
||||
Pebble SDK function calls to create it in `main_window_load()`. After calling
|
||||
``text_layer_create()``, we call other functions with plain English names that
|
||||
describe exactly what they do, which is to help setup layout properties for the
|
||||
text shown in the ``TextLayer`` including colors, alignment and font size. We
|
||||
also include a call to ``text_layer_set_text()`` with "00:00" so that we can
|
||||
verify that the ``TextLayer`` is set up correctly.
|
||||
|
||||
The layout parameters will vary depending on the shape of the display. To easily
|
||||
specify which value of the vertical position is used on each of the round and
|
||||
rectangular display shapes we use ``PBL_IF_ROUND_ELSE()``. Thus
|
||||
`main_window_load()` becomes:
|
||||
|
||||
```c
|
||||
static void main_window_load(Window *window) {
|
||||
// Get information about the Window
|
||||
Layer *window_layer = window_get_root_layer(window);
|
||||
GRect bounds = layer_get_bounds(window_layer);
|
||||
|
||||
// Create the TextLayer with specific bounds
|
||||
s_time_layer = text_layer_create(
|
||||
GRect(0, PBL_IF_ROUND_ELSE(58, 52), bounds.size.w, 50));
|
||||
|
||||
// Improve the layout to be more like a watchface
|
||||
text_layer_set_background_color(s_time_layer, GColorClear);
|
||||
text_layer_set_text_color(s_time_layer, GColorBlack);
|
||||
text_layer_set_text(s_time_layer, "00:00");
|
||||
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
|
||||
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
|
||||
|
||||
// Add it as a child layer to the Window's root layer
|
||||
layer_add_child(window_layer, text_layer_get_layer(s_time_layer));
|
||||
}
|
||||
```
|
||||
|
||||
Note the use of SDK values such as ``GColorBlack`` and `FONT_KEY_BITHAM_42_BOLD`
|
||||
which allow use of built-in features and behavior. These examples here are the
|
||||
color black and a built in system font. Later we will discuss loading a custom
|
||||
font file, which can be used to replace this value.
|
||||
|
||||
Just like with ``Window``, we must be sure to destroy each element we create. We
|
||||
will do this in `main_window_unload()`, to keep the management of the
|
||||
``TextLayer`` completely within the loading and unloading of the ``Window`` it
|
||||
is associated with. This function should now look like this:
|
||||
|
||||
```c
|
||||
static void main_window_unload(Window *window) {
|
||||
// Destroy TextLayer
|
||||
text_layer_destroy(s_time_layer);
|
||||
}
|
||||
```
|
||||
|
||||
^CP^ This completes the setup of the basic watchface layout. If you return to
|
||||
'Compilation' and install a new build, you should now see the following:
|
||||
|
||||
^LC^ This completes the setup of the basic watchface layout. If you run `pebble
|
||||
build && pebble install` (with your phone's IP address) for the new build, you
|
||||
should now see the following:
|
||||
|
||||
{% screenshot_viewer %}
|
||||
{
|
||||
"image": "/images/getting-started/watchface-tutorial/1-textlayer-test.png",
|
||||
"platforms": [
|
||||
{"hw": "aplite", "wrapper": "steel-black"},
|
||||
{"hw": "basalt", "wrapper": "time-red"},
|
||||
{"hw": "chalk", "wrapper": "time-round-rosegold-14"}
|
||||
]
|
||||
}
|
||||
{% endscreenshot_viewer %}
|
||||
|
||||
The final step is to get the current time and display it using the
|
||||
``TextLayer``. This is done with the ``TickTimerService``.
|
||||
|
||||
|
||||
## Telling the Time
|
||||
|
||||
The ``TickTimerService`` is an Event Service that allows access to the current
|
||||
time by subscribing a function to be run whenever the time changes. Normally
|
||||
this may be every minute, but can also be every hour, or every second. However,
|
||||
the latter will incur extra battery costs, so use it sparingly. We can do this
|
||||
by calling ``tick_timer_service_subscribe()``, but first we must create a
|
||||
function to give the service to call whenever the time changes, and must match
|
||||
this signature:
|
||||
|
||||
```c
|
||||
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
This means that whenever the time changes, we are provided with a data structure
|
||||
of type `struct tm` containing the current time
|
||||
[in various forms](http://www.cplusplus.com/reference/ctime/tm/), as well as a
|
||||
constant ``TimeUnits`` value that tells us which unit changed, to allow
|
||||
filtering of behaviour. With our ``TickHandler`` created, we can register it
|
||||
with the Event Service in `init()` like so:
|
||||
|
||||
```c
|
||||
// Register with TickTimerService
|
||||
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
|
||||
```
|
||||
|
||||
The logic to update the time ``TextLayer`` will be created in a function called
|
||||
`update_time()`, enabling us to call it both from the ``TickHandler`` as well as
|
||||
`main_window_load()` to ensure it is showing a time from the very beginning.
|
||||
|
||||
This function will use `strftime()`
|
||||
([See here for formatting](http://www.cplusplus.com/reference/ctime/strftime/))
|
||||
to extract the hours and minutes from the `struct tm` data structure and write
|
||||
it into a character buffer. This buffer is required by ``TextLayer`` to be
|
||||
long-lived as long as the text is to be displayed, as it is not copied into the
|
||||
``TextLayer``, but merely referenced. We achieve this by making the buffer
|
||||
`static`, so it persists across multiple calls to `update_time()`. Therefore
|
||||
this function should be created before `main_window_load()` and look like this:
|
||||
|
||||
```c
|
||||
static void update_time() {
|
||||
// Get a tm structure
|
||||
time_t temp = time(NULL);
|
||||
struct tm *tick_time = localtime(&temp);
|
||||
|
||||
// Write the current hours and minutes into a buffer
|
||||
static char s_buffer[8];
|
||||
strftime(s_buffer, sizeof(s_buffer), clock_is_24h_style() ?
|
||||
"%H:%M" : "%I:%M", tick_time);
|
||||
|
||||
// Display this time on the TextLayer
|
||||
text_layer_set_text(s_time_layer, s_buffer);
|
||||
}
|
||||
```
|
||||
|
||||
Our ``TickHandler`` follows the correct function signature and contains only a
|
||||
single call to `update_time()` to do just that:
|
||||
|
||||
```c
|
||||
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
|
||||
update_time();
|
||||
}
|
||||
```
|
||||
|
||||
Lastly, `init()` should be modified include a call to
|
||||
`update_time()` after ``window_stack_push()`` to ensure the time is displayed
|
||||
correctly when the watchface loads:
|
||||
|
||||
```c
|
||||
// Make sure the time is displayed from the start
|
||||
update_time();
|
||||
```
|
||||
|
||||
Since we can now display the time we can remove the call to
|
||||
``text_layer_set_text()`` in `main_window_load()`, as it is no longer needed to
|
||||
test the layout.
|
||||
|
||||
Re-compile and re-install the watchface on your Pebble, and it should look like
|
||||
this:
|
||||
|
||||
{% screenshot_viewer %}
|
||||
{
|
||||
"image": "/images/getting-started/watchface-tutorial/1-time.png",
|
||||
"platforms": [
|
||||
{"hw": "aplite", "wrapper": "steel-black"},
|
||||
{"hw": "basalt", "wrapper": "time-red"},
|
||||
{"hw": "chalk", "wrapper": "time-round-rosegold-14"}
|
||||
]
|
||||
}
|
||||
{% endscreenshot_viewer %}
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
So there we have it, the basic process required to create a brand new Pebble
|
||||
watchface! To do this we:
|
||||
|
||||
1. Created a new Pebble project.
|
||||
2. Setup basic app structure.
|
||||
3. Setup a main ``Window``.
|
||||
4. Setup a ``TextLayer`` to display the time.
|
||||
5. Subscribed to ``TickTimerService`` to get updates on the time, and wrote
|
||||
these to a buffer for display in the ``TextLayer``.
|
||||
|
||||
If you have problems with your code, check it against the sample source code
|
||||
provided using the button below.
|
||||
|
||||
^CP^ [Edit in CloudPebble >{center,bg-lightblue,fg-white}]({{ site.links.cloudpebble }}ide/gist/9b9d50b990d742a3ae34)
|
||||
|
||||
^LC^ [View Source Code >{center,bg-lightblue,fg-white}](https://gist.github.com/9b9d50b990d742a3ae34)
|
||||
|
||||
## What's Next?
|
||||
|
||||
The next section of the tutorial will introduce adding custom fonts and bitmap
|
||||
images to your watchface.
|
||||
|
||||
[Go to Part 2 → >{wide,bg-dark-red,fg-white}](/tutorials/watchface-tutorial/part2/)
|
Loading…
Add table
Add a link
Reference in a new issue