---
# 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: Content Size
description: |
  Details on how to use the ContentSize API to adapt your watchface layout
  based on user text size preferences.
guide_group: user-interfaces
order: 6
related_docs:
  - ContentSize
  - UnobstructedArea
related_examples:
  - title: Simple Example
    url: https://github.com/pebble-examples/feature-content-size
---

{% alert notice %}
The ContentSize API is currently only available in SDK 4.2-BETA.
{% endalert %}

The [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API, added in SDK 4.2, allows developers to dynamically
adapt their watchface and watchapp design based upon the system `Text Size`
preference (*Settings > Notifications > Text Size*).

While this allows developers to create highly accessible designs, it also serves
to provide a mechanism for creating designs which are less focused upon screen
size, and more focused upon content size.

![ContentSize >{pebble-screenshot,pebble-screenshot--time-red}](/images/guides/content-size/anim.gif)

The `Text Size` setting displays the following options on all platforms:

* Small
* Medium
* Large

Whereas, the
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
API will return different content sizes based on
the `Text Size` setting, varying by platform. The list of content sizes is:

* Small
* Medium
* Large
* Extra Large

An example of the varying content sizes:

* `Text Size`: `small` on `Basalt` is `ContentSize`: `small`
* `Text Size`: `small` on `Emery` is `ContentSize`: `medium`

The following table describes the relationship between `Text Size`, `Platform`
and `ContentSize`:

Platform | Text Size: Small | Text Size: Medium | Text Size: Large
---------|------------------|-------------------|-----------------
Aplite, Basalt, Chalk, Diorite | ContentSize: Small | ContentSize: Medium | ContentSize: Large
Emery | ContentSize: Medium | ContentSize: Large | ContentSize: Extra Large

> *At present the Text Size setting only affects notifications and some system
UI components, but other system UI components will be updated to support
ContentSize in future versions.*

We highly recommend that developers begin to build and update their applications
with consideration for
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
 to provide the best experience to users.

## Detecting ContentSize

In order to detect the current
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
 developers can use the
``preferred_content_size()`` function.

The [ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
will never change during runtime, so it's perfectly
acceptable to check this once during `init()`.

```c
static PreferredContentSize s_content_size;

void init() {
  s_content_size = preferred_content_size();
  // ...
}
```

## Adapting Layouts

There are a number of different approaches to adapting the screen layout based
upon content size. You could change font sizes, show or hide design elements, or
even present an entirely different UI.

In the following example, we will change font sizes based on the
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)

```c
static TextLayer *s_text_layer;
static PreferredContentSize s_content_size;

void init() {
  s_content_size = preferred_content_size();

  // ...
  switch (s_content_size) {
    case PreferredContentSizeMedium:
      // Use a medium font
      text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
      break;
    case PreferredContentSizeLarge:
    case PreferredContentSizeExtraLarge:
      // Use a large font
      text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
      break;
    default:
      // Use a small font
      text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
      break;
  }
  // ...
}
```

## Additional Considerations

When developing an application which dynamically adjusts based on the
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)
setting, try to avoid using fixed widths and heights. Calculate
coordinates and dimensions based upon the size of the root layer,
``UnobstructedArea`` and
[ContentSize](/docs/c/preview/User_Interface/Preferences/#preferred_content_size)


---
# 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: User Interfaces
description: |
  How to build app user interfaces. Includes information on events,
  persistent storage, background worker, wakeups and app configuration.
guide_group: user-interfaces
menu: false
permalink: /guides/user-interfaces/
generate_toc: false
hide_comments: true
---

The User Intefaces section of the developer guide contains information on using
other the Pebble SDK elements that contribute to interface with the user in some
way, shape, or form. For example, ``Layer`` objects form the foundation of all
app user interfaces, while a configuration page asks a user for their input in
terms of preferences.

Graphics-specific UI elements and resources are discussed in the
{% guide_link graphics-and-animations %} and
{% guide_link app-resources %} sections.

## Contents

{% include guides/contents-group.md group=page.group_data %}

This is the latest version of the internal repository from Pebble Technology providing the software to run on Pebble watches. Proprietary source code has been removed from this repository and it will not compile as-is. This is for information only.


---
# 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: Layers
description: |
  How to use standard Layer components to build an app's UI.
guide_group: user-interfaces
order: 3
related_docs:
  - Layer
  - LayerUpdateProc
  - Window
  - TextLayer
  - BitmapLayer
  - MenuLayer
  - ScrollLayer
---

The ``Layer`` and associated subclasses (such as ``TextLayer`` and
``BitmapLayer``) form the foundation of the UI for every Pebble watchapp or
watchface, and are added to a ``Window`` to construct the UI's design. Each
``Layer`` type contains at least three basic elements:

* Frame - contains the position and dimensions of the ``Layer``, relative to the
  parent object.

* Bounds - contains the drawable bounding box within the frame. This allows only
  a portion of the layer to be visible, and is relative to the ``Layer`` frame.

* Update procedure - the function that performs the drawing whenever the
  ``Layer`` is rendered. The subclasses implement a convenience update procedure
  with additional data to achieve their specialization.

## Layer Heirachy

Every app must consist of at least one ``Window`` in order to successfully
launch. Mutiple ``Layer`` objects are added as children of the ``Window``, which
itself contains a ``Layer`` known as the 'root layer'. When the ``Window`` is
rendered, each child ``Layer`` is rendered in the order in which they were
added. For example:

```c
static Window *s_main_window;

static BitmapLayer *s_background_layer;
static TextLayer *s_time_layer;
```

```c
// Get the Window's root layer
Layer *root_layer = window_get_root_layer(s_main_window);

/* set up BitmapLayer and TextLayer */

// Add the background layer first, so that it is drawn behind the time
layer_add_child(root_layer, bitmap_layer_get_layer(s_background_layer));

// Add the time layer second
layer_add_child(root_layer, text_layer_get_layer(s_time_layer));
```

Once added to a ``Window``, the ordering of each ``Layer`` cannot be modified,
but one can be placed at the front by removing and re-adding it to the heirachy:

```c
// Bring a layer to the front
layer_remove_from_parent(s_some_layer);
layer_add_child(root_layer, s_some_layer);
```

## Update Procedures

For creating custom drawing implementations, the basic ``Layer`` update
procedure can be reassigned to one created by a developer. This takes the form
of a ``LayerUpdateProc``, and provides a [`GContext`](``Graphics Context``)
object which can be used for drawing primitive shapes, paths, text, and images.

> Note: See {% guide_link graphics-and-animations %} for more information on
> drawing with the graphics context.

```c
static void layer_update_proc(Layer *layer, GContext *ctx) {
  // Custom drawing happens here
}
```

This function must then be assigned to the ``Layer`` that will be drawn with it:

```c
// Set this Layer's update procedure
layer_set_update_proc(s_some_layer, layer_update_proc);
```

The update procedure will be called every time the ``Layer`` must be redrawn.
This is typically when any other ``Layer`` requests a redraw, the ``Window`` is
shown/hidden, the heirarchy changes, or a modal (such as a notification) appears.
The ``Layer`` can also be manually marked as 'dirty', and will be redrawn at the
next opportunity (usually immediately):

```c
// Request a redraw
layer_mark_dirty(s_some_layer);
```

## Layer Subclasses

For convenience, there are multiple subclasses of ``Layer`` included in the
Pebble SDK to allow developers to easily construct their app's UI. Each should
be created when the ``Window`` is loading (using the `.load` ``WindowHandler``)
and destroyed when it is unloading (using `.the unload` ``WindowHandler``).

These are briefly outlined below, alongside a simple usage example split into
three code snippets - the element declarations, the setup procedure, and the
teardown procedure.

### TextLayer

The ``TextLayer`` is the most commonly used subclass of ``Layer``, and allows
apps to render text using any available font, with built-in behavior to handle
text color, line wrapping, alignment, etc.

```c
static TextLayer *s_text_layer;
```

```c
// Create a TextLayer
s_text_layer = text_layer_create(bounds);

// Set some properties
text_layer_set_text_color(s_text_layer, GColorWhite);
text_layer_set_background_color(s_text_layer, GColorBlack);
text_layer_set_overflow_mode(s_text_layer, GTextOverflowModeWordWrap);
text_layer_set_alignment(s_text_layer, GTextAlignmentCenter);

// Set the text shown
text_layer_set_text(s_text_layer, "Hello, World!");

// Add to the Window
layer_add_child(root_layer, text_layer_get_layer(s_text_layer));
```

```c
// Destroy the TextLayer
text_layer_destroy(s_text_layer);
```

### BitmapLayer

The ``BitmapLayer`` provides an easy way to show images loaded into ``GBitmap``
objects from an image resource. Images shown using a ``BitmapLayer`` are
automatically centered within the bounds provided to ``bitmap_layer_create()``.
Read {% guide_link app-resources/images %} to learn more about using image
resources in apps.

> Note: PNG images with transparency should use `bitmap` resource type, and use
> the ``GCompOpSet`` compositing mode when being displayed, as shown below.

```c
static BitmapLayer *s_bitmap_layer;
static GBitmap *s_bitmap;
```

```c
// Load the image
s_bitmap = gbitmap_create_with_resource(RESOURCE_ID_EXAMPLE_IMAGE);

// Create a BitmapLayer
s_bitmap_layer = bitmap_layer_create(bounds);

// Set the bitmap and compositing mode
bitmap_layer_set_bitmap(s_bitmap_layer, s_bitmap);
bitmap_layer_set_compositing_mode(s_bitmap_layer, GCompOpSet);

// Add to the Window
layer_add_child(root_layer, bitmap_layer_get_layer(s_bitmap_layer));
```

```c
// Destroy the BitmapLayer
bitmap_layer_destroy(s_bitmap_layer);
```

### StatusBarLayer

If a user needs to see the current time inside an app (instead of exiting to the
watchface), the ``StatusBarLayer`` component can be used to display this
information at the top of the ``Window``. Colors and separator display style can
be customized.

```c
static StatusBarLayer *s_status_bar;
```

```c
// Create the StatusBarLayer
s_status_bar = status_bar_layer_create();

// Set properties
status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorBlueMoon);
status_bar_layer_set_separator_mode(s_status_bar,
                                            StatusBarLayerSeparatorModeDotted);

// Add to Window
layer_add_child(root_layer, status_bar_layer_get_layer(s_status_bar));
```

```c
// Destroy the StatusBarLayer
status_bar_layer_destroy(s_status_bar);
```

### MenuLayer

The ``MenuLayer`` allows the user to scroll a list of options using the Up and
Down buttons, and select an option to trigger an action using the Select button.
It differs from the other ``Layer`` subclasses in that it makes use of a number
of ``MenuLayerCallbacks`` to allow the developer to fully control how it renders
and behaves. Some minimum example callbacks are shown below:

```c
static MenuLayer *s_menu_layer;
```

```c
static uint16_t get_num_rows_callback(MenuLayer *menu_layer,
                                      uint16_t section_index, void *context) {
  const uint16_t num_rows = 5;
  return num_rows;
}

static void draw_row_callback(GContext *ctx, const Layer *cell_layer,
                                        MenuIndex *cell_index, void *context) {
  static char s_buff[16];
  snprintf(s_buff, sizeof(s_buff), "Row %d", (int)cell_index->row);

  // Draw this row's index
  menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL);
}

static int16_t get_cell_height_callback(struct MenuLayer *menu_layer,
                                        MenuIndex *cell_index, void *context) {
  const int16_t cell_height = 44;
  return cell_height;
}

static void select_callback(struct MenuLayer *menu_layer,
                                        MenuIndex *cell_index, void *context) {
  // Do something in response to the button press

}
```

```c
// Create the MenuLayer
s_menu_layer = menu_layer_create(bounds);

// Let it receive click events
menu_layer_set_click_config_onto_window(s_menu_layer, window);

// Set the callbacks for behavior and rendering
menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) {
    .get_num_rows = get_num_rows_callback,
    .draw_row = draw_row_callback,
    .get_cell_height = get_cell_height_callback,
    .select_click = select_callback,
});

// Add to the Window
layer_add_child(root_layer, menu_layer_get_layer(s_menu_layer));
```

```c
// Destroy the MenuLayer
menu_layer_destroy(s_menu_layer);
```

### ScrollLayer

The ``ScrollLayer`` provides an easy way to use the Up and Down buttons to
scroll large content that does not all fit onto the screen at the same time. The
usage of this type differs from the others in that the ``Layer`` objects that
are scrolled are added as children of the ``ScrollLayer``, which is then in turn
added as a child of the ``Window``.

The ``ScrollLayer`` frame is the size of the 'viewport', while the content size
determines how far the user can scroll in each direction. The example below
shows a ``ScrollLayer`` scrolling some long text, the total size of which is
calculated with ``graphics_text_layout_get_content_size()`` and used as the
``ScrollLayer`` content size.

> Note: The scrolled ``TextLayer`` frame is relative to that of its parent, the
> ``ScrollLayer``.

```c
static TextLayer *s_text_layer;
static ScrollLayer *s_scroll_layer;
```

```c
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD);

// Find the bounds of the scrolling text
GRect shrinking_rect = GRect(0, 0, bounds.size.w, 2000);
char *text = "Example text that is really really really really really \
                              really really really really really really long";
GSize text_size = graphics_text_layout_get_content_size(text, font,
                shrinking_rect, GTextOverflowModeWordWrap, GTextAlignmentLeft);
GRect text_bounds = bounds;
text_bounds.size.h = text_size.h;

// Create the TextLayer
s_text_layer = text_layer_create(text_bounds);
text_layer_set_overflow_mode(s_text_layer, GTextOverflowModeWordWrap);
text_layer_set_font(s_text_layer, font);
text_layer_set_text(s_text_layer, text);

// Create the ScrollLayer
s_scroll_layer = scroll_layer_create(bounds);

// Set the scrolling content size
scroll_layer_set_content_size(s_scroll_layer, text_size);

// Let the ScrollLayer receive click events
scroll_layer_set_click_config_onto_window(s_scroll_layer, window);

// Add the TextLayer as a child of the ScrollLayer
scroll_layer_add_child(s_scroll_layer, text_layer_get_layer(s_text_layer));

// Add the ScrollLayer as a child of the Window
layer_add_child(root_layer, scroll_layer_get_layer(s_scroll_layer));
```

```c
// Destroy the ScrollLayer and TextLayer
scroll_layer_destroy(s_scroll_layer);
text_layer_destroy(s_text_layer);
```

### ActionBarLayer

The ``ActionBarLayer`` allows apps to use the familiar black right-hand bar,
featuring icons denoting the action that will occur when each button on the
right hand side is pressed. For example, 'previous track', 'more actions', and
'next track' in the built-in Music app.

For three or fewer actions, the ``ActionBarLayer`` can be more appropriate than
a ``MenuLayer`` for presenting the user with a list of actionable options. Each
action's icon must also be loaded into a ``GBitmap`` object from app resources.
The example below demonstrates show to set up an ``ActionBarLayer`` showing an
up, down, and checkmark icon for each of the buttons.

```c
static ActionBarLayer *s_action_bar;
static GBitmap *s_up_bitmap, *s_down_bitmap, *s_check_bitmap;
```

```c
// Load icon bitmaps
s_up_bitmap = gbitmap_create_with_resource(RESOURCE_ID_UP_ICON);
s_down_bitmap = gbitmap_create_with_resource(RESOURCE_ID_DOWN_ICON);
s_check_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CHECK_ICON);

// Create ActionBarLayer
s_action_bar = action_bar_layer_create();
action_bar_layer_set_click_config_provider(s_action_bar, click_config_provider);

// Set the icons
action_bar_layer_set_icon(s_action_bar, BUTTON_ID_UP, s_up_bitmap);
action_bar_layer_set_icon(s_action_bar, BUTTON_ID_DOWN, s_down_bitmap);
action_bar_layer_set_icon(s_action_bar, BUTTON_ID_SELECT, s_check_bitmap);

// Add to Window
action_bar_layer_add_to_window(s_action_bar, window);
```

```c
// Destroy the ActionBarLayer
action_bar_layer_destroy(s_action_bar);

// Destroy the icon GBitmaps
gbitmap_destroy(s_up_bitmap);
gbitmap_destroy(s_down_bitmap);
gbitmap_destroy(s_check_bitmap);
```


---
# 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: Round App UI
description: |
  Details on how to use the Pebble SDK to create layouts specifically for round
  displays.
guide_group: user-interfaces
order: 4
related_docs:
  - Graphics
  - LayerUpdateProc
related_examples:
  - title: Time Dots
    url: https://github.com/pebble-examples/time-dots/
  - title: Text Flow Techniques
    url: https://github.com/pebble-examples/text-flow-techniques
platforms:
  - chalk
---

> This guide is about creating round apps in code. For advice on designing a
> round app, read {% guide_link design-and-interaction/in-the-round %}.

With the addition of Pebble Time Round (the Chalk platform) to the Pebble
family, developers face a new challenge - circular apps! With this display
shape, traditional layouts will not display properly due to the obscuring of the
corners. Another potential issue is the increased display resolution. Any UI
elements that were not previously centered correctly (or drawn with hardcoded
coordinates) will also display incorrectly.

However, the Pebble SDK provides additions and functionality to help developers
cope with this way of thinking. In many cases, a round display can be an
aesthetic advantage. An example of this is the traditional circular dial
watchface, which has been emulated on Pebble many times, but also wastes corner
space. With a round display, these watchfaces can look better than ever.

![time-dots >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/time-dots.png)

## Detecting Display Shape

The first step for any app wishing to correctly support both display shapes is
to use the available compiler directives to conditionally create the UI. This
can be done as shown below:

```c
#if defined(PBL_RECT)
  printf("This code is run on a rectangular display!");

  /* Rectangular UI code */
#elif defined(PBL_ROUND)
  printf("This code is run on a round display!");

  /* Round UI code */
#endif
```

Another approach for single value selection is the ``PBL_IF_RECT_ELSE()`` and
``PBL_IF_ROUND_ELSE()`` macros, which accept two parameters for each of the
respective round and rectangular cases. For example, ``PBL_IF_RECT_ELSE()`` will
compile the first parameter on a rectangular display, and the second one
otherwise:

```c
// Conditionally print out the shape of the display
printf("This is a %s display!", PBL_IF_RECT_ELSE("rectangular", "round"));
```

## Circular Drawing

In addition to the older ``graphics_draw_circle()`` and
``graphics_fill_circle()`` functions, the Pebble SDK for the chalk platform
contains additional functions to help draw shapes better suited for a round
display. These include:

* ``graphics_draw_arc()`` - Draws a line arc clockwise between two angles within
  a given ``GRect`` area, where 0° is the top of the circle.

* ``graphics_fill_radial()`` - Fills a circle clockwise between two angles
  within a given ``GRect`` area, with adjustable inner inset radius allowing the
  creation of 'doughnut-esque' shapes.

* ``gpoint_from_polar()`` - Returns a ``GPoint`` object describing a point given
  by a specified angle within a centered ``GRect``.

In the Pebble SDK angles between `0` and `360` degrees are specified as values
scaled between `0` and ``TRIG_MAX_ANGLE`` to preserve accuracy and avoid
floating point math. These are most commonly used when dealing with drawing
circles. To help with this conversion, developers can use the
``DEG_TO_TRIGANGLE()`` macro.

An example function to draw the letter 'C' in a yellow color is shown below for
use in a ``LayerUpdateProc``.

```c
static void draw_letter_c(GRect bounds, GContext *ctx) {
  GRect frame = grect_inset(bounds, GEdgeInsets(30));

  graphics_context_set_fill_color(ctx, GColorYellow);
  graphics_fill_radial(ctx, frame, GOvalScaleModeFitCircle, 30,
                                  DEG_TO_TRIGANGLE(-225), DEG_TO_TRIGANGLE(45));
}
```

This produces the expected result, drawn with a smooth antialiased filled circle
arc between the specified angles.

![letter-c >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/letter-c.png)

## Adaptive Layouts

With not only a difference in display shape, but also in resolution, it is very
important that an app's layout not be created using hardcoded coordinates.
Consider the examples below, designed to create a child ``Layer`` to fill the
size of the parent layer.

```c
// Bad - only works on Aplite and Basalt rectangular displays
Layer *layer = layer_create(GRect(0, 0, 144, 168));

// Better - uses the native display size
GRect bounds = layer_get_bounds(parent_layer);
Layer *layer = layer_create(bounds);
```

Using this style, the child layer will always fill the parent layer, regardless
of its actual dimensions.

In a similar vein, when working with the Pebble Time Round display it can be
important that the layout is centered correctly. A set of layout values that are
in the center of the classic 144 x 168 pixel display will not be centered when
displayed on a 180 x 180 display. The undesirable effect of this can be seen in
the example shown below:

![cut-corners >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/cut-corners.png)

By using the technique described above, the layout's ``GRect`` objects can
specify their `origin` and `size` as a function of the dimensions of the layer
they are drawn into, solving this problem.

![centered >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/centered.png)

## Text Flow and Pagination

A chief concern when working with a circular display is the rendering of large
amounts of text. As demonstrated by an animation in
{% guide_link design-and-interaction/in-the-round#pagination %}, continuous
reflowing of text makes it much harder to read.

A solution to this problem is to render text while flowing within the
constraints of the shape of the display, and to scroll/animate it one page at a
time. There are three approaches to this available to developers, which are
detailed below. For full examples of each, see the
[`text-flow-techniques`](https://github.com/pebble-examples/text-flow-techniques)
example app.

### Using TextLayer

Additions to the ``TextLayer`` API allow text rendered within it to be
automatically flowed according to the curve of the display, and paged correctly
when the layer is moved or animated further. After a ``TextLayer`` is created in
the usual way, text flow can then be enabled:

```c
// Create TextLayer
TextLayer *s_text_layer = text_layer_create(bounds);

/* other properties set up */

// Add to parent Window
layer_add_child(window_layer, text_layer_get_layer(s_text_layer));

// Enable paging and text flow with an inset of 5 pixels
text_layer_enable_screen_text_flow_and_paging(s_text_layer, 5);
```

> Note: The ``text_layer_enable_screen_text_flow_and_paging()`` function must be
> called **after** the ``TextLayer`` is added to the view heirachy (i.e.: after
> using ``layer_add_child()``), or else it will have no effect.

An example of two ``TextLayer`` elements flowing their text within the
constraints of the display shape is shown below:

![text-flow >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/pebble-apps/display-animations/text-flow.png)

### Using ScrollLayer

The ``ScrollLayer`` UI component also contains round-friendly functionality,
allowing it to scroll its child ``Layer`` elements in pages of the same height
as its frame (usually the size of the parent ``Window``). This allows consuming
long content to be a more consistent experience, whether it is text, images, or
some other kind of information.

```c
// Enable ScrollLayer paging
scroll_layer_set_paging(s_scroll_layer, true);
```

When combined with a ``TextLayer`` as the main child layer, it becomes easy to
display long pieces of textual content on a round display. The ``TextLayer`` can
be set up to handle the reflowing of text to follow the display shape, and the
``ScrollLayer`` handles the paginated scrolling.

```c
// Add the TextLayer and ScrollLayer to the view heirachy
scroll_layer_add_child(s_scroll_layer, text_layer_get_layer(s_text_layer));
layer_add_child(window_layer, scroll_layer_get_layer(s_scroll_layer));

// Set the ScrollLayer's content size to the total size of the text
scroll_layer_set_content_size(s_scroll_layer,
                              text_layer_get_content_size(s_text_layer));

// Enable TextLayer text flow and paging
const int inset_size = 2;
text_layer_enable_screen_text_flow_and_paging(s_text_layer, inset_size);

// Enable ScrollLayer paging
scroll_layer_set_paging(s_scroll_layer, true);
```

### Manual Text Drawing

The drawing of text into a [`Graphics Context`](``Drawing Text``) can also be
performed with awareness of text flow and paging preferences. This can be used
to emulate the behavior of the two previous approaches, but with more
flexibility. This approach involves the use of the ``GTextAttributes`` object,
which is given to the Graphics API to allow it to flow text and paginate when
being animated.

When initializing the ``Window`` that will do the drawing:

```c
// Create the attributes object used for text rendering
GTextAttributes *s_attributes = graphics_text_attributes_create();

// Enable text flow with an inset of 5 pixels
graphics_text_attributes_enable_screen_text_flow(s_attributes, 5);

// Enable pagination with a fixed reference point and bounds, used for animating
graphics_text_attributes_enable_paging(s_attributes, bounds.origin, bounds);
```

When drawing some text in a ``LayerUpdateProc``:

```c
static void update_proc(Layer *layer, GContext *ctx) {
  GRect bounds = layer_get_bounds(layer);

  // Calculate size of the text to be drawn with current attribute settings
  GSize text_size = graphics_text_layout_get_content_size_with_attributes(
    s_sample_text, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), bounds,
    GTextOverflowModeWordWrap, GTextAlignmentCenter, s_attributes
  );

  // Draw the text in this box with the current attribute settings
  graphics_context_set_text_color(ctx, GColorBlack);
  graphics_draw_text(ctx, s_sample_text, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
    GRect(bounds.origin.x, bounds.origin.y, text_size.w, text_size.h),
    GTextOverflowModeWordWrap, GTextAlignmentCenter, s_attributes
  );
}
```

Once this setup is complete, the text will display correctly when moved or
scrolled via a ``PropertyAnimation``, such as one that moves the ``Layer`` that
draws the text upwards, and at the same time extending its height to display
subsequent pages. An example animation is shown below:

```c
GRect window_bounds = layer_get_bounds(window_get_root_layer(s_main_window));
const int duration_ms = 1000;

// Animate the Layer upwards, lengthening it to allow the next page to be drawn
GRect start = layer_get_frame(s_layer);
GRect finish = GRect(start.origin.x, start.origin.y - window_bounds.size.h,
                     start.size.w, start.size.h * 2);

// Create and scedule the PropertyAnimation
PropertyAnimation *prop_anim = property_animation_create_layer_frame(
                                                      s_layer, &start, &finish);
Animation *animation = property_animation_get_animation(prop_anim);
animation_set_duration(animation, duration_ms);
animation_schedule(animation);
```

## Working With a Circular Framebuffer

The traditional rectangular Pebble app framebuffer is a single continuous memory
segment that developers could access with ``gbitmap_get_data()``. With a round
display, Pebble saves memory by clipping sections of each line of difference
between the display area and the rectangle it occupies. The resulting masking
pattern looks like this:

![mask](/images/guides/pebble-apps/display-animations/mask.png)

> Download this mask by saving the PNG image above, or get it as a
> [Photoshop PSD layer](/assets/images/guides/pebble-apps/display-animations/round-mask-layer.psd).

This has an important implication - the memory segment of the framebuffer can no
longer be accessed using classic `y * row_width + x` formulae. Instead,
developers should use the ``gbitmap_get_data_row_info()`` API. When used with a
given y coordinate, this will return a ``GBitmapDataRowInfo`` object containing
a pointer to the row's data, as well as values for the minumum and maximum
visible values of x coordinate on that row. For example:

```c
static void round_update_proc(Layer *layer, GContext *ctx) {
  // Get framebuffer
  GBitmap *fb = graphics_capture_frame_buffer(ctx);
  GRect bounds = layer_get_bounds(layer);

  // Write a value to all visible pixels
  for(int y = 0; y < bounds.size.h; y++) {
    // Get the min and max x values for this row
    GBitmapDataRowInfo info = gbitmap_get_data_row_info(fb, y);

    // Iterate over visible pixels in that row
    for(int x = info.min_x; x < info.max_x; x++) {
      // Set the pixel to black
      memset(&info.data[x], GColorBlack.argb, 1);
    }
  }

  // Release framebuffer
  graphics_release_frame_buffer(ctx, fb);
}
```

## Displaying More Content

When more content is available than fits on the screen at any one time, the user
should be made aware using visual clues. The best way to do this is to use the
``ContentIndicator`` UI component.

![content-indicator >{pebble-screenshot,pebble-screenshot--time-round-silver-20}](/images/guides/design-and-interaction/content-indicator.png)

A ``ContentIndicator`` can be obtained in two ways. It can be created from
scratch with ``content_indicator_create()`` and manually managed to determine
when the arrows should be shown, or a built-in instance can be obtained from a
``ScrollLayer``, as shown below:

```c
// Get the ContentIndicator from the ScrollLayer
s_indicator = scroll_layer_get_content_indicator(s_scroll_layer);
```

In order to draw the arrows indicating more information in each direction, the
``ContentIndicator`` must be supplied with two new ``Layer`` elements that will
be used to do the drawing. These should also be added as children to the main
``Window`` root ``Layer`` such that they are visible on top of all other
``Layer`` elements:

```c
static void window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  /* ... */

  // Create two Layers to draw the arrows
  s_indicator_up_layer = layer_create(
                          GRect(0, 0, bounds.size.w, STATUS_BAR_LAYER_HEIGHT));
  s_indicator_down_layer = layer_create(
                          GRect(0, bounds.size.h - STATUS_BAR_LAYER_HEIGHT,
                                bounds.size.w, STATUS_BAR_LAYER_HEIGHT));

  /* ... */

  // Add these Layers as children after all other components to appear below
  layer_add_child(window_layer, s_indicator_up_layer);
  layer_add_child(window_layer, s_indicator_down_layer);
}
```

Once the indicator ``Layer`` elements have been created, each of the up and down
directions for conventional vertical scrolling must be configured with data to
control its behavior. Aspects such as the color of the arrows and background,
whether or not the arrows time out after being brought into view, and the
alignment of the drawn arrow within the ``Layer`` itself are configured with a
`const` ``ContentIndicatorConfig`` object when each direction is being
configured:

```c
// Configure the properties of each indicator
const ContentIndicatorConfig up_config = (ContentIndicatorConfig) {
  .layer = s_indicator_up_layer,
  .times_out = false,
  .alignment = GAlignCenter,
  .colors = {
    .foreground = GColorBlack,
    .background = GColorWhite
  }
};
content_indicator_configure_direction(s_indicator, ContentIndicatorDirectionUp,
                                      &up_config);

const ContentIndicatorConfig down_config = (ContentIndicatorConfig) {
  .layer = s_indicator_down_layer,
  .times_out = false,
  .alignment = GAlignCenter,
  .colors = {
    .foreground = GColorBlack,
    .background = GColorWhite
  }
};
content_indicator_configure_direction(s_indicator, ContentIndicatorDirectionDown,
                                      &down_config);
```

Unless the ``ContentIndicator`` has been retrieved from another ``Layer`` type
that includes an instance, it should be destroyed along with its parent
``Window``:

```c
// Destroy a manually created ContentIndicator
content_indicator_destroy(s_indicator);
```

For layouts that use the ``StatusBarLayer``, the ``ContentIndicatorDirectionUp``
`.layer` in the ``ContentIndicatorConfig`` object can be given the status bar's
``Layer`` with ``status_bar_layer_get_layer()``, and the drawing routines for
each will be managed automatically.


---
# 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: Unobstructed Area
description: |
  Details on how to use the UnobstructedArea API to adapt your watchface layout
  when the screen is partially obstructed by a system overlay.
guide_group: user-interfaces
order: 5
related_docs:
  - Graphics
  - LayerUpdateProc
  - UnobstructedArea
related_examples:
  - title: Simple Example
    url: https://github.com/pebble-examples/unobstructed-area-example
  - title: Watchface Tutorial
    url: https://github.com/pebble-examples/watchface-tutorial-unobstructed
---

The ``UnobstructedArea`` API, added in SDK 4.0, allows developers to dynamically
adapt their watchface design when an area of the screen is partially obstructed
by a system overlay. Currently, the Timeline Quick View feature is the only
system overlay.

Developers are not required to adjust their designs to cater for such system
overlays, but by using the ``UnobstructedArea`` API they can detect changes to
the available screen real-estate and then move, scale, or hide their layers to
achieve an optimal layout while the screen is partially obscured.

![Unobstructed-watchfaces](/images/guides/user-interfaces/unobstructed-area/01-unobstructed-watchfaces.jpg)

Sample watchfaces with Timeline Quick View overlay

![Obstructed-watchfaces](/images/guides/user-interfaces/unobstructed-area/02-obstructed-watchfaces.jpg)

Potential versions of sample watchfaces using the
UnobstructedArea API

### Determining the Unobstructed Bounds

Prior to SDK 4.0, when displaying layers on screen you would calculate the
size of the display using ``layer_get_bounds()`` and then scale and position
your layers accordingly. Developers can now calculate the size of a layer,
excluding system obstructions, using the new
``layer_get_unobstructed_bounds()``.

```c
static Layer *s_window_layer;
static TextLayer *s_text_layer;

static void main_window_load(Window *window) {
  s_window_layer = window_get_root_layer(window);
  GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer);
  s_text_layer = text_layer_create(GRect(0, unobstructed_bounds.size.h / 4, unobstructed_bounds.size.w, 50));
}
```

If you still want a fullscreen entities such as a background image, regardless
of any obstructions, just combine both techniques as follows:

```c
static Layer *s_window_layer;
static BitmapLayer *s_image_layer;
static TextLayer *s_text_layer;

static void main_window_load(Window *window) {
  s_window_layer = window_get_root_layer(window);
  GRect full_bounds = layer_get_bounds(s_window_layer);
  GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer);
  s_image_layer = bitmap_layer_create(full_bounds);
  s_text_layer = text_layer_create(GRect(0, unobstructed_bounds.size.h / 4, unobstructed_bounds.size.w, 50));
}
```

The approach outlined above is perfectly fine to use when your watchface is
initially launched, but you’re also responsible for handling the obstruction
appearing and disappearing while your watchface is running.

### Rendering with LayerUpdateProc

If your application controls its own rendering process using a
``LayerUpdateProc`` you can just dynamically adjust your rendering
each time your layer updates.

In this example, we use ``layer_get_unobstructed_bounds()`` instead of
``layer_get_bounds()``. The graphics are then positioned or scaled based upon
the available screen real-estate, instead of the screen dimensions.

> You must ensure you fill the entire window, not just the unobstructed
> area, when drawing the screen - failing to do so may cause unexpected
> graphics to be drawn behind the quick view, during animations.

```c
static void hands_update_proc(Layer *layer, GContext *ctx) {
  GRect bounds = layer_get_unobstructed_bounds(layer);
  GPoint center = grect_center_point(&bounds);
  const int16_t second_hand_length = (bounds.size.w / 2);
  time_t now = time(NULL);
  struct tm *t = localtime(&now);
  int32_t second_angle = TRIG_MAX_ANGLE * t->tm_sec / 60;
  GPoint second_hand = {
    .x = (int16_t)(sin_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.x,
    .y = (int16_t)(-cos_lookup(second_angle) * (int32_t)second_hand_length / TRIG_MAX_RATIO) + center.y,
  };

  // second hand
  graphics_context_set_stroke_color(ctx, GColorWhite);
  graphics_draw_line(ctx, second_hand, center);

  // minute/hour hand
  graphics_context_set_fill_color(ctx, GColorWhite);
  graphics_context_set_stroke_color(ctx, GColorBlack);
  gpath_rotate_to(s_minute_arrow, TRIG_MAX_ANGLE * t->tm_min / 60);
  gpath_draw_filled(ctx, s_minute_arrow);
  gpath_draw_outline(ctx, s_minute_arrow);

  gpath_rotate_to(s_hour_arrow, (TRIG_MAX_ANGLE * (((t->tm_hour % 12) * 6) +
                  (t->tm_min / 10))) / (12 * 6));
  gpath_draw_filled(ctx, s_hour_arrow);
  gpath_draw_outline(ctx, s_hour_arrow);

  // dot in the middle
  graphics_context_set_fill_color(ctx, GColorBlack);
  graphics_fill_rect(ctx, GRect(bounds.size.w / 2 - 1, bounds.size.h / 2 - 1, 3,
  3), 0, GCornerNone);
}
```

### Using Unobstructed Area Handlers

If you are not overriding the default rendering of a ``Layer``, you will need to
subscribe to one or more of the ``UnobstructedAreaHandlers`` to adjust the sizes
and positions of layers.

There are 3 events available using ``UnobstructedAreaHandlers``.
These events will notify you when the unobstructed area is: *about to change*,
*is currently changing*, or *has finished changing*. You can use these handlers
to perform any necessary alterations to your layout.

`.will_change` - an event to inform you that the unobstructed area size is about
to change. This provides a ``GRect`` which lets you know the size of the screen
after the change has finished.

`.change` - an event to inform you that the unobstructed area size is currently
changing. This event is called several times during the animation of an
obstruction appearing or disappearing. ``AnimationProgress`` is provided to let
you know the percentage of progress towards completion.

`.did_change` - an event to inform you that the unobstructed area size has
finished changing. This is useful for deinitializing or destroying anything
created or allocated in the will_change handler.

These handlers are optional, but at least one must be specified for a valid
subscription. In the following example, we subscribe to two of the three
available handlers.

> **NOTE**: You must construct the
> ``UnobstructedAreaHandlers`` object *before* passing it to the
> ``unobstructed_area_service_subscribe()`` method.

```c
UnobstructedAreaHandlers handlers = {
  .will_change = prv_unobstructed_will_change,
  .did_change = prv_unobstructed_did_change
};
unobstructed_area_service_subscribe(handlers, NULL);
```

#### Hiding Layers

In this example, we’re going to hide a ``TextLayer`` containing the current
date, while the screen is obstructed.

Just before the Timeline Quick View appears, we’re going to hide the
``TextLayer`` and we’ll show it again after the Timeline Quick View disappears.

```c
static Window *s_main_window;
static Layer *s_window_layer;
static TextLayer *s_date_layer;
```

Subscribe to the `.did_change` and `.will_change` events:

```c
static void main_window_load(Window *window) {
  // Keep a handle on the root layer
  s_window_layer = window_get_root_layer(window);
  // Subscribe to the will_change and did_change events
  UnobstructedAreaHandlers handlers = {
    .will_change = prv_unobstructed_will_change,
    .did_change = prv_unobstructed_did_change
  };
  unobstructed_area_service_subscribe(handlers, NULL);
}
```

The `will_change` event fires before the size of the unobstructed area changes,
so we need to establish whether the screen is already obstructed, or about to
become obstructed. If there isn’t a current obstruction, that means the
obstruction must be about to appear, so we’ll need to hide our data layer.

```c
static void prv_unobstructed_will_change(GRect final_unobstructed_screen_area,
void *context) {
  // Get the full size of the screen
  GRect full_bounds = layer_get_bounds(s_window_layer);
  if (!grect_equal(&full_bounds, &final_unobstructed_screen_area)) {
    // Screen is about to become obstructed, hide the date
    layer_set_hidden(text_layer_get_layer(s_date_layer), true);
  }
}
```

The `did_change` event fires after the unobstructed size changes, so we can
perform the same check to see whether the screen is already obstructed, or
about to become obstructed. If the screen isn’t obstructed when this event
fires, then the obstruction must have just cleared and we’ll need to display
our date layer again.

```c
static void prv_unobstructed_did_change(void *context) {
  // Get the full size of the screen
  GRect full_bounds = layer_get_bounds(s_window_layer);
  // Get the total available screen real-estate
  GRect bounds = layer_get_unobstructed_bounds(s_window_layer);
  if (grect_equal(&full_bounds, &bounds)) {
    // Screen is no longer obstructed, show the date
    layer_set_hidden(text_layer_get_layer(s_date_layer), false);
  }
}
```

#### Animating Layer Positions

The `.change` event will fire several times while the unobstructed area is
changing size. This allows us to use this event to make our layers appear to
slide-in or slide-out of their initial positions.

In this example, we’re going to use percentages to position two text layers
vertically. One layer at the top of the screen and one layer at the bottom. When
the screen is obstructed, these two layers will shift to be closer together.
Because we’re using percentages, it doesn’t matter if the unobstructed area is
increasing or decreasing, our text layers will always be relatively positioned
in the available space.

```c
static const uint8_t s_offset_top_percent = 33;
static const uint8_t s_offset_bottom_percent = 10;
```

A simple helper function to simulate percentage based coordinates:

```c
uint8_t relative_pixel(int16_t percent, int16_t max) {
  return (max * percent) / 100;
}
```

Subscribe to the change event:

```c
static void main_window_load(Window *window) {
  UnobstructedAreaHandlers handler = {
    .change = prv_unobstructed_change
  };
  unobstructed_area_service_subscribe(handler, NULL);
}
```

Move the text layer each time the unobstructed area size changes:

```c
static void prv_unobstructed_change(AnimationProgress progress, void *context) {
  // Get the total available screen real-estate
  GRect bounds = layer_get_unobstructed_bounds(s_window_layer);
  // Get the current position of our top text layer
  GRect frame = layer_get_frame(text_layer_get_layer(s_top_text_layer));
  // Shift the Y coordinate
  frame.origin.y = relative_pixel(s_offset_top_percent, bounds.size.h);
  // Apply the new location
  layer_set_frame(text_layer_get_layer(s_top_text_layer), frame);
  // Get the current position of our bottom text layer
  GRect frame2 = layer_get_frame(text_layer_get_layer(s_top_text_layer));
  // Shift the Y coordinate
  frame2.origin.y = relative_pixel(s_offset_bottom_percent, bounds.size.h);
  // Apply the new position
  layer_set_frame(text_layer_get_layer(s_bottom_text_layer), frame2);
}
```

### Toggling Timeline Quick View

The `pebble` tool which shipped as part of [SDK 4.0](/sdk4),
allows developers to enable and disable Timeline Quick View, which is
incredibly useful for debugging purposes.

![Unobstructed animation >{pebble-screenshot,pebble-screenshot--time-black}](/images/guides/user-interfaces/unobstructed-area/unobstructed-animation.gif)

To enable Timeline Quick View, you can use:

```nc|text
$ pebble emu-set-timeline-quick-view on
```

To disable Timeline Quick View, you can use:

```nc|text
$ pebble emu-set-timeline-quick-view off
```

> [CloudPebble]({{site.links.cloudpebble}}) does not currently support toggling
> Timeline Quick View, but it will be added as part of a future update.

### Additional Considerations

If you're scaling or moving layers based on the unobstructed area, you must
ensure you fill the entire window, not just the unobstructed area. Failing to do
so may cause unexpected graphics to be drawn behind the quick view, during
animations.

At present, Timeline Quick View is not currently planned for the Chalk platform.

For design reference, the height of the Timeline Quick View overlay will be
*51px* in total, which includes a 2px border, but this may vary on newer
platforms and and the height should always be calculated at runtime.

```c
// Calculate the actual height of the Timeline Quick View
s_window_layer = window_get_root_layer(window);
GRect fullscreen = layer_get_bounds(s_window_layer);
GRect unobstructed_bounds = layer_get_unobstructed_bounds(s_window_layer);

int16_t obstruction_height = fullscreen.size.h - unobstructed_bounds.size.h;
```


{% comment %}
 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.
{% endcomment %}

{% for sub_group in include.group.subgroups %}
{% assign sub_grp = sub_group[1] %}
* [**{{ sub_grp.title }}**]({{sub_grp.url}}) - {{ sub_grp.description }}
{% endfor %}
{% if include.group.guides.size > 0 %}
{% assign guides = include.group.guides | sort: 'title' | where:'menu',true %}
{% for guide in guides %}
* [**{{ guide.title }}**]({{guide.url}}) - {{ guide.summary }}
{% endfor %}
{% endif %}


{% comment %}
 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.
{% endcomment %}

### Windows

Installing the Pebble SDK directly on Windows is not supported at this time. We
recommend you use [CloudPebble]({{ site.links.cloudpebble }}) instead.

Alternatively, you can run a Linux virtual machine:

1. Install a virtual machine manager such as
   [VirtualBox](http://www.virtualbox.org/) (free) or
   [VMWare Workstation](http://www.vmware.com/products/workstation/).
2. Install [Ubuntu Linux](http://www.ubuntu.com/) in a new virtual machine.
3. Follow the [manual installation instructions](/sdk/install/linux/), but skip
   "Download and install the Pebble ARM toolchain", as the toolchain is
   included.

### Mac OS X

If you previously used Homebrew to install the Pebble SDK, run:

```bash
$ brew update && brew upgrade --devel pebble-sdk
```

If you've never used Homebrew to install the Pebble SDK, run:

```bash
$ brew update && brew install --devel pebble/pebble-sdk/pebble-sdk
```

If you would prefer to not use Homebrew and would like to manually install the
Pebble SDK:

1. Download the
   [SDK package](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2).

2. Follow the [manual installation instructions](/sdk/install/), but skip
   "Download and install the Pebble ARM toolchain", as the toolchain is
   included.

### Linux

1. Download the relevant package:
   [Linux (32-bit)](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) |
   [Linux (64-bit)](https://s3.amazonaws.com/assets.getpebble.com/pebble-tool/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2)

2. Install the SDK by following the
   [manual installation instructions](/sdk/install/linux/), but skip
   "Download and install the Pebble ARM toolchain", as the toolchain is
   included.


{% comment %}
 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.
{% endcomment %}

## Next Steps

Now that you have the Pebble SDK downloaded and installed on your computer,
it is time to learn how to write your first app!

You should checkout the [Tutorials](/tutorials/) for a step-by-step look at how
to write a simple C Pebble application.


{% comment %}
 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.
{% endcomment %}

### Installation Problems?

If you have any issues with downloading or installing the Pebble SDK, you
should take a look at the
[SDK Help category](https://forums.getpebble.com/categories/watchface-sdk-help)
on our forums.

Alternatively, you can [send us a message](/contact/) letting us know what
issues you're having and we will try and help you out.


{% comment %}
 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.
{% endcomment %}

{% if include.mac %}
1. If you have not already, download the [latest version of the SDK]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-mac.tar.bz2).
{% else %}
1. If you have not already, download the latest version of the SDK -
   [Linux 32-bit]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux32.tar.bz2) |
   [Linux 64-bit]({{ site.links.pebble_tool_root }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-linux64.tar.bz2).
{% endif %}

2. Open {% if include.mac %}Terminal{% else %}a terminal{% endif %} window and
  create a directory to host all Pebble tools:

    ```bash
    mkdir {{ site.data.sdk.path }}
    ```

3. Change into that directory and extract the Pebble SDK that you just
  downloaded, for example:

    ```bash
    cd {{ site.data.sdk.path }}
    tar -jxf ~/Downloads/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}.tar.bz2
    ```

    {% unless include.mac %}
    > Note: If you are using 32-bit Linux, the path shown above will be
    > different as appropriate.
    {% endunless %}

    You should now have the directory
    `{{ site.data.sdk.path }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}` with the SDK files and directories inside it.

4. Add the `pebble` tool to your path and reload your shell configuration:

    ```bash
    echo 'export PATH=~/pebble-dev/pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}/bin:$PATH' >> ~/.bash_profile
    . ~/.bash_profile
    ```

You can now continue on and install the rest of the dependencies.


{% comment %}
 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.
{% endcomment %}

### Download and install Python libraries

The Pebble SDK depends on Python libraries to convert fonts and images from your
computer into Pebble resources.

{% if include.mac %}
You need to use the standard Python `easy_install` package manager to install
the alternative `pip` package manager. This is then used to install other Python
dependencies.

Follow these steps in Terminal:
{% endif %}

1. Install `pip` and `virtualenv`:

    ```bash
    {% if include.mac %}sudo easy_install pip{% else %}sudo apt-get install python-pip python2.7-dev{% endif %}
    sudo pip install virtualenv
    ```

2. Install the Python library dependencies locally:

    ```bash
    cd {{ site.data.sdk.path }}pebble-sdk-{{ site.data.sdk.pebble_tool.version }}-{% if include.mac %}mac{% else %}linux64{% endif %}
    virtualenv --no-site-packages .env
    source .env/bin/activate
    {% if include.mac %}CFLAGS="" {% endif %}pip install -r requirements.txt
    deactivate
    ```

> **Note: virtualenv is not optional.**


---
layout: guides/default
title: Dummy
description: Details
---


---
# 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.

permalink: /feed.xml
layout: docs/markdown
title: PebbleKit Android Documentation
docs_language: pebblekit_android
---

This is the contents page for the PebbleKit Android SDK documentation, which
includes all information on the two main packages below:

{% for module in site.data.docs_tree.pebblekit_android %}

{{ module.name }}

{% endfor %}

This part of the SDK can be used to build companion apps for Android to enhance
the features of the watchapp or to integrate Pebble into an existing mobile app
experience.

Get started using PebbleKit Android by working through the
[*Android Tutorial*](/tutorials/android-tutorial/part1). Check out
{% guide_link communication/using-pebblekit-android %} to learn more about using
this SDK.

You can also find the source code for PebbleKit Android
[on GitHub](https://github.com/pebble/pebble-android-sdk).


---
# 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.

permalink: /feed.xml
layout: docs/markdown
title: PebbleKit iOS Documentation
docs_language: pebblekit_ios
---

This is the contents page for the PebbleKit iOS SDK documentation, which
includes all information on the main reference sections below.

This part of the SDK can be used to build companion apps for iOS to enhance the
features of the watchapp or to integrate Pebble into an existing mobile app
experience.

Get started using PebbleKit iOS by working through the
[*iOS Tutorial*](/tutorials/ios-tutorial/part1). Check out
{% guide_link communication/using-pebblekit-ios %} to learn more about using
this SDK.

You can find the source code for PebbleKit iOS
[on GitHub](https://github.com/pebble/pebble-ios-sdk), and the documentation
is also available on
[CocoaDocs](http://cocoadocs.org/docsets/PebbleKit/{{ site.data.sdk.pebblekit-ios.version }}/).

{% for module in site.data.docs_tree.pebblekit_ios %}

{{ module.name }}

{% for child in module.children %}

{{ child.name }}

{% endfor %}

{% endfor %}


---
# 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: sdk/markdown
menu_subsection: install
title: Installing the Pebble SDK
---

## Develop Online

You can use [CloudPebble]({{ site.links.cloudpebble }}) to build, compile
and test Pebble apps entirely in the cloud without any installation needed.

## Install Through Homebrew

We recommend Mac OS X users [install the SDK using Homebrew](/sdk/download).

## Manual Installation

Once you have [downloaded the Pebble SDK](/sdk/download/), you will need to
follow the instructions for your platform.

## [Mac OS X](/sdk/install/mac/) | [Linux](/sdk/install/linux/) | [Windows](/sdk/install/windows/)

### Problems Installing?

If you need help installing the SDK, feel free to post your comments in the
[SDK Installation Help forum][sdk-install-help]. Please make sure you
provide as many details as you can about the issues
you may have encountered.

**Tip:** Copying and pasting commands from your Terminal output will help a great deal.

### What's Next?

Once you have installed the Pebble SDK, you should check out our
[Tutorials](/tutorials/) section to learn the basics of Pebble development.

[sdk-install-help]: https://forums.getpebble.com/categories/sdk-install/


---
# 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: sdk/markdown
title: Installing the Pebble SDK on Linux
description: Detailed installation instructions for the Pebble SDK on Linux.
menu_subsection: install
menu_platform: linux
generate_toc: true
permalink: /sdk/install/linux/
---

> **Important**: The Pebble SDK is officially supported on
> Ubuntu GNU/Linux 12.04 LTS, Ubuntu 13.04, Ubuntu 13.10 and Ubuntu 14.04 LTS.
>
> The SDK should also work on other distributions with minor adjustments.
>
> **Python version**: the Pebble SDK requires Python 2.7. At this time, the
> Pebble SDK is not compatible with Python 3. However, some newer
> distributions come with both Python 2.7 and Python 3 installed, which can
> cause problems. You can use </br>`python --version` to determine which is being
> used. This means you may need to run `pip2` instead of `pip` when prompted to
> do so below.

## Download and install the Pebble SDK

{% include sdk/steps_install_sdk.md mac=false %}

{% include sdk/steps_python.md mac=false %}

## Install Pebble emulator dependencies

The Pebble emulator requires some libraries that you may not have installed on
your system.

```bash
sudo apt-get install libsdl1.2debian libfdt1 libpixman-1-0
```

{% include sdk/steps_getting_started.md %}

{% include sdk/steps_help.md %}


---
# 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: sdk/markdown
title: Installing the Pebble SDK on Mac OS X
description: Detailed installation instructions for the Pebble SDK on Mac OS X.
menu_subsection: install
menu_platform: mac
generate_toc: true
permalink: /sdk/install/mac/
---

These are the manual installation instructions for installing the Pebble SDK
from a download bundle. We recommend you
[install the SDK using Homebrew](/sdk/download) instead, if possible.

### Compatibility

> **Python version**: the Pebble SDK requires Python 2.7. At this time, the
> Pebble SDK is not compatible with Python 3. However, some newer
> distributions come with both Python 2.7 and Python 3 installed, which can
> cause problems. You can use </br>`python --version` to determine which is being
> used. This means you may need to run `pip2` instead of `pip` when prompted to
> do so below.

### Download and install the Pebble SDK

1. Install the [Xcode Command Line Tools][xcode-command-line-tools] from
   Apple if you do not have them already.

{% include sdk/steps_install_sdk.md mac=true %}

{% include sdk/steps_python.md mac=true %}

### Pebble SDK, fonts and freetype

To manipulate and generate fonts, the Pebble SDK requires the freetype library.
If you intend to use custom fonts in your apps, please use
[homebrew][homebrew-install] to install the freetype library.

```bash
brew install freetype
```

### Install Pebble emulator dependencies

The Pebble emulator requires some libraries that you may not have installed on
your system.

The easiest way to install these dependencies is to use [homebrew][homebrew-install].

```bash
brew update
brew install boost-python
brew install glib
brew install pixman
```

> If you have installed Python using Homebrew, you **must** install boost-python
> from source. You can do that with `brew install boost-python --build-from-source` .

{% include sdk/steps_getting_started.md %}

{% include sdk/steps_help.md %}

[xcode-command-line-tools]: https://developer.apple.com/downloads/
[homebrew-install]: http://brew.sh/


---
# 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: sdk/markdown
title: Installing the Pebble SDK on Windows
description: Detailed installation instructions for the Pebble SDK on Windows.
menu_subsection: install
menu_platform: windows
generate_toc: true
permalink: /sdk/install/windows/
---

Installing the Pebble SDK on Windows is not officially supported at this time.

However, you can choose from several alternative strategies to develop
watchfaces and watchapps on Windows.

## Use CloudPebble

[CloudPebble][cloudpebble] is the official online development environment for
writing Pebble apps.

It allows you to create, edit, build and distribute applications in your web
browser without installing anything on your computer.

**Pebble strongly recommends [CloudPebble][cloudpebble] for Windows users.**

## Use a Virtual Machine

You can also download and run the Pebble SDK in a virtual machine.

 1. Install a virtual machine manager such as
   [VirtualBox](http://www.virtualbox.org) (free) or
   [VMWare Workstation](http://www.vmware.com/products/workstation/).
 2. Install [Ubuntu Linux](http://www.ubuntu.com/) in a virtual machine.
 3. Follow the standard [Linux installation instructions](/sdk/install/linux/).

## Need installation help?

If you need help installing the SDK, feel free to post in the
[SDK Installation Help forum][sdk-install-help].

Please make sure you provide as many details as you can about the issue you have
encountered (copy/pasting your terminal output will help a lot).

[cloudpebble]: {{ site.links.cloudpebble }}
[sdk-install-help]: https://forums.getpebble.com/categories/sdk-install/


---
# 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: utils/redirect_permanent
redirect_to: /tutorials/advanced/vector-animations/
---


---
# 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: advanced
tutorial_part: 1

title: Vector Animations
description: |
  How to use vector images in icons and animations.
permalink: /tutorials/advanced/vector-animations/
generate_toc: true
platform_choice: true
platforms:
  - basalt
  - chalk
  - diorite
  - emery
---

Some of the best Pebble apps make good use of the ``Animation`` and the
[`Graphics Context`](``Graphics``) to create beautiful and eye-catching user
interfaces that look better than those created with just the standard ``Layer``
types.

Taking a good design a step further may involve using the ``Draw Commands`` API
to load vector icons and images, and to animate them on a point-by-point basis
at runtime. An additional capability of the ``Draw Commands`` API is the draw
command sequence, allowing multiple frames to be incorporated into a single
resource and played out frame by frame.

This tutorial will guide you through the process of using these types of image
files in your own projects.

## What Are Vector Images?

As opposed to bitmaps which contain data for every pixel to be drawn, a vector
file contains only instructions about points contained in the image and how to
draw lines connecting them up. Instructions such as fill color, stroke color,
and stroke width are also included.

Vector images on Pebble are implemented using the ``Draw Commands`` APIs, which
load and display PDC (Pebble Draw Command) images and sequences that contain
sets of these instructions. An example is the weather icon used in weather
timeline pins. The benefit of using vector graphics for this icon is that is
allows the image to stretch in the familiar manner as it moves between the
timeline view and the pin detail view:

![weather >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather.png)

By including two or more vector images in a single file, an animation can be
created to enable fast and detailed animated sequences to be played. Examples
can be seen in the Pebble system UI, such as when an action is completed:

![action-completed >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/action-completed.gif)

The main benefits of vectors over bitmaps for simple images and icons are:

* Smaller resource size - instructions for joining points are less memory
  expensive than per-pixel bitmap data.

* Flexible rendering - vector images can be rendered as intended, or manipulated
  at runtime to move the individual points around. This allows icons to appear
  more organic and life-like than static PNG images. Scaling and distortion is
  also made possible.

* Longer animations - a side benefit of taking up less space is the ability to
  make animations longer.

However, there are also some drawbacks to choosing vector images in certain
cases:

* Vector files require more specialized tools to create than bitmaps, and so are
  harder to produce.

* Complicated vector files may take more time to render than if they were simply
  drawn per-pixel as a bitmap, depending on the drawing implementation.

## Creating Compatible Files

The file format of vector image files on Pebble is the PDC (Pebble Draw Command)
format, which includes all the instructions necessary to allow drawing of
vectors. These files are created from compatible SVG (Scalar Vector Graphics)
files using the
[`svg2pdc`]({{site.links.examples_org}}/cards-example/blob/master/tools/svg2pdc.py)
tool.

Pebble Draw Command files can only be used from app resources, and cannot be
created at runtime.

To convert an SVG file to a PDC image of the same name:

```bash
$ python svg2pdc.py image.svg
```

To create a PDCS (Pebble Draw Command Sequence) from individual SVG frames,
specify the directory containing the frames with the `--sequence` flag when
running `svg2pdc`:

```bash
$ ls frames/
1.svg          2.svg          3.svg
4.svg          5.svg

$ python svg2pdc.py --sequence frames/
```

In the example above, this will create an output file in the `frames` directory
called `frames.pdc` that contains draw command data for the complete animation.

{% markdown %}
**Limitations**

The `svg2pdc` tool currently supports SVG files that use **only** the following
elements: `g`, `layer`, `path`, `rect`, `polyline`, `polygon`, `line`, `circle`.

We recommend using Adobe Illustrator to create compatible SVG icons and images.
{% endmarkdown %}

For simplicity, compatible image and sequence files will be provided for you to
use in your own project.

### PDC icons

Example PDC image files are available for the icons listed in
[*App Assets*](/guides/app-resources/app-assets/).
These are ideal for use in many common types of apps, such as notification or
weather apps.

[Download PDC icon files >{center,bg-lightblue,fg-white}]({{ site.links.s3_assets }}/assets/other/pebble-timeline-icons-pdc.zip)

## Getting Started

^CP^ Begin a new [CloudPebble]({{ site.links.cloudpebble }}) project using the
blank template and add code only to push an initial ``Window``, such as the
example below:

^LC^ Begin a new project using `pebble new-project` and create a simple app that
pushes a blank ``Window``, such as the example below:

```c
#include <pebble.h>

static Window *s_main_window;

static void main_window_load(Window *window) {
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

}

static void main_window_unload(Window *window) {

}

static void init() {
  s_main_window = window_create();
  window_set_window_handlers(s_main_window, (WindowHandlers) {
    .load = main_window_load,
    .unload = main_window_unload,
  });
  window_stack_push(s_main_window, true);
}

static void deinit() {
  window_destroy(s_main_window);
}

int main() {
  init();
  app_event_loop();
  deinit();
}
```

## Drawing a PDC Image

For this tutorial, use the example
[`weather_image.pdc`](/assets/other/weather_image.pdc) file provided.

^CP^ Add the PDC file as a project resource using the 'Add new' under
'Resources' on the left-hand side of the CloudPebble editor, with an
'Identifier' of `WEATHER_IMAGE`, and a type of 'raw binary blob'. The file is
assumed to be called `weather_image.pdc`.

^LC^ Add the PDC file to your project resources in `package.json` as shown
below. Set the 'name' field to `WEATHER_IMAGE`, and the 'type' field to `raw`.
The file is assumed to be called `weather_image.pdc`:

{% highlight {} %}
"media": [
  {
    "type": "raw",
    "name": "WEATHER_IMAGE",
    "file": "weather_image.pdc"
  }
]
{% endhighlight %}

^LC^ Drawing a Pebble Draw Command image is just as simple as drawing a normal PNG
image to a graphics context, requiring only one draw call. First, load the
`.pdc` file from resources, for example with the `name` defined as
`WEATHER_IMAGE`, as shown below.

^CP^ Drawing a Pebble Draw Command image is just as simple as drawing a normal
PNG image to a graphics context, requiring only one draw call. First, load the
`.pdc` file from resources, for example with the 'Identifier' defined as
`WEATHER_IMAGE`. This will be available in code as `RESOURCE_ID_WEATHER_IMAGE`,
as shown below.

Declare a pointer of type ``GDrawCommandImage`` at the top of the file:

```c
static GDrawCommandImage *s_command_image;
```

Create and assign the ``GDrawCommandImage`` in `init()`, before calling
`window_stack_push()`:

```nc|c
static void init() {
  /* ... */

  // Create the object from resource file
  s_command_image = gdraw_command_image_create_with_resource(RESOURCE_ID_WEATHER_IMAGE);

  /* ... */
}
```

Next, define the ``LayerUpdateProc`` that will be used to draw the PDC image:

```c
static void update_proc(Layer *layer, GContext *ctx) {
  // Set the origin offset from the context for drawing the image
  GPoint origin = GPoint(10, 20);

  // Draw the GDrawCommandImage to the GContext
  gdraw_command_image_draw(ctx, s_command_image, origin);
}
```

Next, create a ``Layer`` to display the image:

```c
static Layer *s_canvas_layer;
```

Next, set the ``LayerUpdateProc`` that will do the rendering and add it to the
desired ``Window``:

```c
static void main_window_load(Window *window) {

  /* ... */

  // Create the canvas Layer
  s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));

  // Set the LayerUpdateProc
  layer_set_update_proc(s_canvas_layer, update_proc);

  // Add to parent Window
  layer_add_child(window_layer, s_canvas_layer);
}
```

Finally, don't forget to free the memory used by the ``Window``'s sub-components
in `main_window_unload()`:

```c
static void main_window_unload(Window *window) {
  layer_destroy(s_canvas_layer);
  gdraw_command_image_destroy(s_command_image);
}
```

When run, the PDC image will be loaded, and rendered in the ``LayerUpdateProc``.
To put the image into contrast, we will finally change the ``Window`` background
color after `window_create()`:

```c
window_set_background_color(s_main_window, GColorBlueMoon);
```

The result will look similar to the example shown below.

![weather-image >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/weather-image.png)

## Playing a PDC Sequence

The ``GDrawCommandSequence`` API allows developers to use vector graphics as
individual frames in a larger animation. Just like ``GDrawCommandImage``s, each
``GDrawCommandFrame`` is drawn to a graphics context in a ``LayerUpdateProc``.

For this tutorial, use the example
[`clock_sequence.pdc`](/assets/other/clock_sequence.pdc) file provided.

Begin a new app, with a C file containing the [template](#getting-started) provided above.

^CP^ Next, add the file as a `raw` resource in the same way as for a PDC image,
for example with an `Identifier` specified as `CLOCK_SEQUENCE`.

^LC^ Next, add the file as a `raw` resource in the same way as for a PDC image,
for example with the `name` field specified in `package.json` as
`CLOCK_SEQUENCE`.

{% highlight {} %}
"media": [
  {
    "type": "raw",
    "name": "CLOCK_SEQUENCE",
    "file": "clock_sequence.pdc"
  }
]
{% endhighlight %}

Load the PDCS in your app by first declaring a ``GDrawCommandSequence`` pointer:

```c
static GDrawCommandSequence *s_command_seq;
```

Next, initialize the object in `init()` before calling `window_stack_push()`:

```nc|c
static void init() {
  /* ... */

  // Load the sequence
  s_command_seq = gdraw_command_sequence_create_with_resource(RESOURCE_ID_CLOCK_SEQUENCE);

  /* ... */
}
```

Get the next frame and draw it in the ``LayerUpdateProc``. Then register a timer
to draw the next frame:

```c
// Milliseconds between frames
#define DELTA 13

static int s_index = 0;

/* ... */

static void next_frame_handler(void *context) {
  // Draw the next frame
  layer_mark_dirty(s_canvas_layer);

  // Continue the sequence
  app_timer_register(DELTA, next_frame_handler, NULL);
}

static void update_proc(Layer *layer, GContext *ctx) {
  // Get the next frame
  GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(s_command_seq, s_index);

  // If another frame was found, draw it
  if (frame) {
    gdraw_command_frame_draw(ctx, s_command_seq, frame, GPoint(0, 30));
  }

  // Advance to the next frame, wrapping if neccessary
  int num_frames = gdraw_command_sequence_get_num_frames(s_command_seq);
  s_index++;
  if (s_index == num_frames) {
    s_index = 0;
  }
}
```

Next, create a new ``Layer`` to utilize the ``LayerUpdateProc`` and add it to the
desired ``Window``.

Create the `Window` pointer:

```c
static Layer *s_canvas_layer;
```

Next, create the ``Layer`` and assign it to the new pointer. Set its update
procedure and add it to the ``Window``:

```c
static void main_window_load(Window *window) {
  // Get Window information
  Layer *window_layer = window_get_root_layer(window);
  GRect bounds = layer_get_bounds(window_layer);

  // Create the canvas Layer
  s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));

  // Set the LayerUpdateProc
  layer_set_update_proc(s_canvas_layer, update_proc);

  // Add to parent Window
  layer_add_child(window_layer, s_canvas_layer);
}
```

Start the animation loop using a timer at the end of initialization:

```c
// Start the animation
app_timer_register(DELTA, next_frame_handler, NULL);
```

Finally, remember to destroy the ``GDrawCommandSequence`` and ``Layer`` in
`main_window_unload()`:

```c
static void main_window_unload(Window *window) {
  layer_destroy(s_canvas_layer);
  gdraw_command_sequence_destroy(s_command_seq);
}
```

When run, the animation will be played by the timer at a framerate dictated by
`DELTA`, looking similar to the example shown below:

![pdcs-example >{pebble-screenshot,pebble-screenshot--time-red}](/images/tutorials/advanced/pdcs-example.gif)

## What's Next?

You have now learned how to add vector images and animations to your apps.
Complete examples for these APIs are available under the `pebble-examples`
GitHub organization:

* [`pdc-image`]({{site.links.examples_org}}/pdc-image) - Example
  implementation of a Pebble Draw Command Image.

* [`pdc-sequence`]({{site.links.examples_org}}/pdc-sequence) - Example
  implementation of a Pebble Draw Command Sequence animated icon.

More advanced tutorials will be added here in the future, so keep checking back!