Curious about how to design apps that look great on Pebble Classic, Pebble Time, and Pebble Time Round? Confused about how to structure and implement code for multi-platform apps using the Pebble SDK? Kevin Conley (Embedded Developer) will cover these topics as well as share several tips, tricks, and tools for creating amazing apps that run on all Pebble devices.
4. Continuity
1. User installs your app on an older
platform
2. User upgrades to a new Pebble
3. User already has the most recent
version of your app in their locker :)
13. Aplite Basalt Chalk
CPU
24k
ARM Cortex M3
64k
ARM Cortex M4
Resolution
144x168px
(Rectangular)
180x180px
(Circular)
Color Black & White 64 Colors
SmartStrap ❌ ✅
Mic ❌ ✅
Hardware Features
19. Colors
• Aplite
•Black and white
• Basalt and Chalk
•64 colors
•Pebble color picker tool:
• https://developer.getpebble.com/more/color-picker
•Download color palettes for Photoshop, GIMP, ImageMagick
• https://developer.getpebble.com/guides/pebble-apps/display-
and-animations/intro-to-colors/#color-palettes
20. Open-Source Dithering Library
• Created by Mathew Reiss
• Display approximate colors beyond provided color palette
• https://github.com/mathewreiss/dithering
Aplite: shades of gray Basalt/Chalk: > 300 colors
24. Organize Code Using Color Macros
#define PBL_IF_COLOR_ELSE(if_true, if_false)
#define PBL_IF_BW_ELSE(if_true, if_false)
Returns if_true on watches that support color and if_false otherwise
Returns if_true on watches that only support black & white and if_false otherwise
25. Organize Code Using Color Defines
#define PBL_COLOR
#define PBL_BW
Defined on watches that support color and undefined otherwise
Defined on watches that only support black & white and undefined otherwise
27. WatchInfoColor
GColor text_foreground_color, text_background_color;
switch(watch_info_get_color()) {
case WATCH_INFO_COLOR_TIME_STEEL_GOLD:
// White on red theme
text_foreground_color = GColorWhite;
text_background_color = GColorRed;
break;
case WATCH_INFO_COLOR_TIME_ROUND_BLACK_20:
// White on black theme
text_foreground_color = GColorWhite;
text_background_color = GColorBlack;
break;
/* Other cases... */
}
text_layer_set_text_color(s_label_layer, text_foreground_color);
text_layer_set_background_color(s_label_layer, text_background_color);
• Customize your app’s UI using the color of the Pebble watch
36. const GRect a = grect_inset(rect, GEdgeInsets(16)); // all sides
const GRect b = grect_inset(rect, GEdgeInsets(25, 16)); // top & bottom, right & left
const GRect c = grect_inset(rect, GEdgeInsets(25, 16, 10)); // top, right & left, bottom
const GRect d = grect_inset(rect, GEdgeInsets(25, -20, 10, 16)); // top, right, bottom, left
a b
c d
16px
16px
25px 25px 25px
25px
10px 10px
16px 16px 16px 16px16px 16px 16px 20px
Inset Rectangles Using grect_inset() and GEdgeInsets()
38. • Use PBL_IF_[ROUND | RECT]_ELSE() macros to choose
values or simple expressions based on the display shape
• Similar to PBL_IF_[COLOR | BW]_ELSE()
Organize Code Using Display Shape Macros/Defines
const uint16_t area = PBL_IF_ROUND_ELSE(PI * (width * width / 4),
(width * height));
39. • For multiple lines, choose code using PBL_[ROUND | RECT] defines
#if defined(PBL_ROUND)
const GPoint center_point = grect_center_point(&rect);
graphics_fill_circle(ctx, center_point, radius);
#else
const uint16_t corner_radius = 0;
graphics_fill_rect(ctx, rect, corner_radius, GCornerNone);
#endif
Organize Code Using Display Shape Macros/Defines
40. • For > 5 lines, break code into functions with _rect/_round suffixes
• If the functions share the same arguments, you can simplify
calling them by using the PBL_IF_[ROUND | RECT]_ELSE() macro
• Otherwise, use the PBL_[ROUND | RECT] defines:
Organize Code Using Display Shape Macros/Defines
PBL_IF_RECT_ELSE(function_rect, function_round)(arg1, arg2, ...);
#if defined(PBL_RECT)
function_rect(arg1);
#else
function_round(arg1, arg2);
#endif
41. • For > 2 display-specific functions, break into separate _rect/_round files
• Use wrapper file to choose appropriate implementation using display
shape defines/macros
• Alternatively, if the files share a common API:
• (also requires ifdef’ing out implementations)
Organize Code Using Display Shape Macros/Defines
// main.c
#if defined(PBL_RECT)
#include “file_rect.h"
#else
#include “file_round.h"
#endif
src/
file_rect.c
file_rect.h
file_round.c
file_round.h
file.c
file.h
main.c
42. Display-Specific Resources
"resources": {
"media": [
{
"type": "png",
"name": "MY_IMAGE",
"file": "images/image.png"
}
]
}
resources/
images/
image~rect.png
image~color~round.png
appinfo.json:
image~rect.png image~color~round.png
• Most-specific resource will be used
• Ambiguity will result in compiler error
43. Two-Pixel Margin on Round Displays
• Extend background colors to all outer
edges
• Avoid thin rings around the edge of the
display
• Manufacturing variations may result in
off-center appearance
• Instead, use thick rings or significantly
inset from the edge
46. Status Bar
• For SDK 2.x, the status bar appears by default and insets the window from
the top unless you use:
window_set_fullscreen(window, true);
• For SDK 3.x, the status bar is a UI component you must create, configure,
and add to the layer hierarchy yourself:
static StatusBarLayer *s_status_bar;
s_status_bar = status_bar_layer_create();
status_bar_layer_set_separator_mode(s_status_bar,
StatusBarLayerSeparatorModeDotted);
status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorWhite);
const Layer *status_bar_layer = status_bar_layer_get_layer(s_status_bar);
layer_add_child(window_root_layer, status_bar_layer);
55. // default on Chalk
menu_layer_set_center_focused(menu_layer, true);
static int16_t get_cell_height_callback(MenuLayer *menu_layer,
MenuIndex *cell_index,
void *callback_context) {
#if defined(PBL_ROUND)
// when center_focused, MenuLayer can handle cell heights
// that change based on the selection status
if (menu_layer_is_index_selected(menu_layer, cell_index)) {
return MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT;
} else {
return MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT;
}
#else
return MENU_CELL_BASIC_CELL_HEIGHT;
#endif
}
MenuLayer
56. TextLayer
• To maximize content visible on round displays, enable text
flow and paging with an optional inset (for padding):
text_layer_enable_screen_text_flow_and_paging(s_text_layer, 8);
58. WatchInfo
• Inspect what watch the app is running on
• Useful e.g. if you want to align visual elements with buttons
• Next to different screen coordinates for different watches
GRect layer_frame = GRectZero;
switch(watch_info_get_model()) {
case WATCH_INFO_MODEL_PEBBLE_STEEL:
layer_frame = GRect(0, 64, 144, 44);
break;
case WATCH_INFO_MODEL_PEBBLE_TIME:
layer_frame = GRect(0, 58, 144, 56);
break;
/* Other cases */
}
60. Platform Defines
• Feature/capability defines, like PBL_COLOR, PBL_ROUND,
etc. should be used in favor of these
• Exception: CPU/memory-specific code
#define PBL_PLATFORM_APLITE
#define PBL_PLATFORM_BASALT
#define PBL_PLATFORM_CHALK
61. Platform Specific Resources
• Can also use ~aplite, ~basalt, and ~chalk suffixes
• But overall, in most cases it’s best to use ~rect, ~round, ~bw, ~color suffixes instead
"resources": {
"media": [
{
"type": "png",
"name": "MY_IMAGE",
"file": “images/image.png”,
"targetPlatforms": [
"basalt"
]
}
]
}
62. GPoint shim_gpoint_from_polar(GRect rect, int32_t angle) {
#if defined(PBL_SDK_2)
const GPoint center = GPoint(rect.origin.x + (rect.size.w / 2),
rect.origin.y + (rect.size.h / 2));
const int16_t radius = MIN(rect.size.w, rect.size.h) / 2;
return GPoint((sin_lookup(angle) * radius / TRIG_MAX_RATIO) + center.x,
(-cos_lookup(angle) * radius / TRIG_MAX_RATIO) + center.y);
#else
return gpoint_from_polar(rect, GOvalScaleModeFitCircle, angle);
#endif
}
Shims
• Newer SDK 3.x functions do not exist in SDK 2.x
• You can create shims for them using PBL_SDK_2/3 defines:
63. PebbleKit JS API Availability
• Some PebbleKit JS APIs don’t exist on Pebble Classic mobile apps,
• Check if they are available and fail gracefully if necessary
if (Pebble.getActiveWatchInfo) {
// API is available; use it!
var info = Pebble.getActiveWatchInfo();
console.log('Pebble model: ' + info.model);
} else {
// Gracefully handle unavailable API
}
64. Check Return Values
• APIs may return NULL if:
• Unsupported by platform
• Permissions not given
• Phone disconnected
• Check the return value before using it and fail gracefully if necessary
const DictationSession *session = dictation_session_create(0, callback,
callback_context);
if (session) {
/* Success! */
dictation_session_start(session);
} else {
/* DictationSession is unsupported, need to fail gracefully here! */
}
65. Marcel: Friday 1 pm
Starting with Chalk, you
need to compile with
PebbleKit iOS 3.0
Since PebbleOS 3.0, you
need to compile with
PebbleKit Android 3.0
AndroidiOS
Mobile App Compatibility
66. Running Multiple Emulators at Once
• pebble install --emulator=aplite && pebble install --emulator=basalt &&
pebble install —-emulator=chalk