#PDR15 Creating Pebble Apps for Aplite, Basalt, and Chalk

Post on 12-Apr-2017

2.022 views 1 download

Transcript of #PDR15 Creating Pebble Apps for Aplite, Basalt, and Chalk

2015 Pebble Developer Retreat

Creating Pebble Apps for Aplite, Basalt, and Chalk

Kevin Conley, Firmware Engineer

Why Target Multiple Platforms?

Maximize Userbase

Pebble Time

Pebble Classic

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 :)

Developer Portal Icons

Developer Portal Icons

Agenda

•Runtime Platforms vs. SDK Versions •Colors •Display Shapes •SDK Compatibility

Runtime Platforms vs. SDK Versions

“Runtime Platform”

•Set of runtime attributes and capabilities •Does NOT refer to a single watch hardware model

Aplite

2.x ✅

Pebble Classic Pebble Steel

OS Compatibility

•2.x is legacy •3.x for Aplite coming soon!

Aplite Basalt

2.x ✅

3.0 ❌ ✅

Pebble Time Pebble Time Steel

OS Compatibility

Aplite Basalt Chalk

2.x ✅

3.0 ❌ ✅

3.6 ❌ ✅

Pebble Time Round

OS Compatibility

Aplite Basalt Chalk

CPU 24kARM Cortex M3

64k ARM Cortex M4

Resolution 144x168px(Rectangular)

180x180px (Circular)

Color Black & White 64 Colors

SmartStrap ❌ ✅

Mic ❌ ✅

Hardware Features

Aplite, Basalt, and Chalk are runtime platforms, not SDK versions.

Aplite Basalt Chalk

2.x ✅

3.0 ❌ ✅

3.6 ❌ ✅

3.x ✅

Unified 3.x SDK later this year

OS Compatibility

runs on

2.x 3.x

Aplite Basalt

2.x Aplite ✅ ✅

3.x Basalt ❌ ✅

com

pile

d a

gai

nst

2.x Aplite runs on 3.x Basalt

SDK Compatibility

runs on

2.x 3.x

Aplite Aplite Basalt Chalk

2.x Aplite ✅ ✅ ✅ ❌

3.x

Aplite ❌ ✅ ❌ ❌

Basalt ❌ ❌ ✅ ❌

Chalk ❌ ❌ ❌ ✅

com

pile

d a

gai

nst

3.x apps are compiled per platform

SDK Compatibility

Colors

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

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

#define COLOR_FALLBACK(_____, _____)

#define COLOR_FALLBACK(color_value, bw_value)

#define COLOR_FALLBACK(color_value, bw_value)

(deprecated)

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

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

Color-Specific Resources

"resources": { "media": [ { "type": "png", "name": "MY_IMAGE", "file": "images/image.png" } ]}

appinfo.json: resources/ images/ image~bw.png image~color.png

image~bw.png image~color.png

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

Display Shapes

Display Shapes

• Round has 7% more visible pixels than rectangular

Rectangular (144 x 168)

Round (visible)

Round (total) (180 x 180)

(0, 0)

Calculate Position/Size as a Function of Other Layers

const Layer *window_layer = window_get_root_layer(window);

const GRect text_frame = GRect(0, 72, 144, 20);

const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));

Calculate Position/Size as a Function of Other Layers

const Layer *window_layer = window_get_root_layer(window);

const GRect text_frame = GRect(0, 72, 144, 20);

const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));

Calculate Position/Size as a Function of Other Layers

const Layer *window_layer = window_get_root_layer(window);

const GRect text_frame = GRect(0, 72, 144, 20);

const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));

Calculate Position/Size as a Function of Other Layers

const Layer *window_layer = window_get_root_layer(window);

const GRect window_bounds = layer_get_bounds(window_layer); const int16_t text_height = 20; const int16_t text_frame_y = (window_bounds.size.h - text_height) / 2; const GRect text_frame = (GRect) { .origin = GPoint(0, text_frame_y), .size = GSize(window_bounds.size.w, text_height) };

const TextLayer *text_layer = text_layer_create(text_frame); text_layer_set_text(text_layer, "Hello world!"); text_layer_set_text_alignment(text_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(text_layer));

void grect_align(GRect *rect, const GRect *inside_rect, const GAlign alignment, const bool clip);

GAlignCenter clip = false

rect

inside_rect

Align Rectangles Within Other Rectangles Using grect_align()

inside_rect

GAlignTop clip = true

rect

typedef enum GAlign { GAlignCenter, GAlignTopLeft, GAlignTopRight, GAlignTop, GAlignLeft, GAlignBottom, GAlignRight, GAlignBottomRight, GAlignBottomLeft} GAlign; GAlignBottomRight

GAlignRight

GAlignTopRight

GAlignBottom

GAlignCenter

GAlignTop

GAlignBottomLeft

GAlignLeft

GAlignTopLeft

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

25px10px 10px

16px 16px 16px 16px16px 16px 16px 20px

Inset Rectangles Using grect_inset() and GEdgeInsets()

rect

GPoint grect_center_point(const GRect *rect);

result

Find the Center of Rectangles Using grect_center_point()

•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));

•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

• 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

• 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

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

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

Platform-Specific Designs

“Caltrain” by Katharine Berry: https://github.com/Katharine/pebble-caltrain/

Rectangular Round

UI Components

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);

Status Bar

Rectangular Round

Status Bar

s_status_bar = status_bar_layer_create();GRect status_bar_rect = window_bounds;status_bar_rect.size.h = STATUS_BAR_LAYER_HEIGHT; layer_set_frame( status_bar_layer_get_layer(s_status_bar), status_bar_rect);// top/bottom: statusbar, left/right: 0 const GEdgeInsets insets = GEdgeInsets( STATUS_BAR_LAYER_HEIGHT, 0); GRect menu_layer_rect = grect_inset( window_bounds, insets);

Status Bar

ActionBarRectangular

Round

ActionBar

s_action_bar = action_bar_layer_create();action_bar_layer_add_to_window( s_action_bar, window);// top: statusbar, right: actionbar, bottom/left:0 const GEdgeInsets insets = GEdgeInsets( STATUS_BAR_LAYER_HEIGHT, ACTION_BAR_WIDTH, 0, 0);GRect content_rect = grect_inset( window_bounds, insets);

ActionBar

MenuLayer

Rectangular Round

MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT

MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHTMENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT

MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT

MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT

MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT

MENU_CELL_BASIC_CELL_HEIGHT

MENU_CELL_BASIC_CELL_HEIGHT

MENU_CELL_BASIC_CELL_HEIGHT

MenuLayer

// 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

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);

TextLayer

•Text flow on a round display without pagination looks weird…

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 */}

SDK Compatibility

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

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" ] } ]}

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:

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 }

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! */ }

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

Running Multiple Emulators at Once• pebble install --emulator=aplite && pebble install --emulator=basalt && pebble install —-emulator=chalk

Automating Taking Screenshots• pebble screenshot --emulator=aplite && pebble screenshot --emulator=basalt && pebble screenshot —-emulator=chalk

Recap

•Runtime Platforms vs. SDK Versions •Colors •Display Shapes •SDK Compatibility

Questions?