#PDR15 Creating Pebble Apps for Aplite, Basalt, and Chalk
-
Upload
pebble-technology -
Category
Technology
-
view
2.022 -
download
1
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?