├── LICENSE ├── README.md ├── appinfo.json ├── resources └── images │ ├── config-required.png │ ├── confirm.png │ ├── cross.png │ ├── tick.png │ ├── tick_black.png │ ├── tick_white.png │ └── warning.png ├── screenshots ├── checkbox-list.png ├── config-window.png ├── dialog-choice.png ├── dialog-message.gif ├── list-message.png ├── menu.png ├── pin.png ├── progress-bar.gif ├── progresslayer.gif ├── radio-button.png └── text-change-anim.gif ├── src ├── layers │ ├── progress_layer.c │ ├── progress_layer.h │ ├── selection_layer.c │ └── selection_layer.h ├── main.c └── windows │ ├── checkbox_window.c │ ├── checkbox_window.h │ ├── dialog_choice_window.c │ ├── dialog_choice_window.h │ ├── dialog_config_window.c │ ├── dialog_config_window.h │ ├── dialog_message_window.c │ ├── dialog_message_window.h │ ├── list_message_window.c │ ├── list_message_window.h │ ├── pin_window.c │ ├── pin_window.h │ ├── progress_bar_window.c │ ├── progress_bar_window.h │ ├── progress_layer_window.c │ ├── progress_layer_window.h │ ├── radio_button_window.c │ ├── radio_button_window.h │ ├── text_animation_window.c │ └── text_animation_window.h └── wscript /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pebble Examples 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ui-patterns 2 | 3 | ![](screenshots/menu.png) 4 | 5 | Example project showing implementations of recommended Pebble UI design 6 | patterns. Run the app, then choose a menu option to see one of: 7 | 8 | ## Checkbox list 9 | 10 | ![](screenshots/checkbox-list.png) 11 | 12 | ## Radio Button list 13 | 14 | ![](screenshots/radio-button.png) 15 | 16 | ## Message dialog 17 | 18 | ![](screenshots/dialog-message.gif) 19 | 20 | ## Choice dialog 21 | 22 | ![](screenshots/dialog-choice.png) 23 | 24 | ## List hint message 25 | 26 | ![](screenshots/list-message.png) 27 | 28 | ## PIN entry 29 | 30 | ![](screenshots/pin.png) 31 | 32 | ## Text Animation 33 | 34 | ![](screenshots/text-change-anim.gif) 35 | 36 | ## Progress Bar 37 | 38 | ![](screenshots/progress-bar.gif) 39 | 40 | ## Progress Layer 41 | 42 | ![](screenshots/progresslayer.gif) 43 | 44 | ## Config Dialog Window 45 | 46 | ![](screenshots/config-window.png) 47 | -------------------------------------------------------------------------------- /appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "5e8eec99-fa47-4edf-997c-e28d9b3c5c35", 3 | "shortName": "UI Patterns Demo", 4 | "longName": "UI Patterns Demo", 5 | "companyName": "PebbleDev", 6 | "versionCode": 4, 7 | "versionLabel": "1.3", 8 | "sdkVersion": "3", 9 | "targetPlatforms": ["aplite", "basalt", "chalk"], 10 | "watchapp": { 11 | "watchface": false 12 | }, 13 | "appKeys": { 14 | "dummy": 0 15 | }, 16 | "resources": { 17 | "media": [ 18 | { 19 | "type": "bitmap", 20 | "name": "TICK_BLACK", 21 | "file": "images/tick_black.png" 22 | }, 23 | { 24 | "type": "bitmap", 25 | "name": "TICK_WHITE", 26 | "file": "images/tick_white.png" 27 | }, 28 | { 29 | "type": "bitmap", 30 | "name": "CONFIRM", 31 | "file": "images/confirm.png" 32 | }, 33 | { 34 | "type": "bitmap", 35 | "name": "CROSS", 36 | "file": "images/cross.png" 37 | }, 38 | { 39 | "type": "bitmap", 40 | "name": "TICK", 41 | "file": "images/tick.png" 42 | }, 43 | { 44 | "type": "bitmap", 45 | "name": "WARNING", 46 | "file": "images/warning.png" 47 | }, 48 | { 49 | "type": "bitmap", 50 | "name": "CONFIG_REQUIRED", 51 | "file": "images/config-required.png" 52 | } 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /resources/images/config-required.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/config-required.png -------------------------------------------------------------------------------- /resources/images/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/confirm.png -------------------------------------------------------------------------------- /resources/images/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/cross.png -------------------------------------------------------------------------------- /resources/images/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/tick.png -------------------------------------------------------------------------------- /resources/images/tick_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/tick_black.png -------------------------------------------------------------------------------- /resources/images/tick_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/tick_white.png -------------------------------------------------------------------------------- /resources/images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/resources/images/warning.png -------------------------------------------------------------------------------- /screenshots/checkbox-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/checkbox-list.png -------------------------------------------------------------------------------- /screenshots/config-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/config-window.png -------------------------------------------------------------------------------- /screenshots/dialog-choice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/dialog-choice.png -------------------------------------------------------------------------------- /screenshots/dialog-message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/dialog-message.gif -------------------------------------------------------------------------------- /screenshots/list-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/list-message.png -------------------------------------------------------------------------------- /screenshots/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/menu.png -------------------------------------------------------------------------------- /screenshots/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/pin.png -------------------------------------------------------------------------------- /screenshots/progress-bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/progress-bar.gif -------------------------------------------------------------------------------- /screenshots/progresslayer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/progresslayer.gif -------------------------------------------------------------------------------- /screenshots/radio-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/radio-button.png -------------------------------------------------------------------------------- /screenshots/text-change-anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pebble-examples/ui-patterns/f86bd9ac452a919eef932e13ca6e131943fc1c85/screenshots/text-change-anim.gif -------------------------------------------------------------------------------- /src/layers/progress_layer.c: -------------------------------------------------------------------------------- 1 | #include "progress_layer.h" 2 | 3 | #define MIN(a,b) (((a)<(b))?(a):(b)) 4 | 5 | typedef struct { 6 | int16_t progress_percent; 7 | int16_t corner_radius; 8 | GColor foreground_color; 9 | GColor background_color; 10 | } ProgressLayerData; 11 | 12 | static int16_t scale_progress_bar_width_px(unsigned int progress_percent, int16_t rect_width_px) { 13 | return ((progress_percent * (rect_width_px)) / 100); 14 | } 15 | 16 | static void progress_layer_update_proc(ProgressLayer* progress_layer, GContext* ctx) { 17 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 18 | GRect bounds = layer_get_bounds(progress_layer); 19 | 20 | int16_t progress_bar_width_px = scale_progress_bar_width_px(data->progress_percent, bounds.size.w); 21 | GRect progress_bar = GRect(bounds.origin.x, bounds.origin.y, progress_bar_width_px, bounds.size.h); 22 | 23 | graphics_context_set_fill_color(ctx, data->background_color); 24 | graphics_fill_rect(ctx, bounds, data->corner_radius, GCornersAll); 25 | 26 | graphics_context_set_fill_color(ctx, data->foreground_color); 27 | graphics_fill_rect(ctx, progress_bar, data->corner_radius, GCornersAll); 28 | 29 | #ifdef PBL_PLATFORM_APLITE 30 | graphics_context_set_stroke_color(ctx, data->background_color); 31 | graphics_draw_rect(ctx, progress_bar); 32 | #endif 33 | } 34 | 35 | ProgressLayer* progress_layer_create(GRect frame) { 36 | ProgressLayer *progress_layer = layer_create_with_data(frame, sizeof(ProgressLayerData)); 37 | layer_set_update_proc(progress_layer, progress_layer_update_proc); 38 | layer_mark_dirty(progress_layer); 39 | 40 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 41 | data->progress_percent = 0; 42 | data->corner_radius = 1; 43 | data->foreground_color = GColorBlack; 44 | data->background_color = GColorWhite; 45 | 46 | return progress_layer; 47 | } 48 | 49 | void progress_layer_destroy(ProgressLayer* progress_layer) { 50 | if (progress_layer) { 51 | layer_destroy(progress_layer); 52 | } 53 | } 54 | 55 | void progress_layer_increment_progress(ProgressLayer* progress_layer, int16_t progress) { 56 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 57 | data->progress_percent = MIN(100, data->progress_percent + progress); 58 | layer_mark_dirty(progress_layer); 59 | } 60 | 61 | void progress_layer_set_progress(ProgressLayer* progress_layer, int16_t progress_percent) { 62 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 63 | data->progress_percent = MIN(100, progress_percent); 64 | layer_mark_dirty(progress_layer); 65 | } 66 | 67 | void progress_layer_set_corner_radius(ProgressLayer* progress_layer, uint16_t corner_radius) { 68 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 69 | data->corner_radius = corner_radius; 70 | layer_mark_dirty(progress_layer); 71 | } 72 | 73 | void progress_layer_set_foreground_color(ProgressLayer* progress_layer, GColor color) { 74 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 75 | data->foreground_color = color; 76 | layer_mark_dirty(progress_layer); 77 | } 78 | 79 | void progress_layer_set_background_color(ProgressLayer* progress_layer, GColor color) { 80 | ProgressLayerData *data = (ProgressLayerData *)layer_get_data(progress_layer); 81 | data->background_color = color; 82 | layer_mark_dirty(progress_layer); 83 | } 84 | -------------------------------------------------------------------------------- /src/layers/progress_layer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef Layer ProgressLayer; 6 | 7 | ProgressLayer* progress_layer_create(GRect frame); 8 | void progress_layer_destroy(ProgressLayer* progress_layer); 9 | void progress_layer_increment_progress(ProgressLayer* progress_layer, int16_t progress); 10 | void progress_layer_set_progress(ProgressLayer* progress_layer, int16_t progress_percent); 11 | void progress_layer_set_corner_radius(ProgressLayer* progress_layer, uint16_t corner_radius); 12 | void progress_layer_set_foreground_color(ProgressLayer* progress_layer, GColor color); 13 | void progress_layer_set_background_color(ProgressLayer* progress_layer, GColor color); -------------------------------------------------------------------------------- /src/layers/selection_layer.c: -------------------------------------------------------------------------------- 1 | // Pebble UI component adapted for modular use by Eric Phillips 2 | 3 | #include 4 | #include "selection_layer.h" 5 | 6 | // Look and feel 7 | #define DEFAULT_CELL_PADDING 10 8 | #define DEFAULT_SELECTED_INDEX 0 9 | #define DEFAULT_FONT FONT_KEY_GOTHIC_28_BOLD 10 | #define DEFAULT_ACTIVE_COLOR GColorWhite 11 | #define DEFAULT_INACTIVE_COLOR PBL_IF_COLOR_ELSE(GColorDarkGray, GColorBlack) 12 | 13 | #define BUTTON_HOLD_REPEAT_MS 100 14 | #define SETTLE_HEIGHT_DIFF 6 15 | 16 | // Animation 17 | #define BUMP_TEXT_DURATION_MS 107 18 | #define BUMP_SETTLE_DURATION_MS 214 19 | #define SLIDE_DURATION_MS 107 20 | #define SLIDE_SETTLE_DURATION_MS 179 21 | 22 | // Function prototypes 23 | static Animation* prv_create_bump_settle_animation(Layer *layer); 24 | static Animation* prv_create_slide_settle_animation(Layer *layer); 25 | 26 | static int prv_get_pixels_for_bump_settle(int anim_percent_complete) { 27 | if (anim_percent_complete) { 28 | return SETTLE_HEIGHT_DIFF - ((SETTLE_HEIGHT_DIFF * anim_percent_complete) / 100); 29 | } else { 30 | return 0; 31 | } 32 | } 33 | 34 | static int prv_get_font_top_padding(GFont font) { 35 | if (font == fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)) { 36 | return 10; 37 | } else if (font == fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)) { 38 | return 10; 39 | } else { 40 | return 0; 41 | } 42 | } 43 | 44 | static int prv_get_y_offset_which_vertically_centers_font(GFont font, int height) { 45 | int font_height = 0; 46 | int font_top_padding = prv_get_font_top_padding(font); 47 | if (font == fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)) { 48 | font_height = 18; 49 | } else if (font == fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)) { 50 | font_height = 14; 51 | } 52 | 53 | return (height / 2) - (font_height / 2) - font_top_padding; 54 | } 55 | 56 | static void prv_draw_cell_backgrounds(Layer *layer, GContext *ctx) { 57 | SelectionLayerData *data = layer_get_data(layer); 58 | // Loop over each cell and draw the background rectangles 59 | for (int i = 0, current_x_offset = 0; i < data->num_cells; i++) { 60 | if (data->cell_widths[i] == 0) { 61 | continue; 62 | } 63 | 64 | int y_offset = 0; 65 | if (data->selected_cell_idx == i && data->bump_is_upwards) { 66 | y_offset = -prv_get_pixels_for_bump_settle(data->bump_settle_anim_progress); 67 | } 68 | 69 | int height = layer_get_bounds(layer).size.h; 70 | if (data->selected_cell_idx == i) { 71 | height += prv_get_pixels_for_bump_settle(data->bump_settle_anim_progress); 72 | } 73 | 74 | const GRect rect = GRect(current_x_offset, y_offset, data->cell_widths[i], height); 75 | 76 | GColor bg_color = data->inactive_background_color; 77 | 78 | if (data->selected_cell_idx == i && !data->slide_amin_progress) { 79 | bg_color = data->active_background_color; 80 | } 81 | graphics_context_set_fill_color(ctx, bg_color); 82 | graphics_fill_rect(ctx, rect, 1, GCornerNone); 83 | 84 | current_x_offset += data->cell_widths[i] + data->cell_padding; 85 | } 86 | } 87 | 88 | static void prv_draw_slider_slide(Layer *layer, GContext *ctx) { 89 | SelectionLayerData *data = layer_get_data(layer); 90 | 91 | int starting_x_offset = 0; 92 | for (int i = 0; i < data->num_cells; i++) { 93 | if (data->selected_cell_idx == i) { 94 | break; 95 | } 96 | starting_x_offset += data->cell_widths[i] + data->cell_padding; 97 | } 98 | 99 | int next_cell_width = data->cell_widths[data->selected_cell_idx + 1]; 100 | if (!data->slide_is_forward) { 101 | next_cell_width = data->cell_widths[data->selected_cell_idx - 1]; 102 | } 103 | 104 | int slide_distance = next_cell_width + data->cell_padding; 105 | int current_slide_distance = (slide_distance * data->slide_amin_progress) / 100; 106 | if (!data->slide_is_forward) { 107 | current_slide_distance = -current_slide_distance; 108 | } 109 | 110 | int current_x_offset = starting_x_offset + current_slide_distance; 111 | int cur_cell_width = data->cell_widths[data->selected_cell_idx]; 112 | int total_cell_width_change = next_cell_width - cur_cell_width + data->cell_padding; 113 | int current_cell_width_change = (total_cell_width_change * (int) data->slide_amin_progress) / 100; 114 | int current_cell_width = cur_cell_width + current_cell_width_change; 115 | if (!data->slide_is_forward) { 116 | current_x_offset -= current_cell_width_change; 117 | } 118 | 119 | GRect rect = GRect(current_x_offset, 0, current_cell_width, layer_get_bounds(layer).size.h); 120 | 121 | graphics_context_set_fill_color(ctx, data->active_background_color); 122 | graphics_fill_rect(ctx, rect, 1, GCornerNone); 123 | } 124 | 125 | static void prv_draw_slider_settle(Layer *layer, GContext *ctx) { 126 | SelectionLayerData *data = layer_get_data(layer); 127 | int starting_x_offset = 0; 128 | for (int i = 0; i < data->num_cells; i++) { 129 | if (data->selected_cell_idx == i) { 130 | break; 131 | } 132 | starting_x_offset += data->cell_widths[i] + data->cell_padding; 133 | } 134 | 135 | int x_offset = starting_x_offset; 136 | if (data->slide_is_forward) { 137 | x_offset += data->cell_widths[data->selected_cell_idx]; 138 | } 139 | 140 | int current_width = (data->cell_padding * data->slide_settle_anim_progress) / 100; 141 | if (!data->slide_is_forward) { 142 | x_offset -= current_width; 143 | } 144 | 145 | GRect rect = GRect(x_offset, 0, current_width, layer_get_bounds(layer).size.h); 146 | 147 | graphics_context_set_fill_color(ctx, data->active_background_color); 148 | graphics_fill_rect(ctx, rect, 1, GCornerNone); 149 | } 150 | 151 | static void prv_draw_text(Layer *layer, GContext *ctx) { 152 | SelectionLayerData *data = layer_get_data(layer); 153 | for (int i = 0, current_x_offset = 0; i < data->num_cells; i++) { 154 | if (data->callbacks.get_cell_text) { 155 | char *text = data->callbacks.get_cell_text(i, data->context); 156 | if (text) { 157 | int height = layer_get_bounds(layer).size.h; 158 | if (data->selected_cell_idx == i) { 159 | height += prv_get_pixels_for_bump_settle(data->bump_settle_anim_progress); 160 | } 161 | int y_offset = prv_get_y_offset_which_vertically_centers_font(data->font, height); 162 | 163 | if (data->selected_cell_idx == i && data->bump_is_upwards) { 164 | y_offset -= prv_get_pixels_for_bump_settle(data->bump_settle_anim_progress); 165 | } 166 | 167 | if (data->selected_cell_idx == i) { 168 | int delta = (data->bump_text_anim_progress * prv_get_font_top_padding(data->font)) / 100; 169 | if (data->bump_is_upwards) { 170 | delta *= -1; 171 | } 172 | y_offset += delta; 173 | } 174 | 175 | GRect rect = GRect(current_x_offset, y_offset, data->cell_widths[i], height); 176 | graphics_draw_text(ctx, text, data->font, rect, GTextOverflowModeFill, GTextAlignmentCenter, NULL); 177 | } 178 | } 179 | 180 | current_x_offset += data->cell_widths[i] + data->cell_padding; 181 | } 182 | } 183 | 184 | static void prv_draw_selection_layer(Layer *layer, GContext *ctx) { 185 | SelectionLayerData *data = layer_get_data(layer); 186 | prv_draw_cell_backgrounds(layer, ctx); 187 | 188 | if (data->slide_amin_progress) { 189 | prv_draw_slider_slide(layer, ctx); 190 | } 191 | if (data->slide_settle_anim_progress) { 192 | prv_draw_slider_settle(layer, ctx); 193 | } 194 | 195 | prv_draw_text(layer, ctx); 196 | } 197 | 198 | /////////////////////////////////////////////////////////////////////////////////////////////////// 199 | //! Increment / Decrement Animation 200 | 201 | //! This animation causes a the active cell to "bump" when the user presses the up button. 202 | //! This animation has two parts: 203 | //! 1) The "text to cell edge" 204 | //! 2) The "background settle" 205 | 206 | //! The "text to cell edge" (bump_text) moves the text until it hits the top / bottom of the cell. 207 | 208 | //! The "background settle" (bump_settle) is a reaction to the "text to cell edge" animation. 209 | //! The top of the cell immediately expands down giving the impression that the text "pushed" the 210 | //! cell making it bigger. The cell then shrinks / settles back to its original height 211 | //! with the text vertically centered 212 | 213 | static void prv_bump_text_impl(struct Animation *animation, const AnimationProgress distance_normalized) { 214 | Layer *layer = (Layer*) animation_get_context(animation); 215 | SelectionLayerData *data = layer_get_data(layer); 216 | 217 | data->bump_text_anim_progress = (100 * distance_normalized) / ANIMATION_NORMALIZED_MAX; 218 | layer_mark_dirty(layer); 219 | } 220 | 221 | static void prv_bump_text_stopped(Animation *animation, bool finished, void *context) { 222 | Layer *layer = (Layer*)animation_get_context(animation); 223 | SelectionLayerData *data = layer_get_data(layer); 224 | 225 | data->bump_text_anim_progress = 0; 226 | 227 | if (data->bump_is_upwards == true) { 228 | data->callbacks.increment(data->selected_cell_idx, 1, data->context); 229 | } else { 230 | data->callbacks.decrement(data->selected_cell_idx, 1, data->context); 231 | } 232 | 233 | animation_destroy(animation); 234 | 235 | Animation *bump_settle = prv_create_bump_settle_animation(layer); 236 | animation_schedule(bump_settle); 237 | } 238 | 239 | static void prv_bump_settle_impl(struct Animation *animation, const AnimationProgress distance_normalized) { 240 | Layer *layer = (Layer*)animation_get_context(animation); 241 | SelectionLayerData *data = layer_get_data(layer); 242 | 243 | data->bump_settle_anim_progress = (100 * distance_normalized) / ANIMATION_NORMALIZED_MAX; 244 | layer_mark_dirty(layer); 245 | } 246 | 247 | static void prv_bump_settle_stopped(Animation *animation, bool finished, void *context) { 248 | Layer *layer = (Layer*)animation_get_context(animation); 249 | SelectionLayerData *data = layer_get_data(layer); 250 | 251 | data->bump_settle_anim_progress = 0; 252 | animation_destroy(animation); 253 | } 254 | 255 | static Animation* prv_create_bump_text_animation(Layer *layer) { 256 | SelectionLayerData *data = layer_get_data(layer); 257 | 258 | PropertyAnimation *bump_text_anim = property_animation_create_layer_frame(layer, NULL, NULL); 259 | Animation *animation = property_animation_get_animation(bump_text_anim); 260 | animation_set_curve(animation, AnimationCurveEaseIn); 261 | animation_set_duration(animation, BUMP_TEXT_DURATION_MS); 262 | AnimationHandlers anim_handler = { 263 | .stopped = prv_bump_text_stopped, 264 | }; 265 | animation_set_handlers(animation, anim_handler, layer); 266 | 267 | data->bump_text_impl = (AnimationImplementation) { 268 | .update = prv_bump_text_impl, 269 | }; 270 | animation_set_implementation(animation, &data->bump_text_impl); 271 | 272 | return animation; 273 | } 274 | 275 | static Animation* prv_create_bump_settle_animation(Layer *layer) { 276 | SelectionLayerData *data = layer_get_data(layer); 277 | 278 | PropertyAnimation *bump_settle_anim = property_animation_create_layer_frame(layer, NULL, NULL); 279 | Animation *animation = property_animation_get_animation(bump_settle_anim); 280 | animation_set_curve(animation, AnimationCurveEaseOut); 281 | animation_set_duration(animation, BUMP_SETTLE_DURATION_MS); 282 | AnimationHandlers anim_handler = { 283 | .stopped = prv_bump_settle_stopped, 284 | }; 285 | animation_set_handlers(animation, anim_handler, layer); 286 | 287 | data->bump_settle_anim_impl = (AnimationImplementation) { 288 | .update = prv_bump_settle_impl, 289 | }; 290 | animation_set_implementation(animation, &data->bump_settle_anim_impl); 291 | 292 | return animation; 293 | } 294 | 295 | static void prv_run_value_change_animation(Layer *layer) { 296 | SelectionLayerData *data = layer_get_data(layer); 297 | 298 | Animation *bump_text = prv_create_bump_text_animation(layer); 299 | Animation *bump_settle = prv_create_bump_settle_animation(layer); 300 | data->value_change_animation = animation_sequence_create(bump_text, bump_settle, NULL); 301 | animation_schedule(data->value_change_animation); 302 | } 303 | 304 | /////////////////////////////////////////////////////////////////////////////////////////////////// 305 | //! Slide Animation 306 | 307 | //! This animation moves the "selection box" (active color) to the next cell to the right. 308 | //! This animation has two parts: 309 | //! 1) The "move and expand" 310 | //! 2) The "settle" 311 | 312 | //! The "move and expand" (slide) moves the selection box from the currently active cell to 313 | //! the next cell to the right. At the same time the width is changed to be the size of the 314 | //! next cell plus the size of the padding. This creates an overshoot effect. 315 | 316 | //! The "settle" (slide_settle) removes the extra width that was added in the "move and expand" 317 | //! step. 318 | 319 | static void prv_slide_impl(struct Animation *animation, const AnimationProgress distance_normalized) { 320 | Layer *layer = (Layer*) animation_get_context(animation); 321 | SelectionLayerData *data = layer_get_data(layer); 322 | 323 | data->slide_amin_progress = (100 * distance_normalized) / ANIMATION_NORMALIZED_MAX; 324 | layer_mark_dirty(layer); 325 | } 326 | 327 | static void prv_slide_stopped(Animation *animation, bool finished, void *context) { 328 | Layer *layer = (Layer*)animation_get_context(animation); 329 | SelectionLayerData *data = layer_get_data(layer); 330 | 331 | data->slide_amin_progress = 0; 332 | 333 | if (data->slide_is_forward) { 334 | data->selected_cell_idx++; 335 | } else { 336 | data->selected_cell_idx--; 337 | } 338 | 339 | animation_destroy(animation); 340 | 341 | Animation *settle_animation = prv_create_slide_settle_animation(layer); 342 | animation_schedule(settle_animation); 343 | } 344 | 345 | static void prv_slide_settle_impl(struct Animation *animation, const AnimationProgress distance_normalized) { 346 | Layer *layer = (Layer*)animation_get_context(animation); 347 | SelectionLayerData *data = layer_get_data(layer); 348 | 349 | data->slide_settle_anim_progress = 100 - ((100 * distance_normalized) / ANIMATION_NORMALIZED_MAX); 350 | layer_mark_dirty(layer); 351 | } 352 | 353 | static void prv_slide_settle_stopped(Animation *animation, bool finished, void *context) { 354 | Layer *layer = (Layer*) animation_get_context(animation); 355 | SelectionLayerData *data = layer_get_data(layer); 356 | 357 | data->slide_settle_anim_progress = 0; 358 | animation_destroy(animation); 359 | } 360 | 361 | static Animation* prv_create_slide_animation(Layer *layer) { 362 | SelectionLayerData *data = layer_get_data(layer); 363 | 364 | PropertyAnimation *slide_amin = property_animation_create_layer_frame(layer, NULL, NULL); 365 | Animation *animation = property_animation_get_animation(slide_amin); 366 | animation_set_curve(animation, AnimationCurveEaseIn); 367 | animation_set_duration(animation, SLIDE_DURATION_MS); 368 | AnimationHandlers anim_handler = { 369 | .stopped = prv_slide_stopped, 370 | }; 371 | animation_set_handlers(animation, anim_handler, layer); 372 | 373 | data->slide_amin_impl = (AnimationImplementation) { 374 | .update = prv_slide_impl, 375 | }; 376 | animation_set_implementation(animation, &data->slide_amin_impl); 377 | 378 | return animation; 379 | } 380 | 381 | static Animation* prv_create_slide_settle_animation(Layer *layer) { 382 | SelectionLayerData *data = layer_get_data(layer); 383 | PropertyAnimation *slide_settle_anim = property_animation_create_layer_frame(layer, NULL, NULL); 384 | Animation *animation = property_animation_get_animation(slide_settle_anim); 385 | animation_set_curve(animation, AnimationCurveEaseOut); 386 | animation_set_duration(animation, SLIDE_SETTLE_DURATION_MS); 387 | AnimationHandlers anim_handler = { 388 | .stopped = prv_slide_settle_stopped, 389 | }; 390 | animation_set_handlers(animation, anim_handler, layer); 391 | 392 | data->slide_settle_anim_impl = (AnimationImplementation) { 393 | .update = prv_slide_settle_impl, 394 | }; 395 | animation_set_implementation(animation, &data->slide_settle_anim_impl); 396 | 397 | return animation; 398 | } 399 | 400 | static void prv_run_slide_animation(Layer *layer) { 401 | SelectionLayerData *data = layer_get_data(layer); 402 | 403 | Animation *over_animation = prv_create_slide_animation(layer); 404 | Animation *settle_animation = prv_create_slide_settle_animation(layer); 405 | data->next_cell_animation = animation_sequence_create(over_animation, settle_animation, NULL); 406 | animation_schedule(data->next_cell_animation); 407 | } 408 | 409 | /////////////////////////////////////////////////////////////////////////////////////////////////// 410 | //! Click handlers 411 | 412 | void prv_up_click_handler(ClickRecognizerRef recognizer, void *context) { 413 | Layer *layer = (Layer*)context; 414 | SelectionLayerData *data = layer_get_data(layer); 415 | 416 | if (data->is_active) { 417 | if (click_recognizer_is_repeating(recognizer)) { 418 | // Don't animate if the button is being held down. Just update the text 419 | data->callbacks.increment(data->selected_cell_idx, click_number_of_clicks_counted(recognizer), data->context); 420 | layer_mark_dirty(layer); 421 | } else { 422 | data->bump_is_upwards = true; 423 | prv_run_value_change_animation(layer); 424 | } 425 | } 426 | } 427 | 428 | void prv_down_click_handler(ClickRecognizerRef recognizer, void *context) { 429 | Layer *layer = (Layer*)context; 430 | SelectionLayerData *data = layer_get_data(layer); 431 | 432 | if (data->is_active) { 433 | if (click_recognizer_is_repeating(recognizer)) { 434 | // Don't animate if the button is being held down. Just update the text 435 | data->callbacks.decrement(data->selected_cell_idx, click_number_of_clicks_counted(recognizer), data->context); 436 | layer_mark_dirty(layer); 437 | } else { 438 | data->bump_is_upwards = false; 439 | prv_run_value_change_animation(layer); 440 | } 441 | } 442 | } 443 | 444 | void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) { 445 | Layer *layer = (Layer*)context; 446 | SelectionLayerData *data = layer_get_data(layer); 447 | 448 | if (data->is_active) { 449 | animation_unschedule(data->next_cell_animation); 450 | if (data->selected_cell_idx >= data->num_cells - 1) { 451 | data->selected_cell_idx = 0; 452 | data->callbacks.complete(data->context); 453 | } else { 454 | data->slide_is_forward = true; 455 | prv_run_slide_animation(layer); 456 | } 457 | } 458 | } 459 | 460 | void prv_back_click_handler(ClickRecognizerRef recognizer, void *context) { 461 | Layer *layer = (Layer*)context; 462 | SelectionLayerData *data = layer_get_data(layer); 463 | 464 | if (data->is_active) { 465 | animation_unschedule(data->next_cell_animation); 466 | if (data->selected_cell_idx == 0) { 467 | data->selected_cell_idx = 0; 468 | window_stack_pop(true); 469 | } else { 470 | data->slide_is_forward = false; 471 | prv_run_slide_animation(layer); 472 | } 473 | } 474 | } 475 | 476 | static void prv_click_config_provider(Layer *layer) { 477 | window_set_click_context(BUTTON_ID_UP, layer); 478 | window_set_click_context(BUTTON_ID_DOWN, layer); 479 | window_set_click_context(BUTTON_ID_SELECT, layer); 480 | window_set_click_context(BUTTON_ID_BACK, layer); 481 | 482 | window_single_repeating_click_subscribe(BUTTON_ID_UP, BUTTON_HOLD_REPEAT_MS, prv_up_click_handler); 483 | window_single_repeating_click_subscribe(BUTTON_ID_DOWN, BUTTON_HOLD_REPEAT_MS, prv_down_click_handler); 484 | window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler); 485 | window_single_click_subscribe(BUTTON_ID_BACK, prv_back_click_handler); 486 | } 487 | 488 | /////////////////////////////////////////////////////////////////////////////////////////////////// 489 | //! API 490 | 491 | static Layer* selection_layer_init(SelectionLayerData *selection_layer_, GRect frame, int num_cells) { 492 | Layer *layer = layer_create_with_data(frame, sizeof(SelectionLayerData)); 493 | SelectionLayerData *selection_layer_data = layer_get_data(layer); 494 | 495 | if (num_cells > MAX_SELECTION_LAYER_CELLS) { 496 | num_cells = MAX_SELECTION_LAYER_CELLS; 497 | } 498 | 499 | // Set layer defaults 500 | *selection_layer_data = (SelectionLayerData) { 501 | .active_background_color = DEFAULT_ACTIVE_COLOR, 502 | .inactive_background_color = DEFAULT_INACTIVE_COLOR, 503 | .num_cells = num_cells, 504 | .cell_padding = DEFAULT_CELL_PADDING, 505 | .selected_cell_idx = DEFAULT_SELECTED_INDEX, 506 | .font = fonts_get_system_font(DEFAULT_FONT), 507 | .is_active = true, 508 | }; 509 | for (int i = 0; i < num_cells; i++) { 510 | selection_layer_data->cell_widths[i] = 0; 511 | } 512 | layer_set_frame(layer, frame); 513 | layer_set_clips(layer, false); 514 | layer_set_update_proc(layer, (LayerUpdateProc)prv_draw_selection_layer); 515 | 516 | return layer; 517 | } 518 | 519 | Layer* selection_layer_create(GRect frame, int num_cells) { 520 | SelectionLayerData *selection_layer_data = NULL; 521 | return selection_layer_init(selection_layer_data, frame, num_cells); 522 | } 523 | 524 | static void selection_layer_deinit(Layer* layer) { 525 | layer_destroy(layer); 526 | } 527 | 528 | void selection_layer_destroy(Layer* layer) { 529 | SelectionLayerData *data = layer_get_data(layer); 530 | 531 | animation_unschedule_all(); 532 | if (data) { 533 | selection_layer_deinit(layer); 534 | } 535 | } 536 | 537 | void selection_layer_set_cell_width(Layer *layer, int idx, int width) { 538 | SelectionLayerData *data = layer_get_data(layer); 539 | 540 | if (data && idx < data->num_cells) { 541 | data->cell_widths[idx] = width; 542 | } 543 | } 544 | 545 | void selection_layer_set_font(Layer *layer, GFont font) { 546 | SelectionLayerData *data = layer_get_data(layer); 547 | 548 | if (data) { 549 | data->font = font; 550 | } 551 | } 552 | 553 | void selection_layer_set_inactive_bg_color(Layer *layer, GColor color) { 554 | SelectionLayerData *data = layer_get_data(layer); 555 | 556 | if (data) { 557 | data->inactive_background_color = color; 558 | } 559 | } 560 | 561 | void selection_layer_set_active_bg_color(Layer *layer, GColor color) { 562 | SelectionLayerData *data = layer_get_data(layer); 563 | 564 | if (data) { 565 | data->active_background_color = color; 566 | } 567 | } 568 | 569 | void selection_layer_set_cell_padding(Layer *layer, int padding) { 570 | SelectionLayerData *data = layer_get_data(layer); 571 | 572 | if (data) { 573 | data->cell_padding = padding; 574 | } 575 | } 576 | 577 | void selection_layer_set_active(Layer *layer, bool is_active) { 578 | SelectionLayerData *data = layer_get_data(layer); 579 | 580 | if (data) { 581 | if (is_active && !data->is_active) { 582 | data->selected_cell_idx = 0; 583 | } if (!is_active && data->is_active) { 584 | data->selected_cell_idx = MAX_SELECTION_LAYER_CELLS + 1; 585 | } 586 | 587 | data->is_active = is_active; 588 | layer_mark_dirty(layer); 589 | } 590 | } 591 | 592 | void selection_layer_set_click_config_onto_window(Layer *layer, struct Window *window) { 593 | if (layer && window) { 594 | window_set_click_config_provider_with_context(window, (ClickConfigProvider)prv_click_config_provider, layer); 595 | } 596 | } 597 | 598 | void selection_layer_set_callbacks(Layer *layer, void *context, SelectionLayerCallbacks callbacks) { 599 | SelectionLayerData *data = layer_get_data(layer); 600 | data->callbacks = callbacks; 601 | data->context = context; 602 | } 603 | -------------------------------------------------------------------------------- /src/layers/selection_layer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MAX_SELECTION_LAYER_CELLS 3 6 | 7 | typedef char* (*SelectionLayerGetCellText)(int index, void *context); 8 | 9 | typedef void (*SelectionLayerCompleteCallback)(void *context); 10 | 11 | typedef void (*SelectionLayerIncrementCallback)(int selected_cell_idx, uint8_t reapeating_count, void *context); 12 | 13 | typedef void (*SelectionLayerDecrementCallback)(int selected_cell_idx, uint8_t reapeating_count, void *context); 14 | 15 | typedef struct SelectionLayerCallbacks { 16 | SelectionLayerGetCellText get_cell_text; 17 | SelectionLayerCompleteCallback complete; 18 | SelectionLayerIncrementCallback increment; 19 | SelectionLayerDecrementCallback decrement; 20 | } SelectionLayerCallbacks; 21 | 22 | typedef struct SelectionLayerData { 23 | int num_cells; 24 | int cell_widths[MAX_SELECTION_LAYER_CELLS]; 25 | int cell_padding; 26 | int selected_cell_idx; 27 | 28 | // If is_active = false the the selected cell will become invalid, and any clicks will be ignored 29 | bool is_active; 30 | 31 | GFont font; 32 | GColor inactive_background_color; 33 | GColor active_background_color; 34 | 35 | SelectionLayerCallbacks callbacks; 36 | void *context; 37 | 38 | // Animation stuff 39 | Animation *value_change_animation; 40 | bool bump_is_upwards; 41 | int bump_text_anim_progress; 42 | AnimationImplementation bump_text_impl; 43 | int bump_settle_anim_progress; 44 | AnimationImplementation bump_settle_anim_impl; 45 | 46 | Animation *next_cell_animation; 47 | bool slide_is_forward; 48 | int slide_amin_progress; 49 | AnimationImplementation slide_amin_impl; 50 | int slide_settle_anim_progress; 51 | AnimationImplementation slide_settle_anim_impl; 52 | } SelectionLayerData; 53 | 54 | Layer* selection_layer_create(GRect frame, int num_cells); 55 | 56 | void selection_layer_destroy(Layer* layer); 57 | 58 | void selection_layer_set_cell_width(Layer *layer, int cell_idx, int width); 59 | 60 | void selection_layer_set_font(Layer *layer, GFont font); 61 | 62 | void selection_layer_set_inactive_bg_color(Layer *layer, GColor color); 63 | 64 | void selection_layer_set_active_bg_color(Layer *layer, GColor color); 65 | 66 | void selection_layer_set_cell_padding(Layer *layer, int padding); 67 | 68 | // When transitioning from inactive -> active, the selected cell will be index 0 69 | void selection_layer_set_active(Layer *layer, bool is_active); 70 | 71 | void selection_layer_set_click_config_onto_window(Layer *layer, struct Window *window); 72 | 73 | void selection_layer_set_callbacks(Layer *layer, void *context, SelectionLayerCallbacks callbacks); 74 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "windows/checkbox_window.h" 4 | #include "windows/dialog_choice_window.h" 5 | #include "windows/dialog_message_window.h" 6 | #include "windows/list_message_window.h" 7 | #include "windows/radio_button_window.h" 8 | #include "windows/pin_window.h" 9 | #include "windows/text_animation_window.h" 10 | #include "windows/progress_bar_window.h" 11 | #include "windows/progress_layer_window.h" 12 | #include "windows/dialog_config_window.h" 13 | 14 | #define NUM_WINDOWS 10 15 | 16 | static Window *s_main_window; 17 | static MenuLayer *s_menu_layer; 18 | 19 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { 20 | return NUM_WINDOWS; 21 | } 22 | 23 | static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { 24 | switch(cell_index->row) { 25 | case 0: 26 | menu_cell_basic_draw(ctx, cell_layer, "Checkbox List", NULL, NULL); 27 | break; 28 | case 1: 29 | menu_cell_basic_draw(ctx, cell_layer, "Choice Dialog", NULL, NULL); 30 | break; 31 | case 2: 32 | menu_cell_basic_draw(ctx, cell_layer, "Message Dialog", NULL, NULL); 33 | break; 34 | case 3: 35 | menu_cell_basic_draw(ctx, cell_layer, "List Message", NULL, NULL); 36 | break; 37 | case 4: 38 | menu_cell_basic_draw(ctx, cell_layer, "Radio Button", NULL, NULL); 39 | break; 40 | case 5: 41 | menu_cell_basic_draw(ctx, cell_layer, "PIN Entry", NULL, NULL); 42 | break; 43 | case 6: 44 | menu_cell_basic_draw(ctx, cell_layer, "Text Animation", NULL, NULL); 45 | break; 46 | case 7: 47 | menu_cell_basic_draw(ctx, cell_layer, "Progress Bar", NULL, NULL); 48 | break; 49 | case 8: 50 | menu_cell_basic_draw(ctx, cell_layer, "Progress Layer", NULL, NULL); 51 | break; 52 | case 9: 53 | menu_cell_basic_draw(ctx, cell_layer, "App Config Prompt", NULL, NULL); 54 | break; 55 | default: 56 | break; 57 | } 58 | } 59 | 60 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 61 | return PBL_IF_ROUND_ELSE( 62 | menu_layer_is_index_selected(menu_layer, cell_index) ? 63 | MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT : MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT, 64 | CHECKBOX_WINDOW_CELL_HEIGHT); 65 | } 66 | 67 | static void pin_complete_callback(PIN pin, void *context) { 68 | APP_LOG(APP_LOG_LEVEL_INFO, "Pin was %d %d %d", pin.digits[0], pin.digits[1], pin.digits[2]); 69 | pin_window_pop((PinWindow*)context, true); 70 | } 71 | 72 | static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 73 | switch(cell_index->row) { 74 | case 0: 75 | checkbox_window_push(); 76 | break; 77 | case 1: 78 | dialog_choice_window_push(); 79 | break; 80 | case 2: 81 | dialog_message_window_push(); 82 | break; 83 | case 3: 84 | list_message_window_push(); 85 | break; 86 | case 4: 87 | radio_button_window_push(); 88 | break; 89 | case 5: { 90 | PinWindow *pin_window = pin_window_create((PinWindowCallbacks) { 91 | .pin_complete = pin_complete_callback 92 | }); 93 | pin_window_push(pin_window, true); 94 | } 95 | break; 96 | case 6: 97 | text_animation_window_push(); 98 | break; 99 | case 7: 100 | progress_bar_window_push(); 101 | break; 102 | case 8: 103 | progress_layer_window_push(); 104 | break; 105 | case 9: 106 | dialog_config_window_push(); 107 | break; 108 | default: 109 | break; 110 | } 111 | } 112 | 113 | static void window_load(Window *window) { 114 | Layer *window_layer = window_get_root_layer(window); 115 | GRect bounds = layer_get_bounds(window_layer); 116 | 117 | s_menu_layer = menu_layer_create(bounds); 118 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 119 | #if defined(PBL_COLOR) 120 | menu_layer_set_normal_colors(s_menu_layer, GColorBlack, GColorWhite); 121 | menu_layer_set_highlight_colors(s_menu_layer, GColorRed, GColorWhite); 122 | #endif 123 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 124 | .get_num_rows = get_num_rows_callback, 125 | .draw_row = draw_row_callback, 126 | .get_cell_height = get_cell_height_callback, 127 | .select_click = select_callback, 128 | }); 129 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 130 | } 131 | 132 | static void window_unload(Window *window) { 133 | menu_layer_destroy(s_menu_layer); 134 | } 135 | 136 | static void init() { 137 | s_main_window = window_create(); 138 | window_set_window_handlers(s_main_window, (WindowHandlers) { 139 | .load = window_load, 140 | .unload = window_unload, 141 | }); 142 | window_stack_push(s_main_window, true); 143 | } 144 | 145 | static void deinit() { 146 | window_destroy(s_main_window); 147 | } 148 | 149 | int main() { 150 | init(); 151 | app_event_loop(); 152 | deinit(); 153 | } 154 | -------------------------------------------------------------------------------- /src/windows/checkbox_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the checkbox list UI pattern. 3 | */ 4 | 5 | #include "checkbox_window.h" 6 | 7 | static Window *s_main_window; 8 | static MenuLayer *s_menu_layer; 9 | 10 | static GBitmap *s_tick_black_bitmap, *s_tick_white_bitmap; 11 | static bool s_selections[CHECKBOX_WINDOW_NUM_ROWS]; 12 | 13 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { 14 | return CHECKBOX_WINDOW_NUM_ROWS + 1; 15 | } 16 | 17 | static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { 18 | if(cell_index->row == CHECKBOX_WINDOW_NUM_ROWS) { 19 | // Submit item 20 | menu_cell_basic_draw(ctx, cell_layer, "Submit", NULL, NULL); 21 | } else { 22 | // Choice item 23 | static char s_buff[16]; 24 | snprintf(s_buff, sizeof(s_buff), "Choice %d", (int)cell_index->row); 25 | menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL); 26 | 27 | // Selected? 28 | GBitmap *ptr = s_tick_black_bitmap; 29 | if(menu_cell_layer_is_highlighted(cell_layer)) { 30 | graphics_context_set_stroke_color(ctx, GColorWhite); 31 | ptr = s_tick_white_bitmap; 32 | } 33 | 34 | GRect bounds = layer_get_bounds(cell_layer); 35 | GRect bitmap_bounds = gbitmap_get_bounds(ptr); 36 | 37 | // Draw checkbox 38 | GRect r = GRect( 39 | bounds.size.w - (2 * CHECKBOX_WINDOW_BOX_SIZE), 40 | (bounds.size.h / 2) - (CHECKBOX_WINDOW_BOX_SIZE / 2), 41 | CHECKBOX_WINDOW_BOX_SIZE, CHECKBOX_WINDOW_BOX_SIZE); 42 | graphics_draw_rect(ctx, r); 43 | if(s_selections[cell_index->row]) { 44 | graphics_context_set_compositing_mode(ctx, GCompOpSet); 45 | graphics_draw_bitmap_in_rect(ctx, ptr, GRect(r.origin.x, r.origin.y - 3, bitmap_bounds.size.w, bitmap_bounds.size.h)); 46 | } 47 | } 48 | } 49 | 50 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 51 | return PBL_IF_ROUND_ELSE( 52 | menu_layer_is_index_selected(menu_layer, cell_index) ? 53 | MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT : MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT, 54 | CHECKBOX_WINDOW_CELL_HEIGHT); 55 | } 56 | 57 | static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) { 58 | if(cell_index->row == CHECKBOX_WINDOW_NUM_ROWS) { 59 | // Do something with choices made 60 | for(int i = 0; i < CHECKBOX_WINDOW_NUM_ROWS; i++) { 61 | APP_LOG(APP_LOG_LEVEL_INFO, "Option %d was %s", i, (s_selections[i] ? "selected" : "not selected")); 62 | } 63 | window_stack_pop(true); 64 | } else { 65 | // Check/uncheck 66 | int row = cell_index->row; 67 | s_selections[row] = !s_selections[row]; 68 | menu_layer_reload_data(menu_layer); 69 | } 70 | } 71 | 72 | static void window_load(Window *window) { 73 | Layer *window_layer = window_get_root_layer(window); 74 | GRect bounds = layer_get_bounds(window_layer); 75 | 76 | s_tick_black_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_BLACK); 77 | s_tick_white_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_WHITE); 78 | 79 | s_menu_layer = menu_layer_create(bounds); 80 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 81 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 82 | .get_num_rows = get_num_rows_callback, 83 | .draw_row = draw_row_callback, 84 | .get_cell_height = get_cell_height_callback, 85 | .select_click = select_callback, 86 | }); 87 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 88 | } 89 | 90 | static void window_unload(Window *window) { 91 | menu_layer_destroy(s_menu_layer); 92 | 93 | gbitmap_destroy(s_tick_black_bitmap); 94 | gbitmap_destroy(s_tick_white_bitmap); 95 | 96 | window_destroy(window); 97 | s_main_window = NULL; 98 | } 99 | 100 | void checkbox_window_push() { 101 | if(!s_main_window) { 102 | s_main_window = window_create(); 103 | window_set_window_handlers(s_main_window, (WindowHandlers) { 104 | .load = window_load, 105 | .unload = window_unload, 106 | }); 107 | } 108 | window_stack_push(s_main_window, true); 109 | } 110 | -------------------------------------------------------------------------------- /src/windows/checkbox_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define CHECKBOX_WINDOW_NUM_ROWS 4 6 | #define CHECKBOX_WINDOW_BOX_SIZE 12 7 | #define CHECKBOX_WINDOW_CELL_HEIGHT 44 8 | 9 | void checkbox_window_push(); 10 | -------------------------------------------------------------------------------- /src/windows/dialog_choice_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the dialog choice UI pattern. 3 | */ 4 | 5 | #include "dialog_choice_window.h" 6 | 7 | static Window *s_main_window; 8 | static TextLayer *s_label_layer; 9 | static BitmapLayer *s_icon_layer; 10 | static ActionBarLayer *s_action_bar_layer; 11 | 12 | static GBitmap *s_icon_bitmap, *s_tick_bitmap, *s_cross_bitmap; 13 | 14 | static void window_load(Window *window) { 15 | Layer *window_layer = window_get_root_layer(window); 16 | GRect bounds = layer_get_bounds(window_layer); 17 | 18 | s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CONFIRM); 19 | 20 | const GEdgeInsets icon_insets = {.top = 7, .right = 28, .bottom = 56, .left = 14}; 21 | s_icon_layer = bitmap_layer_create(grect_inset(bounds, icon_insets)); 22 | bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap); 23 | bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet); 24 | layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer)); 25 | 26 | const GEdgeInsets label_insets = {.top = 112, .right = ACTION_BAR_WIDTH, .left = ACTION_BAR_WIDTH / 2}; 27 | s_label_layer = text_layer_create(grect_inset(bounds, label_insets)); 28 | text_layer_set_text(s_label_layer, DIALOG_CHOICE_WINDOW_MESSAGE); 29 | text_layer_set_background_color(s_label_layer, GColorClear); 30 | text_layer_set_text_alignment(s_label_layer, GTextAlignmentCenter); 31 | text_layer_set_font(s_label_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 32 | layer_add_child(window_layer, text_layer_get_layer(s_label_layer)); 33 | 34 | s_tick_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK); 35 | s_cross_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CROSS); 36 | 37 | s_action_bar_layer = action_bar_layer_create(); 38 | action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_UP, s_tick_bitmap); 39 | action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_DOWN, s_cross_bitmap); 40 | action_bar_layer_add_to_window(s_action_bar_layer, window); 41 | } 42 | 43 | static void window_unload(Window *window) { 44 | text_layer_destroy(s_label_layer); 45 | action_bar_layer_destroy(s_action_bar_layer); 46 | bitmap_layer_destroy(s_icon_layer); 47 | 48 | gbitmap_destroy(s_icon_bitmap); 49 | gbitmap_destroy(s_tick_bitmap); 50 | gbitmap_destroy(s_cross_bitmap); 51 | 52 | window_destroy(window); 53 | s_main_window = NULL; 54 | } 55 | 56 | void dialog_choice_window_push() { 57 | if(!s_main_window) { 58 | s_main_window = window_create(); 59 | window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorJaegerGreen, GColorWhite)); 60 | window_set_window_handlers(s_main_window, (WindowHandlers) { 61 | .load = window_load, 62 | .unload = window_unload, 63 | }); 64 | } 65 | window_stack_push(s_main_window, true); 66 | } 67 | -------------------------------------------------------------------------------- /src/windows/dialog_choice_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DIALOG_CHOICE_WINDOW_MESSAGE "Set as default?" 6 | 7 | void dialog_choice_window_push(); 8 | -------------------------------------------------------------------------------- /src/windows/dialog_config_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the 'config required' dialog UI pattern. 3 | */ 4 | 5 | #include "windows/dialog_config_window.h" 6 | 7 | static Window *s_main_window; 8 | static TextLayer *s_body_layer, *s_title_layer; 9 | static BitmapLayer *s_icon_layer; 10 | 11 | static GBitmap *s_icon_bitmap; 12 | 13 | static void window_load(Window *window) { 14 | Layer *window_layer = window_get_root_layer(window); 15 | GRect bounds = layer_get_bounds(window_layer); 16 | 17 | s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_CONFIG_REQUIRED); 18 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap); 19 | 20 | const GEdgeInsets icon_insets = GEdgeInsets( 21 | (bounds.size.h - bitmap_bounds.size.h) / 2, 22 | (bounds.size.w - bitmap_bounds.size.w) / 2); 23 | s_icon_layer = bitmap_layer_create(grect_inset(bounds, icon_insets)); 24 | bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap); 25 | bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet); 26 | layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer)); 27 | 28 | const GEdgeInsets title_insets = {.top = 10}; 29 | s_title_layer = text_layer_create(grect_inset(bounds, title_insets)); 30 | text_layer_set_text(s_title_layer, DIALOG_CONFIG_WINDOW_APP_NAME); 31 | text_layer_set_text_color(s_title_layer, PBL_IF_COLOR_ELSE(GColorWhite, GColorBlack)); 32 | text_layer_set_background_color(s_title_layer, GColorClear); 33 | text_layer_set_text_alignment(s_title_layer, GTextAlignmentCenter); 34 | text_layer_set_font(s_title_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 35 | layer_add_child(window_layer, text_layer_get_layer(s_title_layer)); 36 | 37 | const GEdgeInsets body_insets = {.top = 125, .right = 5, .left = 5}; 38 | s_body_layer = text_layer_create(grect_inset(bounds, body_insets)); 39 | text_layer_set_text(s_body_layer, DIALOG_CONFIG_WINDOW_MESSAGE); 40 | text_layer_set_text_color(s_body_layer, PBL_IF_COLOR_ELSE(GColorWhite, GColorBlack)); 41 | text_layer_set_background_color(s_body_layer, GColorClear); 42 | text_layer_set_font(s_body_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 43 | text_layer_set_text_alignment(s_body_layer, GTextAlignmentCenter); 44 | layer_add_child(window_layer, text_layer_get_layer(s_body_layer)); 45 | } 46 | 47 | static void window_unload(Window *window) { 48 | text_layer_destroy(s_title_layer); 49 | text_layer_destroy(s_body_layer); 50 | 51 | bitmap_layer_destroy(s_icon_layer); 52 | gbitmap_destroy(s_icon_bitmap); 53 | 54 | window_destroy(window); 55 | s_main_window = NULL; 56 | } 57 | 58 | void dialog_config_window_push() { 59 | if(!s_main_window) { 60 | s_main_window = window_create(); 61 | window_set_background_color(s_main_window, PBL_IF_COLOR_ELSE(GColorDarkGray, GColorWhite)); 62 | window_set_window_handlers(s_main_window, (WindowHandlers) { 63 | .load = window_load, 64 | .unload = window_unload 65 | }); 66 | } 67 | window_stack_push(s_main_window, true); 68 | } 69 | -------------------------------------------------------------------------------- /src/windows/dialog_config_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DIALOG_CONFIG_WINDOW_APP_NAME "Example App" 6 | #define DIALOG_CONFIG_WINDOW_MESSAGE "Set up in the\nPebble app" 7 | 8 | void dialog_config_window_push(); 9 | -------------------------------------------------------------------------------- /src/windows/dialog_message_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the dialog message UI pattern. 3 | */ 4 | 5 | #include "windows/dialog_message_window.h" 6 | 7 | static Window *s_main_window; 8 | static TextLayer *s_label_layer; 9 | static Layer *s_background_layer, *s_icon_layer; 10 | 11 | static Animation *s_appear_anim = NULL; 12 | 13 | static GBitmap *s_icon_bitmap; 14 | 15 | static void anim_stopped_handler(Animation *animation, bool finished, void *context) { 16 | s_appear_anim = NULL; 17 | } 18 | 19 | static void background_update_proc(Layer *layer, GContext *ctx) { 20 | graphics_context_set_fill_color(ctx, PBL_IF_COLOR_ELSE(GColorYellow, GColorWhite)); 21 | graphics_fill_rect(ctx, layer_get_bounds(layer), 0, 0); 22 | } 23 | 24 | static void icon_update_proc(Layer *layer, GContext *ctx) { 25 | GRect bounds = layer_get_bounds(layer); 26 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap); 27 | graphics_context_set_compositing_mode(ctx, GCompOpSet); 28 | graphics_draw_bitmap_in_rect(ctx, s_icon_bitmap, (GRect){.origin = bounds.origin, .size = bitmap_bounds.size}); 29 | } 30 | 31 | static void window_load(Window *window) { 32 | Layer *window_layer = window_get_root_layer(window); 33 | GRect bounds = layer_get_bounds(window_layer); 34 | 35 | const GEdgeInsets background_insets = {.top = bounds.size.h /* Start hidden */}; 36 | s_background_layer = layer_create(grect_inset(bounds, background_insets)); 37 | layer_set_update_proc(s_background_layer, background_update_proc); 38 | layer_add_child(window_layer, s_background_layer); 39 | 40 | s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_WARNING); 41 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap); 42 | 43 | s_icon_layer = layer_create(PBL_IF_ROUND_ELSE( 44 | GRect((bounds.size.w - bitmap_bounds.size.w) / 2, bounds.size.h + DIALOG_MESSAGE_WINDOW_MARGIN, bitmap_bounds.size.w, bitmap_bounds.size.h), 45 | GRect(DIALOG_MESSAGE_WINDOW_MARGIN, bounds.size.h + DIALOG_MESSAGE_WINDOW_MARGIN, bitmap_bounds.size.w, bitmap_bounds.size.h) 46 | )); 47 | layer_set_update_proc(s_icon_layer, icon_update_proc); 48 | layer_add_child(window_layer, s_icon_layer); 49 | 50 | s_label_layer = text_layer_create(GRect(DIALOG_MESSAGE_WINDOW_MARGIN, bounds.size.h + DIALOG_MESSAGE_WINDOW_MARGIN + bitmap_bounds.size.h, bounds.size.w - (2 * DIALOG_MESSAGE_WINDOW_MARGIN), bounds.size.h)); 51 | text_layer_set_text(s_label_layer, DIALOG_MESSAGE_WINDOW_MESSAGE); 52 | text_layer_set_background_color(s_label_layer, GColorClear); 53 | text_layer_set_text_alignment(s_label_layer, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft)); 54 | text_layer_set_font(s_label_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 55 | layer_add_child(window_layer, text_layer_get_layer(s_label_layer)); 56 | } 57 | 58 | static void window_unload(Window *window) { 59 | layer_destroy(s_background_layer); 60 | layer_destroy(s_icon_layer); 61 | 62 | text_layer_destroy(s_label_layer); 63 | 64 | gbitmap_destroy(s_icon_bitmap); 65 | 66 | window_destroy(window); 67 | s_main_window = NULL; 68 | } 69 | 70 | static void window_appear(Window *window) { 71 | if(s_appear_anim) { 72 | // In progress, cancel 73 | animation_unschedule(s_appear_anim); 74 | } 75 | 76 | GRect bounds = layer_get_bounds(window_get_root_layer(window)); 77 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap); 78 | 79 | Layer *label_layer = text_layer_get_layer(s_label_layer); 80 | 81 | GRect start = layer_get_frame(s_background_layer); 82 | GRect finish = bounds; 83 | Animation *background_anim = (Animation*)property_animation_create_layer_frame(s_background_layer, &start, &finish); 84 | 85 | start = layer_get_frame(s_icon_layer); 86 | const GEdgeInsets icon_insets = { 87 | .top = DIALOG_MESSAGE_WINDOW_MARGIN, 88 | .left = PBL_IF_ROUND_ELSE((bounds.size.w - bitmap_bounds.size.w) / 2, DIALOG_MESSAGE_WINDOW_MARGIN)}; 89 | finish = grect_inset(bounds, icon_insets); 90 | Animation *icon_anim = (Animation*)property_animation_create_layer_frame(s_icon_layer, &start, &finish); 91 | 92 | start = layer_get_frame(label_layer); 93 | const GEdgeInsets finish_insets = { 94 | .top = DIALOG_MESSAGE_WINDOW_MARGIN + bitmap_bounds.size.h + 5 /* small adjustment */, 95 | .right = DIALOG_MESSAGE_WINDOW_MARGIN, .left = DIALOG_MESSAGE_WINDOW_MARGIN}; 96 | finish = grect_inset(bounds, finish_insets); 97 | Animation *label_anim = (Animation*)property_animation_create_layer_frame(label_layer, &start, &finish); 98 | 99 | s_appear_anim = animation_spawn_create(background_anim, icon_anim, label_anim, NULL); 100 | animation_set_handlers(s_appear_anim, (AnimationHandlers) { 101 | .stopped = anim_stopped_handler 102 | }, NULL); 103 | animation_set_delay(s_appear_anim, 700); 104 | animation_schedule(s_appear_anim); 105 | } 106 | 107 | void dialog_message_window_push() { 108 | if(!s_main_window) { 109 | s_main_window = window_create(); 110 | window_set_background_color(s_main_window, GColorBlack); 111 | window_set_window_handlers(s_main_window, (WindowHandlers) { 112 | .load = window_load, 113 | .unload = window_unload, 114 | .appear = window_appear 115 | }); 116 | } 117 | window_stack_push(s_main_window, true); 118 | } 119 | -------------------------------------------------------------------------------- /src/windows/dialog_message_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define DIALOG_MESSAGE_WINDOW_MESSAGE "Battery is low! Connect the charger." 6 | #define DIALOG_MESSAGE_WINDOW_MARGIN 10 7 | 8 | void dialog_message_window_push(); 9 | -------------------------------------------------------------------------------- /src/windows/list_message_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the list menu UI pattern. 3 | */ 4 | 5 | #include "list_message_window.h" 6 | 7 | static Window *s_main_window; 8 | static MenuLayer *s_menu_layer; 9 | static TextLayer *s_list_message_layer; 10 | 11 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { 12 | return LIST_MESSAGE_WINDOW_NUM_ROWS; 13 | } 14 | 15 | static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { 16 | static char s_buff[16]; 17 | snprintf(s_buff, sizeof(s_buff), "Item %d", (int)cell_index->row); 18 | menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL); 19 | } 20 | 21 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 22 | return PBL_IF_ROUND_ELSE( 23 | menu_layer_is_index_selected(menu_layer, cell_index) ? 24 | MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT : MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT, 25 | LIST_MESSAGE_WINDOW_CELL_HEIGHT); 26 | } 27 | 28 | static void window_load(Window *window) { 29 | Layer *window_layer = window_get_root_layer(window); 30 | GRect bounds = layer_get_bounds(window_layer); 31 | 32 | s_menu_layer = menu_layer_create(GRect(bounds.origin.x, bounds.origin.y, bounds.size.w, LIST_MESSAGE_WINDOW_MENU_HEIGHT)); 33 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 34 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 35 | .get_num_rows = get_num_rows_callback, 36 | .draw_row = draw_row_callback, 37 | .get_cell_height = get_cell_height_callback, 38 | }); 39 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 40 | 41 | const GEdgeInsets message_insets = {.top = 140}; 42 | s_list_message_layer = text_layer_create(grect_inset(bounds, message_insets)); 43 | text_layer_set_text_alignment(s_list_message_layer, GTextAlignmentCenter); 44 | text_layer_set_text(s_list_message_layer, LIST_MESSAGE_WINDOW_HINT_TEXT); 45 | layer_add_child(window_layer, text_layer_get_layer(s_list_message_layer)); 46 | } 47 | 48 | static void window_unload(Window *window) { 49 | menu_layer_destroy(s_menu_layer); 50 | text_layer_destroy(s_list_message_layer); 51 | 52 | window_destroy(window); 53 | s_main_window = NULL; 54 | } 55 | 56 | void list_message_window_push() { 57 | if(!s_main_window) { 58 | s_main_window = window_create(); 59 | window_set_window_handlers(s_main_window, (WindowHandlers) { 60 | .load = window_load, 61 | .unload = window_unload, 62 | }); 63 | } 64 | window_stack_push(s_main_window, true); 65 | } 66 | -------------------------------------------------------------------------------- /src/windows/list_message_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define LIST_MESSAGE_WINDOW_NUM_ROWS 5 6 | #define LIST_MESSAGE_WINDOW_CELL_HEIGHT 30 7 | #define LIST_MESSAGE_WINDOW_MENU_HEIGHT \ 8 | LIST_MESSAGE_WINDOW_NUM_ROWS * LIST_MESSAGE_WINDOW_CELL_HEIGHT 9 | #define LIST_MESSAGE_WINDOW_HINT_TEXT "Your list items" 10 | 11 | void list_message_window_push(); 12 | -------------------------------------------------------------------------------- /src/windows/pin_window.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pin_window.h" 3 | #include "../layers/selection_layer.h" 4 | 5 | static char* selection_handle_get_text(int index, void *context) { 6 | PinWindow *pin_window = (PinWindow*)context; 7 | snprintf( 8 | pin_window->field_buffs[index], 9 | sizeof(pin_window->field_buffs[0]), "%d", 10 | (int)pin_window->pin.digits[index] 11 | ); 12 | return pin_window->field_buffs[index]; 13 | } 14 | 15 | static void selection_handle_complete(void *context) { 16 | PinWindow *pin_window = (PinWindow*)context; 17 | pin_window->callbacks.pin_complete(pin_window->pin, pin_window); 18 | } 19 | 20 | static void selection_handle_inc(int index, uint8_t clicks, void *context) { 21 | PinWindow *pin_window = (PinWindow*)context; 22 | pin_window->pin.digits[index]++; 23 | if(pin_window->pin.digits[index] > PIN_WINDOW_MAX_VALUE) { 24 | pin_window->pin.digits[index] = 0; 25 | } 26 | } 27 | 28 | static void selection_handle_dec(int index, uint8_t clicks, void *context) { 29 | PinWindow *pin_window = (PinWindow*)context; 30 | pin_window->pin.digits[index]--; 31 | if(pin_window->pin.digits[index] < 0) { 32 | pin_window->pin.digits[index] = PIN_WINDOW_MAX_VALUE; 33 | } 34 | } 35 | 36 | PinWindow* pin_window_create(PinWindowCallbacks callbacks) { 37 | PinWindow *pin_window = (PinWindow*)malloc(sizeof(PinWindow)); 38 | if (pin_window) { 39 | pin_window->window = window_create(); 40 | pin_window->callbacks = callbacks; 41 | if (pin_window->window) { 42 | pin_window->field_selection = 0; 43 | for(int i = 0; i < PIN_WINDOW_NUM_CELLS; i++) { 44 | pin_window->pin.digits[i] = 0; 45 | } 46 | 47 | // Get window parameters 48 | Layer *window_layer = window_get_root_layer(pin_window->window); 49 | GRect bounds = layer_get_bounds(window_layer); 50 | 51 | // Main TextLayer 52 | const GEdgeInsets main_text_insets = {.top = 30}; 53 | pin_window->main_text = text_layer_create(grect_inset(bounds, main_text_insets)); 54 | text_layer_set_text(pin_window->main_text, "PIN Required"); 55 | text_layer_set_font(pin_window->main_text, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 56 | text_layer_set_text_alignment(pin_window->main_text, GTextAlignmentCenter); 57 | layer_add_child(window_layer, text_layer_get_layer(pin_window->main_text)); 58 | 59 | // Sub TextLayer 60 | const GEdgeInsets sub_text_insets = {.top = 115, .right = 5, .bottom = 10, .left = 5}; 61 | pin_window->sub_text = text_layer_create(grect_inset(bounds, sub_text_insets)); 62 | text_layer_set_text(pin_window->sub_text, "Enter your PIN to continue"); 63 | text_layer_set_text_alignment(pin_window->sub_text, GTextAlignmentCenter); 64 | text_layer_set_font(pin_window->sub_text, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); 65 | layer_add_child(window_layer, text_layer_get_layer(pin_window->sub_text)); 66 | 67 | // Create selection layer 68 | const GEdgeInsets selection_insets = GEdgeInsets( 69 | (bounds.size.h - PIN_WINDOW_SIZE.h) / 2, 70 | (bounds.size.w - PIN_WINDOW_SIZE.w) / 2); 71 | pin_window->selection = selection_layer_create(grect_inset(bounds, selection_insets), PIN_WINDOW_NUM_CELLS); 72 | for (int i = 0; i < PIN_WINDOW_NUM_CELLS; i++) { 73 | selection_layer_set_cell_width(pin_window->selection, i, 40); 74 | } 75 | selection_layer_set_cell_padding(pin_window->selection, 4); 76 | selection_layer_set_active_bg_color(pin_window->selection, GColorRed); 77 | selection_layer_set_inactive_bg_color(pin_window->selection, GColorDarkGray); 78 | selection_layer_set_click_config_onto_window(pin_window->selection, pin_window->window); 79 | selection_layer_set_callbacks(pin_window->selection, pin_window, (SelectionLayerCallbacks) { 80 | .get_cell_text = selection_handle_get_text, 81 | .complete = selection_handle_complete, 82 | .increment = selection_handle_inc, 83 | .decrement = selection_handle_dec, 84 | }); 85 | layer_add_child(window_get_root_layer(pin_window->window), pin_window->selection); 86 | 87 | // Create status bar 88 | pin_window->status = status_bar_layer_create(); 89 | status_bar_layer_set_colors(pin_window->status, GColorClear, GColorBlack); 90 | layer_add_child(window_layer, status_bar_layer_get_layer(pin_window->status)); 91 | return pin_window; 92 | } 93 | } 94 | 95 | APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to create PinWindow"); 96 | return NULL; 97 | } 98 | 99 | void pin_window_destroy(PinWindow *pin_window) { 100 | if (pin_window) { 101 | status_bar_layer_destroy(pin_window->status); 102 | selection_layer_destroy(pin_window->selection); 103 | text_layer_destroy(pin_window->sub_text); 104 | text_layer_destroy(pin_window->main_text); 105 | free(pin_window); 106 | pin_window = NULL; 107 | return; 108 | } 109 | } 110 | 111 | void pin_window_push(PinWindow *pin_window, bool animated) { 112 | window_stack_push(pin_window->window, animated); 113 | } 114 | 115 | void pin_window_pop(PinWindow *pin_window, bool animated) { 116 | window_stack_remove(pin_window->window, animated); 117 | } 118 | 119 | bool pin_window_get_topmost_window(PinWindow *pin_window) { 120 | return window_stack_get_top_window() == pin_window->window; 121 | } 122 | 123 | void pin_window_set_highlight_color(PinWindow *pin_window, GColor color) { 124 | pin_window->highlight_color = color; 125 | selection_layer_set_active_bg_color(pin_window->selection, color); 126 | } 127 | -------------------------------------------------------------------------------- /src/windows/pin_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PIN_WINDOW_NUM_CELLS 3 6 | #define PIN_WINDOW_MAX_VALUE 9 7 | #define PIN_WINDOW_SIZE GSize(128, 34) 8 | 9 | typedef struct { 10 | int digits[PIN_WINDOW_NUM_CELLS]; 11 | } PIN; 12 | 13 | typedef void (*PinWindowComplete)(PIN pin, void *context); 14 | 15 | typedef struct PinWindowCallbacks { 16 | PinWindowComplete pin_complete; 17 | } PinWindowCallbacks; 18 | 19 | typedef struct { 20 | Window *window; 21 | TextLayer *main_text, *sub_text; 22 | Layer *selection; 23 | GColor highlight_color; 24 | StatusBarLayer *status; 25 | PinWindowCallbacks callbacks; 26 | 27 | PIN pin; 28 | char field_buffs[PIN_WINDOW_NUM_CELLS][2]; 29 | int8_t field_selection; 30 | } PinWindow; 31 | 32 | /* 33 | * Creates a new PinWindow in memory but does not push it into view 34 | * pin_window_callbacks: callbacks for communication 35 | * returns: a pointer to a new PinWindow structure 36 | */ 37 | PinWindow* pin_window_create(PinWindowCallbacks pin_window_callbacks); 38 | 39 | /* 40 | * Destroys an existing PinWindow 41 | * pin_window: a pointer to the PinWindow being destroyed 42 | */ 43 | void pin_window_destroy(PinWindow *pin_window); 44 | 45 | /* 46 | * Push the window onto the stack 47 | * pin_window: a pointer to the PinWindow being pushed 48 | * animated: whether to animate the push or not 49 | */ 50 | void pin_window_push(PinWindow *pin_window, bool animated); 51 | 52 | /* 53 | * Pop the window off the stack 54 | * pin_window: a pointer to the PinWindow to pop 55 | * animated: whether to animate the pop or not 56 | */ 57 | void pin_window_pop(PinWindow *pin_window, bool animated); 58 | 59 | /* 60 | * Gets whether it is the topmost window or not 61 | * pin_window: a pointer to the PinWindow being checked 62 | * returns: a boolean indicating if it is the topmost window 63 | */ 64 | bool pin_window_get_topmost_window(PinWindow *pin_window); 65 | 66 | /* 67 | * Sets the over-all color scheme of the window 68 | * color: the GColor to set the highlight to 69 | */ 70 | void pin_window_set_highlight_color(PinWindow *pin_window, GColor color); 71 | -------------------------------------------------------------------------------- /src/windows/progress_bar_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example progress bar implementation. 3 | */ 4 | 5 | #include "progress_bar_window.h" 6 | 7 | static Window *s_window; 8 | static Layer *s_progress_bar; 9 | static StatusBarLayer *s_status_bar; 10 | 11 | static AppTimer *s_timer; 12 | static int s_progress = 0; // 0 - 100 13 | 14 | static void progress_callback(void *context); 15 | 16 | static void progress_bar_proc(Layer *layer, GContext *ctx) { 17 | GRect bounds = layer_get_bounds(layer); 18 | 19 | int width = (int)(float)(((float)s_progress / 100.0F) * bounds.size.w); 20 | graphics_context_set_stroke_color(ctx, GColorWhite); 21 | graphics_draw_line(ctx, GPointZero, GPoint(width, 0)); 22 | } 23 | 24 | static void next_timer() { 25 | s_timer = app_timer_register(PROGRESS_BAR_WINDOW_DELTA, progress_callback, NULL); 26 | } 27 | 28 | static void progress_callback(void *context) { 29 | s_progress += (s_progress < 100) ? 1 : -100; 30 | layer_mark_dirty(s_progress_bar); 31 | next_timer(); 32 | } 33 | 34 | static void window_appear(Window *window) { 35 | s_progress = 0; 36 | next_timer(); 37 | } 38 | 39 | static void window_load(Window *window) { 40 | Layer *window_layer = window_get_root_layer(window); 41 | 42 | s_status_bar = status_bar_layer_create(); 43 | status_bar_layer_set_separator_mode(s_status_bar, StatusBarLayerSeparatorModeDotted); 44 | status_bar_layer_set_colors(s_status_bar, GColorClear, GColorWhite); 45 | layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar)); 46 | 47 | s_progress_bar = layer_create((GRect){ 48 | .origin = GPoint(0, STATUS_BAR_LAYER_HEIGHT - 2), 49 | .size = PROGRESS_BAR_WINDOW_SIZE 50 | }); 51 | layer_set_update_proc(s_progress_bar, progress_bar_proc); 52 | layer_add_child(window_layer, s_progress_bar); 53 | } 54 | 55 | static void window_unload(Window *window) { 56 | layer_destroy(s_progress_bar); 57 | status_bar_layer_destroy(s_status_bar); 58 | window_destroy(s_window); 59 | s_window = NULL; 60 | } 61 | 62 | static void window_disappear(Window *window) { 63 | if(s_timer) { 64 | app_timer_cancel(s_timer); 65 | s_timer = NULL; 66 | } 67 | } 68 | 69 | void progress_bar_window_push() { 70 | if(!s_window) { 71 | s_window = window_create(); 72 | window_set_background_color(s_window, PBL_IF_COLOR_ELSE(GColorDarkCandyAppleRed, GColorBlack)); 73 | window_set_window_handlers(s_window, (WindowHandlers) { 74 | .appear = window_appear, 75 | .load = window_load, 76 | .unload = window_unload, 77 | .disappear = window_disappear 78 | }); 79 | } 80 | window_stack_push(s_window, true); 81 | } 82 | -------------------------------------------------------------------------------- /src/windows/progress_bar_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PROGRESS_BAR_WINDOW_SIZE GSize(144, 1) // System default 6 | #define PROGRESS_BAR_WINDOW_DELTA 33 7 | 8 | void progress_bar_window_push(); -------------------------------------------------------------------------------- /src/windows/progress_layer_window.c: -------------------------------------------------------------------------------- 1 | #include "progress_layer_window.h" 2 | 3 | static Window *s_window; 4 | static ProgressLayer *s_progress_layer; 5 | 6 | static AppTimer *s_timer; 7 | static int s_progress; 8 | 9 | static void progress_callback(void *context); 10 | 11 | static void next_timer() { 12 | s_timer = app_timer_register(PROGRESS_LAYER_WINDOW_DELTA, progress_callback, NULL); 13 | } 14 | 15 | static void progress_callback(void *context) { 16 | s_progress += (s_progress < 100) ? 1 : -100; 17 | progress_layer_set_progress(s_progress_layer, s_progress); 18 | next_timer(); 19 | } 20 | 21 | static void window_load(Window *window) { 22 | Layer *window_layer = window_get_root_layer(window); 23 | GRect bounds = layer_get_bounds(window_layer); 24 | 25 | s_progress_layer = progress_layer_create(GRect((bounds.size.w - PROGRESS_LAYER_WINDOW_WIDTH) / 2, 80, PROGRESS_LAYER_WINDOW_WIDTH, 6)); 26 | progress_layer_set_progress(s_progress_layer, 0); 27 | progress_layer_set_corner_radius(s_progress_layer, 2); 28 | progress_layer_set_foreground_color(s_progress_layer, GColorWhite); 29 | progress_layer_set_background_color(s_progress_layer, GColorBlack); 30 | layer_add_child(window_layer, s_progress_layer); 31 | } 32 | 33 | static void window_unload(Window *window) { 34 | progress_layer_destroy(s_progress_layer); 35 | 36 | window_destroy(window); 37 | s_window = NULL; 38 | } 39 | 40 | static void window_appear(Window *window) { 41 | s_progress = 0; 42 | next_timer(); 43 | } 44 | 45 | static void window_disappear(Window *window) { 46 | if(s_timer) { 47 | app_timer_cancel(s_timer); 48 | s_timer = NULL; 49 | } 50 | } 51 | 52 | void progress_layer_window_push() { 53 | if(!s_window) { 54 | s_window = window_create(); 55 | window_set_background_color(s_window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite)); 56 | window_set_window_handlers(s_window, (WindowHandlers) { 57 | .load = window_load, 58 | .appear = window_appear, 59 | .disappear = window_disappear, 60 | .unload = window_unload 61 | }); 62 | } 63 | window_stack_push(s_window, true); 64 | } -------------------------------------------------------------------------------- /src/windows/progress_layer_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../layers/progress_layer.h" 6 | 7 | #define PROGRESS_LAYER_WINDOW_DELTA 33 8 | #define PROGRESS_LAYER_WINDOW_WIDTH 80 9 | 10 | void progress_layer_window_push(); 11 | -------------------------------------------------------------------------------- /src/windows/radio_button_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example implementation of the radio button list UI pattern. 3 | */ 4 | 5 | #include "radio_button_window.h" 6 | 7 | static Window *s_main_window; 8 | static MenuLayer *s_menu_layer; 9 | 10 | static int s_current_selection = 0; 11 | 12 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { 13 | return RADIO_BUTTON_WINDOW_NUM_ROWS + 1; 14 | } 15 | 16 | static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { 17 | if(cell_index->row == RADIO_BUTTON_WINDOW_NUM_ROWS) { 18 | // This is the submit item 19 | menu_cell_basic_draw(ctx, cell_layer, "Submit", NULL, NULL); 20 | } else { 21 | // This is a choice item 22 | static char s_buff[16]; 23 | snprintf(s_buff, sizeof(s_buff), "Choice %d", (int)cell_index->row); 24 | menu_cell_basic_draw(ctx, cell_layer, s_buff, NULL, NULL); 25 | 26 | GRect bounds = layer_get_bounds(cell_layer); 27 | GPoint p = GPoint(bounds.size.w - (3 * RADIO_BUTTON_WINDOW_RADIO_RADIUS), (bounds.size.h / 2)); 28 | 29 | // Selected? 30 | if(menu_cell_layer_is_highlighted(cell_layer)) { 31 | graphics_context_set_stroke_color(ctx, GColorWhite); 32 | graphics_context_set_fill_color(ctx, GColorWhite); 33 | } else { 34 | graphics_context_set_fill_color(ctx, GColorBlack); 35 | } 36 | 37 | // Draw radio filled/empty 38 | graphics_draw_circle(ctx, p, RADIO_BUTTON_WINDOW_RADIO_RADIUS); 39 | if(cell_index->row == s_current_selection) { 40 | // This is the selection 41 | graphics_fill_circle(ctx, p, RADIO_BUTTON_WINDOW_RADIO_RADIUS - 3); 42 | } 43 | } 44 | } 45 | 46 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 47 | return PBL_IF_ROUND_ELSE( 48 | menu_layer_is_index_selected(menu_layer, cell_index) ? 49 | MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT : MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT, 50 | 44); 51 | } 52 | 53 | static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) { 54 | if(cell_index->row == RADIO_BUTTON_WINDOW_NUM_ROWS) { 55 | // Do something with user choice 56 | APP_LOG(APP_LOG_LEVEL_INFO, "Submitted choice %d", s_current_selection); 57 | window_stack_pop(true); 58 | } else { 59 | // Change selection 60 | s_current_selection = cell_index->row; 61 | menu_layer_reload_data(menu_layer); 62 | } 63 | } 64 | 65 | static void window_load(Window *window) { 66 | Layer *window_layer = window_get_root_layer(window); 67 | GRect bounds = layer_get_bounds(window_layer); 68 | 69 | s_menu_layer = menu_layer_create(bounds); 70 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 71 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 72 | .get_num_rows = get_num_rows_callback, 73 | .draw_row = draw_row_callback, 74 | .get_cell_height = get_cell_height_callback, 75 | .select_click = select_callback, 76 | }); 77 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 78 | } 79 | 80 | static void window_unload(Window *window) { 81 | menu_layer_destroy(s_menu_layer); 82 | 83 | window_destroy(window); 84 | s_main_window = NULL; 85 | } 86 | 87 | void radio_button_window_push() { 88 | if(!s_main_window) { 89 | s_main_window = window_create(); 90 | window_set_window_handlers(s_main_window, (WindowHandlers) { 91 | .load = window_load, 92 | .unload = window_unload, 93 | }); 94 | } 95 | window_stack_push(s_main_window, true); 96 | } 97 | -------------------------------------------------------------------------------- /src/windows/radio_button_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define RADIO_BUTTON_WINDOW_NUM_ROWS 4 6 | #define RADIO_BUTTON_WINDOW_CELL_HEIGHT 44 7 | #define RADIO_BUTTON_WINDOW_RADIO_RADIUS 6 8 | 9 | void radio_button_window_push(); 10 | -------------------------------------------------------------------------------- /src/windows/text_animation_window.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Example text change animation implementation. 3 | */ 4 | 5 | #include "text_animation_window.h" 6 | 7 | static Window *s_window; 8 | static TextLayer *s_text_layer; 9 | 10 | static AppTimer *s_timer; 11 | static char s_text[2][32]; 12 | static uint8_t s_current_text; 13 | 14 | static void animate(); 15 | 16 | static void out_stopped_handler(Animation *animation, bool finished, void *context) { 17 | s_current_text += (s_current_text == 0) ? 1 : -1; 18 | text_layer_set_text(s_text_layer, s_text[s_current_text]); 19 | 20 | Layer *text_layer = text_layer_get_layer(s_text_layer); 21 | GRect frame = layer_get_frame(text_layer); 22 | GRect start = GRect(frame.origin.x + (2 * TEXT_ANIMATION_WINDOW_DISTANCE), frame.origin.y, frame.size.w, frame.size.h); 23 | GRect finish = GRect(frame.origin.x + TEXT_ANIMATION_WINDOW_DISTANCE, frame.origin.y, frame.size.w, frame.size.h); 24 | 25 | PropertyAnimation *in_prop_anim = property_animation_create_layer_frame(text_layer, &start, &finish); 26 | Animation *in_anim = property_animation_get_animation(in_prop_anim); 27 | animation_set_curve(in_anim, AnimationCurveEaseInOut); 28 | animation_set_duration(in_anim, TEXT_ANIMATION_WINDOW_DURATION); 29 | animation_schedule(in_anim); 30 | } 31 | 32 | static void shake_animation() { 33 | Layer *text_layer = text_layer_get_layer(s_text_layer); 34 | GRect start = layer_get_frame(text_layer); 35 | GRect finish = GRect(start.origin.x - TEXT_ANIMATION_WINDOW_DISTANCE, start.origin.y, start.size.w, start.size.h); 36 | 37 | PropertyAnimation *out_prop_anim = property_animation_create_layer_frame(text_layer, &start, &finish); 38 | Animation *out_anim = property_animation_get_animation(out_prop_anim); 39 | animation_set_curve(out_anim, AnimationCurveEaseInOut); 40 | animation_set_duration(out_anim, TEXT_ANIMATION_WINDOW_DURATION); 41 | animation_set_handlers(out_anim, (AnimationHandlers) { 42 | .stopped = out_stopped_handler 43 | }, NULL); 44 | animation_schedule(out_anim); 45 | } 46 | 47 | static void animate_callback(void *context) { 48 | animate(); 49 | } 50 | 51 | static void animate() { 52 | shake_animation(); 53 | s_timer = app_timer_register(TEXT_ANIMATION_WINDOW_INTERVAL, animate_callback, NULL); 54 | } 55 | 56 | static void window_load(Window *window) { 57 | Layer *window_layer = window_get_root_layer(window); 58 | GRect bounds = layer_get_bounds(window_layer); 59 | 60 | const GEdgeInsets text_insets = {.top = (bounds.size.h / 2) - 24}; 61 | s_text_layer = text_layer_create(grect_inset(bounds, text_insets)); 62 | text_layer_set_text(s_text_layer, "Example text."); 63 | text_layer_set_text_color(s_text_layer, GColorWhite); 64 | text_layer_set_background_color(s_text_layer, GColorClear); 65 | text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 66 | text_layer_set_text_alignment(s_text_layer, GTextAlignmentCenter); 67 | layer_add_child(window_layer, text_layer_get_layer(s_text_layer)); 68 | } 69 | 70 | static void window_unload(Window *window) { 71 | text_layer_destroy(s_text_layer); 72 | window_destroy(s_window); 73 | s_window = NULL; 74 | } 75 | 76 | static void window_disappear(Window *window) { 77 | if(s_timer) { 78 | app_timer_cancel(s_timer); 79 | s_timer = NULL; 80 | } 81 | } 82 | 83 | void text_animation_window_push() { 84 | snprintf(s_text[0], sizeof(s_text[0]), "Some example text"); 85 | snprintf(s_text[1], sizeof(s_text[1]), "Some more example text"); 86 | s_current_text = 0; 87 | 88 | if(!s_window) { 89 | s_window = window_create(); 90 | window_set_background_color(s_window, PBL_IF_COLOR_ELSE(GColorBlueMoon, GColorBlack)); 91 | window_set_window_handlers(s_window, (WindowHandlers) { 92 | .load = window_load, 93 | .unload = window_unload, 94 | .disappear = window_disappear 95 | }); 96 | } 97 | window_stack_push(s_window, true); 98 | 99 | animate(); 100 | } 101 | -------------------------------------------------------------------------------- /src/windows/text_animation_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define TEXT_ANIMATION_WINDOW_DURATION 40 // Duration of each half of the animation 6 | #define TEXT_ANIMATION_WINDOW_DISTANCE 5 // Pixels the animating text move by 7 | #define TEXT_ANIMATION_WINDOW_INTERVAL 1000 // Interval between timers 8 | 9 | void text_animation_window_push(); 10 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file is the default set of rules to compile a Pebble project. 4 | # 5 | # Feel free to customize this to your needs. 6 | # 7 | 8 | import os.path 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | ctx.load('pebble_sdk') 21 | 22 | build_worker = os.path.exists('worker_src') 23 | binaries = [] 24 | 25 | for p in ctx.env.TARGET_PLATFORMS: 26 | ctx.set_env(ctx.all_envs[p]) 27 | ctx.set_group(ctx.env.PLATFORM_NAME) 28 | app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 29 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 30 | target=app_elf) 31 | 32 | if build_worker: 33 | worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 34 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 35 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 36 | target=worker_elf) 37 | else: 38 | binaries.append({'platform': p, 'app_elf': app_elf}) 39 | 40 | ctx.set_group('bundle') 41 | ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js')) 42 | --------------------------------------------------------------------------------