Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,135 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/text.h"
#include "applib/ui/window_private.h"
#include "applib/ui/app_window_stack.h"
#include "board/board.h"
#include "kernel/event_loop.h"
#include "kernel/panic.h"
#include "kernel/pbl_malloc.h"
#include "process_management/pebble_process_md.h"
#include "process_state/app_state/app_state.h"
#include "services/common/i18n/i18n.h"
#include "services/runlevel.h"
#include "system/passert.h"
#include "system/reset.h"
#include <stdio.h>
static const uint8_t sad_watch[] = {
0x04, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00, 0xff, 0xff, 0xff, 0xff, /* bytes 0 - 16 */
0xff, 0x0f, 0xf8, 0xff, 0xff, 0x57, 0xf5, 0xff, 0xff, 0xa7, 0xf2, 0xff, 0xff, 0x57, 0xf5, 0xff, /* bytes 16 - 32 */
0xff, 0xa9, 0xca, 0xff, 0xff, 0x06, 0xb0, 0xff, 0xff, 0xfe, 0xbf, 0xff, 0x7f, 0x06, 0x30, 0xff, /* bytes 32 - 48 */
0x7f, 0xfa, 0x2f, 0xff, 0x7f, 0xfa, 0x2f, 0xff, 0x7f, 0xaa, 0x2a, 0xff, 0xff, 0xda, 0xad, 0xff, /* bytes 48 - 64 */
0xff, 0xaa, 0x2a, 0xff, 0xff, 0xfa, 0x2f, 0xff, 0xff, 0xfa, 0x2f, 0xff, 0xff, 0x1a, 0x2c, 0xff, /* bytes 64 - 80 */
0xff, 0xea, 0xab, 0xff, 0xff, 0xfa, 0x2f, 0xff, 0xff, 0xfa, 0x2f, 0xff, 0xff, 0xfa, 0x2f, 0xff, /* bytes 80 - 96 */
0xff, 0x06, 0x20, 0xff, 0xff, 0xfe, 0xbf, 0xff, 0xff, 0xfe, 0xbf, 0xff, 0xff, 0x06, 0xb0, 0xff, /* bytes 96 - 112 */
0xff, 0xa9, 0xca, 0xff, 0xff, 0x57, 0xf5, 0xff, 0xff, 0xa7, 0xf2, 0xff, 0xff, 0x57, 0xf5, 0xff, /* bytes 112 - 128 */
0xff, 0x0f, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
typedef struct PanicWindowAppData {
Window window;
Layer layer;
} PanicWindowAppData;
static void prv_update_proc(Layer* layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpAssignInverted);
GBitmap sad_watch_bitmap;
gbitmap_init_with_data(&sad_watch_bitmap, sad_watch);
const GRect bitmap_dest_rect = GRect(56, 68, 32, 32);
graphics_draw_bitmap_in_rect(ctx, &sad_watch_bitmap, &bitmap_dest_rect);
GFont error_code_face = fonts_get_system_font(FONT_KEY_GOTHIC_14);
const GRect text_dest_rect = GRect(38, 108, 70, 30);
char text_buffer[11];
snprintf(text_buffer, sizeof(text_buffer), "0x%"PRIx32, launcher_panic_get_current_error());
graphics_draw_text(ctx, text_buffer, error_code_face,
text_dest_rect, GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL);
}
static void prv_panic_reset_callback(void* data) {
RebootReason reason = {
.code = RebootReasonCode_LauncherPanic,
.extra = launcher_panic_get_current_error()
};
reboot_reason_set(&reason);
system_reset();
}
static void prv_panic_button_click_handler(ClickRecognizerRef recognizer, void *context) {
launcher_task_add_callback(prv_panic_reset_callback, NULL);
}
static void prv_panic_click_config_provider(void* context) {
for (ButtonId button_id = BUTTON_ID_BACK; button_id < NUM_BUTTONS; ++button_id) {
window_single_click_subscribe(button_id, prv_panic_button_click_handler);
}
}
static void prv_handle_init(void) {
PanicWindowAppData* data = app_malloc_check(sizeof(PanicWindowAppData));
app_state_set_user_data(data);
services_set_runlevel(RunLevel_BareMinimum);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Panic"));
window_set_overrides_back_button(window, true);
window_set_background_color(window, GColorBlack);
window_set_click_config_provider(window, prv_panic_click_config_provider);
#if CAPABILITY_HAS_HARDWARE_PANIC_SCREEN
display_show_panic_screen(launcher_panic_get_current_error());
#else
layer_init(&data->layer, &window_get_root_layer(&data->window)->frame);
layer_set_update_proc(&data->layer, prv_update_proc);
layer_add_child(window_get_root_layer(&data->window), &data->layer);
#endif
const bool animated = false;
app_window_stack_push(window, animated);
}
static void s_main(void) {
prv_handle_init();
app_event_loop();
}
const PebbleProcessMd* panic_app_get_app_info() {
static const PebbleProcessMdSystem s_app_md = {
.common = {
.main_func = s_main,
.visibility = ProcessVisibilityHidden,
// UUID: 130fb6d7-da9e-485a-87ca-a5ca4bf21912
.uuid = {0x13, 0x0f, 0xb6, 0xd7, 0xda, 0x9e, 0x48, 0x5a, 0x87, 0xca, 0xa5, 0xca, 0x4b, 0xf2, 0x19, 0x12},
},
.name = "Panic App",
.run_level = ProcessAppRunLevelCritical,
};
return (const PebbleProcessMd*) &s_app_md;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* panic_app_get_app_info();

View file

@ -0,0 +1,274 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_ui_app.h"
#include "applib/app.h"
#include "applib/app_timer.h"
#include "applib/graphics/gpath_builder.h"
#include "applib/graphics/graphics.h"
#include "util/trig.h"
#include "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "applib/ui/layer.h"
#include "applib/ui/progress_layer.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window_private.h"
#include "applib/ui/app_window_stack.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_manager.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "services/common/firmware_update.h"
#include "services/common/i18n/i18n.h"
#include "system/logging.h"
#include "system/passert.h"
#include <stdio.h>
#include <string.h>
#define UPDATE_FREQ_MS 1000
#define FAIL_SCREEN_VISIBLE_DURATION_MS 10000
#define COMPLETE_SCREEN_VISIBLE_DURATION_MS 5000
#define PROG_LAYER_START_VAL 6
// Used to force the progress bar to start at PROG_LAYER_START_VAL and scale
// the reset of the progress between that value and MAX_PROGRESS_PERCENT
#define PROG_LAYER_TRANSFORM(real_prog) \
(PROG_LAYER_START_VAL + (real_prog * \
(MAX_PROGRESS_PERCENT - PROG_LAYER_START_VAL) / MAX_PROGRESS_PERCENT))
////////////////////////////////////////////////////////////
// Data structures
typedef struct {
Window window;
TextLayer percent_done_text_layer;
char percent_done_text_buffer[5]; //<! Text for progress percentage label, format %02d%%
SimpleDialog finished_dialog;
ProgressLayer progress_layer;
AppTimer *timer;
unsigned int percent_complete;
ProgressUISource progress_source;
bool is_finished;
} ProgressUIData;
////////////////////////////////////////////////////////////
// Progress Logic
static void prv_quit(void *data) {
i18n_free_all(data);
app_window_stack_pop_all(true);
}
static const char *prv_get_dialog_text(ProgressUIData *data, bool success) {
if (data->progress_source == PROGRESS_UI_SOURCE_FW_UPDATE) {
return success ? i18n_get("Update Complete", data) : i18n_get("Update Failed", data);
} else {
return "";
}
}
static void prv_handle_finished(ProgressUIData *data, bool success) {
if (data->is_finished) {
return;
}
data->is_finished = true;
layer_set_hidden(&data->percent_done_text_layer.layer, true);
layer_set_hidden(&data->progress_layer.layer, true);
uint32_t res_id;
uint32_t end_screen_timeout;
if (success) {
res_id = RESOURCE_ID_GENERIC_CONFIRMATION_LARGE;
end_screen_timeout = COMPLETE_SCREEN_VISIBLE_DURATION_MS;
dialog_set_background_color(simple_dialog_get_dialog(&data->finished_dialog), GColorGreen);
simple_dialog_set_buttons_enabled(&data->finished_dialog, false);
} else {
res_id = RESOURCE_ID_GENERIC_WARNING_LARGE;
end_screen_timeout = FAIL_SCREEN_VISIBLE_DURATION_MS;
}
dialog_set_icon(&data->finished_dialog.dialog, res_id);
#if !PLATFORM_ROBERT && !PLATFORM_CALCULUS
dialog_set_text(&data->finished_dialog.dialog, prv_get_dialog_text(data, success));
#endif
// Show the status screen for a bit before closing the app
dialog_set_timeout(&data->finished_dialog.dialog, end_screen_timeout);
simple_dialog_push(&data->finished_dialog, app_state_get_window_stack());
app_timer_cancel(data->timer);
}
static void prv_update_progress_text(ProgressUIData *data) {
sniprintf(data->percent_done_text_buffer,
sizeof(data->percent_done_text_buffer), "%u%%", data->percent_complete);
layer_mark_dirty(&data->percent_done_text_layer.layer);
}
static void prv_update_progress(ProgressUIData *data) {
switch (data->progress_source) {
case PROGRESS_UI_SOURCE_COREDUMP: {
break;
}
case PROGRESS_UI_SOURCE_LOGS: {
break;
}
case PROGRESS_UI_SOURCE_FW_UPDATE: {
if (firmware_update_current_status() == FirmwareUpdateFailed) {
prv_handle_finished(data, false /* success */);
return;
} else if (firmware_update_current_status() == FirmwareUpdateStopped) {
prv_handle_finished(data, true /* success */);
return;
}
data->percent_complete = firmware_update_get_percent_progress();
break;
}
}
prv_update_progress_text(data);
progress_layer_set_progress(&data->progress_layer,
PROG_LAYER_TRANSFORM(data->percent_complete));
if ((data->progress_source != PROGRESS_UI_SOURCE_FW_UPDATE) &&
(data->percent_complete >= 100)) {
prv_handle_finished(data, true /* success */);
}
}
static void prv_refresh_progress(void *data_in) {
ProgressUIData *data = (ProgressUIData*) data_in;
if (!data) {
// Sanity check
return;
}
// Overwrite the old timer handle, it's no longer valid
data->timer = app_timer_register(UPDATE_FREQ_MS, prv_refresh_progress, data);
prv_update_progress(data);
}
////////////////////////////////////////////////////////////
// Window loading, unloading, initializing
static void prv_dialog_unloaded(void *context) {
ProgressUIData *data = context;
// Schedule a super quick timer to pop all windows. Can't call it here directly
// since we would actually try popping the dialog window too, causing a fault.
data->timer = app_timer_register(10, prv_quit, data);
}
static void prv_window_unload_handler(Window* window) {
ProgressUIData *data = window_get_user_data(window);
if (data) {
i18n_free_all(data);
app_timer_cancel(data->timer);
app_free(data);
}
}
static void prv_window_load_handler(Window* window) {
ProgressUIData *data = window_get_user_data(window);
const ProgressUIAppArgs* app_args = app_manager_get_task_context()->args;
data->progress_source = app_args->progress_source;
simple_dialog_init(&data->finished_dialog, "Update Completed Dialog");
dialog_set_callbacks(&data->finished_dialog.dialog, &(DialogCallbacks) {
.unload = prv_dialog_unloaded,
}, data);
dialog_set_destroy_on_pop(&data->finished_dialog.dialog, false);
const int16_t load_bar_length = 108;
const int16_t x_offset = (window->layer.bounds.size.w - load_bar_length) / 2;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
const int16_t y_offset_progress = 123;
const int16_t y_offset_text = 85;
#else
const int16_t y_offset_progress = PBL_IF_RECT_ELSE(93, 99);
const int16_t y_offset_text = PBL_IF_RECT_ELSE(55, 62);
#endif
const GRect progress_bounds = GRect(x_offset, y_offset_progress, load_bar_length, 8);
ProgressLayer *progress_layer = &data->progress_layer;
progress_layer_init(progress_layer, &progress_bounds);
progress_layer_set_corner_radius(progress_layer, 3);
layer_add_child(&window->layer, &progress_layer->layer);
TextLayer *percent_done_text_layer = &data->percent_done_text_layer;
text_layer_init_with_parameters(percent_done_text_layer,
&GRect(0, y_offset_text, window->layer.bounds.size.w, 30),
data->percent_done_text_buffer,
fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
GColorBlack, GColorClear, GTextAlignmentCenter,
GTextOverflowModeTrailingEllipsis);
layer_add_child(&window->layer, &percent_done_text_layer->layer);
data->timer = app_timer_register(UPDATE_FREQ_MS, prv_refresh_progress, data);
prv_update_progress(data);
}
static void prv_progress_ui_window_push(void) {
ProgressUIData *data = app_zalloc_check(sizeof(ProgressUIData));
Window* window = &data->window;
window_init(window, WINDOW_NAME("Progress UI App"));
window_set_user_data(window, data);
window_set_overrides_back_button(window, true);
window_set_background_color(window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite));
window_set_window_handlers(window, &(WindowHandlers){
.load = prv_window_load_handler,
.unload = prv_window_unload_handler,
});
app_window_stack_push(window, false);
}
static void prv_main(void) {
if (!app_manager_get_task_context()->args) {
PBL_LOG(LOG_LEVEL_WARNING, "Progress UI App must be launched with args");
return;
}
launcher_block_popups(true);
prv_progress_ui_window_push();
app_event_loop();
launcher_block_popups(false);
}
////////////////////////////////////////////////////////////
// Public functions
const PebbleProcessMd* progress_ui_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common = {
.main_func = &prv_main,
.visibility = ProcessVisibilityHidden,
// UUID: f29f18ac-bbec-452b-9262-49c4f6e5c920
.uuid = {0xf2, 0x9f, 0x18, 0xac, 0xbb, 0xec, 0x45, 0x2b,
0x92, 0x62, 0x49, 0xc4, 0xf6, 0xe5, 0xc9, 0x20},
},
.name = "Progress UI",
.run_level = ProcessAppRunLevelSystem,
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
typedef enum {
PROGRESS_UI_SOURCE_COREDUMP,
PROGRESS_UI_SOURCE_LOGS,
PROGRESS_UI_SOURCE_FW_UPDATE,
} ProgressUISource;
typedef struct {
ProgressUISource progress_source;
} ProgressUIAppArgs;
const PebbleProcessMd* progress_ui_app_get_info();

View file

@ -0,0 +1,194 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "spinner_ui_window.h"
#include "applib/graphics/gpath_builder.h"
#include "applib/graphics/graphics.h"
#include "util/trig.h"
#include "applib/ui/animation.h"
#include "applib/ui/layer.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/bitmap_layer.h"
#include "kernel/pbl_malloc.h"
#include "resource/resource_ids.auto.h"
#include "system/passert.h"
#include "string.h"
////////////////////////////////////////////////////////////
// Data structures
typedef struct {
Window window;
GBitmap *bitmap;
bool bitmap_inited;
BitmapLayer bitmap_layer;
Layer anim_layer;
PropertyAnimation *spinner_animation;
AnimationImplementation spinner_anim_impl;
GColor spinner_color;
AnimationProgress cur_distance_normalized;
bool should_cancel_animation;
} SpinnerUIData;
////////////////////////////////////////////////////////////
// Animation Logic
// There is a slight delay (lag) between the animation stopping and starting it again. To minimize
// this, make the animation contain multiple loops (360 degree rotations) instead of 1.
// This means that the the lag occurs once less frequently and is less noticable
#define LOOPS_PER_ANIMATION 10
#define LOOP_DURATION_MS 1500
static void prv_draw_spinner_circles(Layer *layer, GContext* ctx) {
// Drawing the circles with aa is just too slow and we end up backing up the rest of the system.
// See PBL-16184
graphics_context_set_antialiased(ctx, false);
SpinnerUIData *data = window_get_user_data(layer_get_window(layer));
// This is the background image's circle.
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
const unsigned int center_of_circle_y_val = 103;
#else
const unsigned int center_of_circle_y_val = PBL_IF_RECT_ELSE(72, layer->bounds.size.h / 2);
#endif
const unsigned int radius_of_path = 37;
const unsigned int radius_of_spinner_circles = 9;
const GPoint circle_center_point = GPoint(layer->bounds.size.w / 2, center_of_circle_y_val);
const unsigned int angle = (TRIG_MAX_ANGLE * data->cur_distance_normalized *
LOOPS_PER_ANIMATION) / ANIMATION_NORMALIZED_MAX;
const GPoint circle1_location = {
.x = (sin_lookup(angle) * radius_of_path / TRIG_MAX_RATIO) + circle_center_point.x,
.y = (-cos_lookup(angle) * radius_of_path / TRIG_MAX_RATIO) + circle_center_point.y,
};
const GPoint circle2_location = {
.x = (-sin_lookup(angle) * (-radius_of_path) / TRIG_MAX_RATIO) + circle_center_point.x,
.y = (-cos_lookup(angle) * (-radius_of_path) / TRIG_MAX_RATIO) + circle_center_point.y,
};
graphics_context_set_fill_color(ctx, data->spinner_color);
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_fill_circle(ctx, circle1_location, radius_of_spinner_circles);
graphics_draw_circle(ctx, circle1_location, radius_of_spinner_circles);
graphics_fill_circle(ctx, circle2_location, radius_of_spinner_circles);
graphics_draw_circle(ctx, circle2_location, radius_of_spinner_circles);
}
static void prv_anim_impl(struct Animation *animation,
const AnimationProgress distance_normalized) {
SpinnerUIData *data = (SpinnerUIData*) animation_get_context(animation);
// We need to artificially limit how frequent we attempt to update the screen. If we update
// it too fast the thing we wanted to do in the background never gets done. This isn't quite
// ideal, as around 60 steps is when things are actually smooth, but 60 is too fast and does
// restrict the speed of our core dump. See PBL-16184
const uint32_t steps_per_loop = 25;
const int32_t min_delta = (ANIMATION_NORMALIZED_MAX/LOOPS_PER_ANIMATION) / steps_per_loop;
if (data->cur_distance_normalized + min_delta < distance_normalized) {
data->cur_distance_normalized = distance_normalized;
layer_mark_dirty(&data->anim_layer);
}
}
static void prv_anim_stopped(Animation *animation, bool finished, void *context) {
SpinnerUIData *data = (SpinnerUIData*) animation_get_context(animation);
if (!data->should_cancel_animation) {
data->cur_distance_normalized = 0;
animation_schedule(property_animation_get_animation(data->spinner_animation));
}
}
////////////////////////////////////////////////////////////
// Window loading, unloading, initializing
static void prv_window_unload_handler(Window* window) {
SpinnerUIData *data = window_get_user_data(window);
if (data) {
gbitmap_destroy(data->bitmap);
data->should_cancel_animation = true;
property_animation_destroy(data->spinner_animation);
kernel_free(data);
}
}
static void prv_window_load_handler(Window* window) {
SpinnerUIData *data = window_get_user_data(window);
GRect spinner_bounds = window->layer.bounds;
spinner_bounds.origin.y += PBL_IF_RECT_ELSE(0, 10);
window_set_background_color(window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite));
BitmapLayer *bitmap_layer = &data->bitmap_layer;
bitmap_layer_init(bitmap_layer, &window->layer.bounds);
bitmap_layer_set_alignment(bitmap_layer, PBL_IF_RECT_ELSE(GAlignTopLeft, GAlignCenter));
layer_set_frame(&bitmap_layer->layer, &spinner_bounds);
data->bitmap = gbitmap_create_with_resource(RESOURCE_ID_SPINNER_BACKGROUND);
bitmap_layer_set_bitmap(bitmap_layer, data->bitmap);
layer_add_child(&window->layer, &bitmap_layer->layer);
// Animation setup
Layer *anim_layer = &data->anim_layer;
layer_set_bounds(anim_layer, &window->layer.bounds);
layer_set_update_proc(anim_layer, prv_draw_spinner_circles);
layer_add_child(&window->layer, anim_layer);
// See comment about loops above (animation section)
const int animation_duration_ms = LOOP_DURATION_MS * LOOPS_PER_ANIMATION;
const int animation_delay_ms = 0;
data->spinner_animation = property_animation_create_layer_frame(&data->anim_layer, NULL, NULL);
if (data->spinner_animation) {
Animation *animation = property_animation_get_animation(data->spinner_animation);
animation_set_duration(animation, animation_duration_ms);
animation_set_delay(animation, animation_delay_ms);
animation_set_curve(animation, AnimationCurveLinear);
animation_set_auto_destroy(animation, false);
AnimationHandlers anim_handler = {
.stopped = prv_anim_stopped,
};
animation_set_handlers(animation, anim_handler, data);
data->spinner_anim_impl = (AnimationImplementation) {
.update = prv_anim_impl,
};
animation_set_implementation(animation, &data->spinner_anim_impl);
animation_schedule(animation);
}
}
Window* spinner_ui_window_get(GColor spinner_color) {
SpinnerUIData *data = kernel_malloc_check(sizeof(SpinnerUIData));
*data = (SpinnerUIData){};
data->spinner_color = spinner_color;
data->should_cancel_animation = false;
Window* window = &data->window;
window_init(window, WINDOW_NAME("Spinner UI Window"));
window_set_user_data(window, data);
window_set_overrides_back_button(window, true);
window_set_window_handlers(window, &(WindowHandlers){
.load = prv_window_load_handler,
.unload = prv_window_unload_handler,
});
return window;
}

View file

@ -0,0 +1,27 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/graphics/gcolor_definitions.h"
#include "applib/ui/window_private.h"
#include "applib/ui/window_stack.h"
// The function creates a Spinner UI window on the heap with the specified color.
// The window is cleaned up when it is popped.
// Returns a pointer to the created window
Window* spinner_ui_window_get(GColor spinner_color);

View file

@ -0,0 +1,221 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "action_menu_demo.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/action_menu_hierarchy.h"
#include "applib/ui/action_menu_window.h"
#include "applib/ui/action_menu_window_private.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "system/passert.h"
#include "util/size.h"
#include <string.h>
static struct {
// main window
Window *main_window;
TextLayer *info_layer;
// action menu
ActionMenu *action_menu;
// result_window
Window *result_window;
TextLayer *result_layer;
} *s_app_data;
///////////////////////
// Result Window
static void prv_result_window_load(Window *window) {
Layer *root_layer = window_get_root_layer(window);
s_app_data->result_layer = text_layer_create(GRect(0, 60, root_layer->bounds.size.w, 50));
text_layer_set_text_alignment(s_app_data->result_layer, GTextAlignmentCenter);
text_layer_set_text(s_app_data->result_layer, "Result!");
layer_add_child(root_layer, (Layer *)s_app_data->result_layer);
}
static void prv_result_window_unload(Window *window) {
window_destroy(window);
}
///////////////////////
// Action Menu Window
static void prv_action_menu_did_close_cb(ActionMenu *action_menu,
const ActionMenuItem *item,
void *context) {
ActionMenuLevel *root_level = action_menu_get_root_level(action_menu);
action_menu_hierarchy_destroy(root_level, NULL, NULL);
}
static void prv_action_callback(ActionMenu *action_menu,
const ActionMenuItem *action,
void *context) {
s_app_data->result_window = window_create();
window_set_window_handlers(s_app_data->result_window, &(WindowHandlers) {
.load = prv_result_window_load,
.unload = prv_result_window_unload,
});
action_menu_set_result_window(action_menu, s_app_data->result_window);
}
static void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) {
// First Level
ActionMenuLevel *first_level = action_menu_level_create(10);
action_menu_level_add_action(first_level,
"First!",
prv_action_callback,
NULL);
action_menu_level_add_action(first_level,
"Second!",
prv_action_callback,
NULL);
// More Levels
ActionMenuLevel *more_level = action_menu_level_create(1);
action_menu_level_add_action(more_level,
"That's it, folks!",
prv_action_callback,
NULL);
action_menu_level_add_child(first_level,
more_level,
"More...");
// Levels with multiple lines of text
ActionMenuLevel *multiline_level = action_menu_level_create(5);
action_menu_level_add_action(multiline_level,
"Sorry, I can't talk right now.",
prv_action_callback,
NULL);
action_menu_level_add_action(multiline_level,
"I can't talk just now, please text me if this is an emergency.",
prv_action_callback,
NULL);
action_menu_level_add_action(multiline_level,
"In a meeting, I will call you back when the meeting is over.",
prv_action_callback,
NULL);
action_menu_level_add_action(multiline_level,
"On my way, I will text you when I'm nearby.",
prv_action_callback,
NULL);
action_menu_level_add_action(multiline_level,
"I am busy.",
prv_action_callback,
NULL);
action_menu_level_add_child(first_level,
multiline_level,
"Canned Responses");
// Level with multi-column values of various row lengths
ActionMenuLevel *multicolumn_select = action_menu_level_create(3);
static const char* thin_values[] = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
"M", "🍺" };
ActionMenuLevel *multicolumn_one = action_menu_level_create(2);
action_menu_level_set_display_mode(multicolumn_one, ActionMenuLevelDisplayModeThin);
for (size_t i = 0; i < 2; i++) {
action_menu_level_add_action(multicolumn_one, thin_values[i], prv_action_callback, NULL);
}
// ah ah ah
ActionMenuLevel *multicolumn_two = action_menu_level_create(5);
action_menu_level_set_display_mode(multicolumn_two, ActionMenuLevelDisplayModeThin);
for (size_t i = 0; i < 5; i++) {
action_menu_level_add_action(multicolumn_two, thin_values[i], prv_action_callback, NULL);
}
// ah ah ah
ActionMenuLevel *multicolumn_many = action_menu_level_create(ARRAY_LENGTH(thin_values));
action_menu_level_set_display_mode(multicolumn_many, ActionMenuLevelDisplayModeThin);
for (size_t i = 0; i < ARRAY_LENGTH(thin_values); i++) {
action_menu_level_add_action(multicolumn_many, thin_values[i], prv_action_callback, NULL);
}
// ah ah ah
action_menu_level_add_child(multicolumn_select, multicolumn_one, "One row");
action_menu_level_add_child(multicolumn_select, multicolumn_two, "Two rows");
action_menu_level_add_child(multicolumn_select, multicolumn_many, "Many rows");
action_menu_level_add_child(first_level, multicolumn_select, "Columns");
first_level->separator_index = first_level->num_items - 1;
ActionMenuConfig config = {
.root_level = first_level,
.context = NULL,
.colors.background = GColorOxfordBlue,
.colors.foreground = GColorOrange,
.did_close = prv_action_menu_did_close_cb,
};
s_app_data->action_menu = app_action_menu_open(&config);
}
///////////////////////
// Main Window
static void prv_main_window_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler);
}
static void prv_main_window_load(Window *window) {
Layer *root_layer = window_get_root_layer(window);
s_app_data->info_layer = text_layer_create(GRect(0, 60, root_layer->bounds.size.w, 50));
text_layer_set_text_alignment(s_app_data->info_layer, GTextAlignmentCenter);
text_layer_set_text(s_app_data->info_layer, "Press the select button");
layer_add_child(root_layer, (Layer *)s_app_data->info_layer);
}
////////////////////
// App boilerplate
static void prv_init(void) {
s_app_data = app_zalloc_check(sizeof(*s_app_data));
s_app_data->main_window = window_create();
window_set_window_handlers(s_app_data->main_window, &(WindowHandlers) {
.load = prv_main_window_load,
});
window_set_click_config_provider(s_app_data->main_window, prv_main_window_click_config_provider);
app_window_stack_push(s_app_data->main_window, true /* animated */);
}
static void s_main(void) {
prv_init();
app_event_loop();
}
const PebbleProcessMd* action_menu_demo_get_app_info() {
static const PebbleProcessMdSystem s_app_data = {
.common = {
.main_func = s_main,
// UUID: 101a32d95-ef69-46d4-a0b9-854cc62f97f9
.uuid = {0x99, 0xa3, 0x2d, 0x95, 0xef, 0x69, 0x46, 0xd4,
0xa0, 0xb9, 0x85, 0x4c, 0xc6, 0x2f, 0x97, 0xf9},
},
.name = "Action Menu Demo",
};
return (const PebbleProcessMd*) &s_app_data;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* action_menu_demo_get_app_info(void);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* activity_test_get_app_info(void);

View file

@ -0,0 +1,92 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/text_layer.h"
#include "drivers/ambient_light.h"
#include "kernel/pbl_malloc.h"
#include "process_management/pebble_process_md.h"
#include "process_state/app_state/app_state.h"
#include <stdint.h>
#include <stdio.h>
#define AMBIENT_READING_STR_LEN 32
typedef struct {
Window *window;
TextLayer *text_layer;
char ambient_reading[AMBIENT_READING_STR_LEN];
} AmbientLightAppData;
static void prv_populate_amb_read_str(char *str) {
int level = ambient_light_get_light_level();
snprintf(str, AMBIENT_READING_STR_LEN, "Amb Level:\n %d", level);
}
static void timer_callback(void *cb_data) {
AmbientLightAppData *data = app_state_get_user_data();
prv_populate_amb_read_str(&data->ambient_reading[0]);
layer_mark_dirty(window_get_root_layer(data->window));
app_timer_register(500, timer_callback, NULL);
}
static void handle_init(void) {
AmbientLightAppData *data = task_malloc_check(sizeof(AmbientLightAppData));
data->window = window_create();
Layer *window_layer = window_get_root_layer(data->window);
GRect bounds = window_layer->bounds;
data->text_layer = text_layer_create((GRect)
{ .origin = { 0, 40 }, .size = { bounds.size.w, 100 } });
prv_populate_amb_read_str(&data->ambient_reading[0]);
text_layer_set_font(data->text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
text_layer_set_text(data->text_layer, data->ambient_reading);
text_layer_set_text_alignment(data->text_layer, GTextAlignmentCenter);
layer_add_child(window_layer, text_layer_get_layer(data->text_layer));
app_state_set_user_data(data);
app_window_stack_push(data->window, true);
app_timer_register(10, timer_callback, NULL);
}
static void handle_deinit(void) {
AmbientLightAppData *data = app_state_get_user_data();
text_layer_destroy(data->text_layer);
window_destroy(data->window);
task_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* ambient_light_reading_get_info() {
static const PebbleProcessMdSystem s_ambient_light_info = {
.common.main_func = s_main,
.name = "Amb Reading"
};
return (const PebbleProcessMd*) &s_ambient_light_info;
}

View file

@ -0,0 +1,172 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "animated_demo.h"
#include "applib/app.h"
#include "process_state/app_state/app_state.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "system/passert.h"
#include <string.h>
#include <stdio.h>
typedef struct AnimatedDemoData {
Window window;
TextLayer text_layer;
PropertyAnimation *prop_animation;
bool toggle;
} AnimatedDemoData;
static void animation_started(Animation *animation, AnimatedDemoData *data) {
text_layer_set_text(&data->text_layer, "Started.");
(void)animation;
}
static void animation_stopped(Animation *animation, bool finished, AnimatedDemoData *data) {
text_layer_set_text(&data->text_layer, finished ? "Hi, I'm a TextLayer!" : "Just Stopped.");
(void)animation;
}
AnimationProgress animation_bounce(AnimationProgress linear_distance) {
// An awful linear "bounce-like" animation
if (linear_distance < ANIMATION_NORMALIZED_MAX/2) {
return linear_distance * 2;
} else if (linear_distance < ANIMATION_NORMALIZED_MAX * 3/4) {
return ANIMATION_NORMALIZED_MAX * 3/2 - linear_distance;
} else {
return linear_distance;
}
}
static void click_handler(ClickRecognizerRef recognizer, Window *window) {
AnimatedDemoData *data = window_get_user_data(window);
Layer *layer = &data->text_layer.layer;
GRect to_rect;
if (data->toggle) {
to_rect = GRect(4, 4, 120, 60);
} else {
to_rect = GRect(84, 92, 60, 60);
}
data->toggle = !data->toggle;
// Does nothing if prop_animation is NULL
if (data->prop_animation) {
animation_unschedule(property_animation_get_animation(data->prop_animation));
property_animation_init_layer_frame(data->prop_animation, layer, NULL, &to_rect);
} else {
data->prop_animation = property_animation_create_layer_frame(layer, NULL, &to_rect);
}
Animation *animation = property_animation_get_animation(data->prop_animation);
PBL_ASSERTN(animation);
animation_set_auto_destroy(animation, true);
animation_set_duration(animation, 400);
switch (click_recognizer_get_button_id(recognizer)) {
case BUTTON_ID_UP:
animation_set_curve(animation, AnimationCurveEaseOut);
break;
case BUTTON_ID_DOWN:
// animation_set_curve(animation, AnimationCurveEaseIn);
// animation_set_duration(animation, 2000);
animation_set_custom_curve(animation, animation_bounce);
break;
default:
case BUTTON_ID_SELECT:
animation_set_curve(animation, AnimationCurveEaseInOut);
break;
}
/*
// Exmple animation parameters:
// Duration defaults to 250 ms
animation_set_duration(animation, 1000);
// Curve defaults to ease-in-out
animation_set_curve(animation, AnimationCurveEaseOut);
// Delay defaults to 0 ms
animation_set_delay(animation, 1000);
*/
animation_set_handlers(animation, (AnimationHandlers) {
.started = (AnimationStartedHandler) animation_started,
.stopped = (AnimationStoppedHandler) animation_stopped,
}, data);
animation_schedule(animation);
}
void animated_demo_window_load(Window *window) {
AnimatedDemoData *data = window_get_user_data(window);
text_layer_init(&data->text_layer, &GRect(0, 0, 60, 60));
text_layer_set_background_color(&data->text_layer, GColorBlack);
text_layer_set_text_color(&data->text_layer, GColorWhite);
text_layer_set_text(&data->text_layer, "Press Buttons");
GFont gothic_24_norm = fonts_get_system_font(FONT_KEY_GOTHIC_24);
text_layer_set_font(&data->text_layer, gothic_24_norm);
text_layer_set_text_alignment(&data->text_layer, GTextAlignmentCenter);
layer_add_child(&window->layer, &data->text_layer.layer);
}
static void config_provider(Window *window) {
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler)click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler)click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler)click_handler);
(void)window;
}
static void handle_init(void) {
AnimatedDemoData *data = (AnimatedDemoData*) app_malloc_check(sizeof(AnimatedDemoData));
data->toggle = false;
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Animated Demo"));
window_set_user_data(window, data);
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
window_set_window_handlers(window, &(WindowHandlers){
.load = animated_demo_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
static void handle_deinit(void) {
AnimatedDemoData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* animated_demo_get_app_info() {
static const PebbleProcessMdSystem animated_demo_app_info = {
.common.main_func = s_main,
.name = "Animation Demo"
};
return (const PebbleProcessMd*) &animated_demo_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* animated_demo_get_app_info();

View file

@ -0,0 +1,70 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "app_heap_demo.h"
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include <stdio.h>
// This app allocated approximately 75% of memory available to it.
// The idea is to run it multiple times to show that all data is being freed on
// exit, and is available for the next app to use.
static TextLayer *text_heap_info;
static Window *window;
static void init(void) {
char *start = app_malloc_check(1); // Get a pointer close to where the heap starts
window = window_create();
app_window_stack_push(window, true /* Animated */);
Layer *window_layer = window_get_root_layer(window);
text_heap_info = text_layer_create(window_layer->frame);
text_layer_set_text_color(text_heap_info, GColorWhite);
text_layer_set_background_color(text_heap_info, GColorBlack);
text_layer_set_font(text_heap_info, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
// Get the size of the heap from the beginning of the first thing allocated
unsigned long heap_size = 0x20020000 - (unsigned long)start;
unsigned long alloc_size = 0.75*heap_size;
char *buf = app_malloc_check(alloc_size);
snprintf(buf, 80, "%luB/%luB\n\nJust allocated %lu%% of the app heap.", alloc_size, heap_size, 100*alloc_size/heap_size);
text_layer_set_text(text_heap_info, buf);
layer_add_child(window_layer, text_layer_get_layer(text_heap_info));
}
static void deinit(void) {
// Don't free anything
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* app_heap_demo_app_get_info(void) {
static const PebbleProcessMdSystem s_app_heap_demo_app_info = {
.common.main_func = &s_main,
.name = "AppHeap"
};
return (const PebbleProcessMd*) &s_app_heap_demo_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* app_heap_demo_app_get_info(void);

View file

@ -0,0 +1,120 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "bouncing_box.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
static const int TARGET_FPS = 20;
static const int PIXEL_SPEED_PER_FRAME = 4;
typedef struct AppData {
Window window;
GRect box_rect;
int x_velocity;
int y_velocity;
uint8_t color;
} AppData;
static void prv_change_color(AppData *data) {
data->color++; // next RGB value (in lower 6 bits, might overflow to most significant 2 bits)
data->color |= (uint8_t)0b11000000; // make sure color is always 100% opaque
}
static void prv_move_rect(AppData *data) {
data->box_rect.origin.x += (data->x_velocity * PIXEL_SPEED_PER_FRAME);
if (data->box_rect.origin.x <= 0 ||
data->box_rect.origin.x + data->box_rect.size.w > data->window.layer.bounds.size.w) {
data->x_velocity = data->x_velocity * -1;
prv_change_color(data);
}
data->box_rect.origin.y += (data->y_velocity * PIXEL_SPEED_PER_FRAME);
if (data->box_rect.origin.y <= 0 ||
data->box_rect.origin.y + data->box_rect.size.h > data->window.layer.bounds.size.h) {
data->y_velocity = data->y_velocity * -1;
prv_change_color(data);
}
}
static void layer_update_proc(Layer *layer, GContext* ctx) {
AppData *data = app_state_get_user_data();
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_rect(ctx, &layer->bounds);
graphics_context_set_fill_color(ctx, (GColor)data->color);
graphics_fill_rect(ctx, &data->box_rect);
graphics_context_set_stroke_color(ctx, GColorBlack);
graphics_draw_rect(ctx, &data->box_rect);
}
void prv_redraw_timer_cb(void *cb_data) {
AppData *data = app_state_get_user_data();
prv_move_rect(data);
layer_mark_dirty(&data->window.layer);
app_timer_register(1000 / TARGET_FPS, prv_redraw_timer_cb, NULL);
}
static void s_main(void) {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Bouncing Box"));
window_set_user_data(window, data);
window_set_fullscreen(window, true);
layer_set_update_proc(&window->layer, layer_update_proc);
const bool animated = true;
app_window_stack_push(window, animated);
data->box_rect = GRect(10, 10, 40, 40);
data->x_velocity = 1;
data->y_velocity = 1;
app_timer_register(33, prv_redraw_timer_cb, NULL);
app_event_loop();
}
const PebbleProcessMd* bouncing_box_demo_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Bouncing Box"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* bouncing_box_demo_get_app_info(void);

View file

@ -0,0 +1,191 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "click_app.h"
#include "applib/app.h"
#include "process_state/app_state/app_state.h"
#include "applib/ui/ui.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include <stdio.h>
#define TEXT_BUFFER_SIZE 64
typedef struct {
Window window;
TextLayer text;
char text_buffer[TEXT_BUFFER_SIZE];
} ClickAppData;
////////////////////////////
// Click app's main window
//! Toggle the colors of the label, so we can see change even if the text stayed the same:
static void toggle_color(Window *window) {
ClickAppData *data = window_get_user_data(window);
TextLayer *text = &data->text;
const GColor bg_color = text->background_color;
if (gcolor_equal(bg_color, GColorBlack)) {
text_layer_set_background_color(text, GColorWhite);
text_layer_set_text_color(text, GColorBlack);
} else {
text_layer_set_background_color(text, GColorBlack);
text_layer_set_text_color(text, GColorWhite);
}
}
static void raw_click_handler(ClickRecognizerRef recognizer, Window *window, const bool up) {
ClickAppData *data = window_get_user_data(window);
sniprintf(data->text_buffer, TEXT_BUFFER_SIZE, up ? "Raw UP" : "Raw DOWN");
if (up) { // PBL_LOG requires a fixed const string, so can't use ternary
PBL_LOG(LOG_LEVEL_DEBUG, "Raw UP");
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Raw DOWN");
}
text_layer_set_text(&data->text, data->text_buffer);
toggle_color(window);
(void)recognizer;
}
static void raw_up_click_handler(ClickRecognizerRef recognizer, Window *window) {
raw_click_handler(recognizer, window, true);
}
static void raw_down_click_handler(ClickRecognizerRef recognizer, Window *window) {
raw_click_handler(recognizer, window, false);
}
static void select_multi_click_handler(ClickRecognizerRef recognizer, Window *window) {
ClickAppData *data = window_get_user_data(window);
const uint16_t count = click_number_of_clicks_counted(recognizer);
sniprintf(data->text_buffer, TEXT_BUFFER_SIZE, "Multi Click! (%u)\nMin: 2, Max: 10", count);
PBL_LOG(LOG_LEVEL_DEBUG, "Multi Click! (%u)", click_number_of_clicks_counted(recognizer));
text_layer_set_text(&data->text, data->text_buffer);
toggle_color(window);
}
static void select_single_click_handler(ClickRecognizerRef recognizer, Window *window) {
ClickAppData *data = window_get_user_data(window);
const uint16_t count = click_number_of_clicks_counted(recognizer);
sniprintf(data->text_buffer, TEXT_BUFFER_SIZE, "Single Click! (%u)", count);
PBL_LOG(LOG_LEVEL_DEBUG, "Single Click! (%u)", click_number_of_clicks_counted(recognizer));
text_layer_set_text(&data->text, data->text_buffer);
toggle_color(window);
// Let's try shortening the repeat interval as we go:
ClickConfig *config = click_recognizer_get_config(recognizer);
config->click.repeat_interval_ms = MAX((config->click.repeat_interval_ms / 2), 100);
}
static void select_long_click_handler(ClickRecognizerRef recognizer, Window *window) {
ClickAppData *data = window_get_user_data(window);
sniprintf(data->text_buffer, TEXT_BUFFER_SIZE, "Long Click!");
PBL_LOG(LOG_LEVEL_DEBUG, "Long Click!");
text_layer_set_text(&data->text, data->text_buffer);
toggle_color(window);
(void)recognizer;
}
static void select_long_click_release_handler(ClickRecognizerRef recognizer, Window *window) {
ClickAppData *data = window_get_user_data(window);
sniprintf(data->text_buffer, TEXT_BUFFER_SIZE, "Long Click Released!");
PBL_LOG(LOG_LEVEL_DEBUG, "Long Click Released!");
text_layer_set_text(&data->text, data->text_buffer);
toggle_color(window);
(void)recognizer;
}
static void config_provider(Window *window) {
// See ui/click.h for more information and default values.
// single click / repeat-on-hold config:
window_single_repeating_click_subscribe(BUTTON_ID_SELECT, 1000, (ClickHandler)select_single_click_handler); // "hold-to-repeat" gets overriden if there's a long click handler configured!
// multi click config:
window_multi_click_subscribe(BUTTON_ID_SELECT, 2, 10, 0, false, (ClickHandler) select_multi_click_handler);
// long click config:
window_long_click_subscribe(BUTTON_ID_SELECT, 700, (ClickHandler) select_long_click_handler, (ClickHandler) select_long_click_release_handler);
// single click / repeat-on-hold config:
window_single_repeating_click_subscribe(BUTTON_ID_UP, 1000, (ClickHandler) select_single_click_handler); // "hold-to-repeat" gets overriden if there's a long click handler configured!
// multi click config:
window_multi_click_subscribe(BUTTON_ID_UP, 2, 10, 0, true, (ClickHandler) select_multi_click_handler);
// raw:
window_raw_click_subscribe(BUTTON_ID_DOWN, (ClickHandler) raw_down_click_handler, (ClickHandler) raw_up_click_handler, NULL);
}
static void prv_window_load(Window *window) {
ClickAppData *data = window_get_user_data(window);
TextLayer *text = &data->text;
text_layer_init(text, &window->layer.bounds);
text_layer_set_text(text, "Use select button and try different clicks: single, hold-to-repeat, multiple, long press, etc.\n\nNOTE: a long click config will override hold-to-repeat config. Comment out the long_click section of the config to enable hold-to-repeat.");
layer_add_child(&window->layer, &text->layer);
}
static void push_window(ClickAppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Click Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init(void) {
ClickAppData *data = (ClickAppData*) app_malloc_check(sizeof(ClickAppData));
if (data == NULL) {
PBL_CROAK("Out of memory");
}
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit(void) {
ClickAppData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* click_app_get_info() {
static const PebbleProcessMdSystem s_click_app_info = {
.common.main_func = s_main,
.name = "Clicks"
};
return (const PebbleProcessMd*) &s_click_app_info;
}
#undef TEXT_BUFFER_SIZE

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* click_app_get_info();

View file

@ -0,0 +1,216 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "data_logging_test.h"
#include "system/logging.h"
#include "services/common/comm_session/session.h"
#include "services/normal/data_logging/data_logging_service.h"
#include "services/normal/data_logging/dls_private.h"
#include "applib/app.h"
#include "applib/data_logging.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "applib/ui/text_layer.h"
#include "applib/app_timer.h"
#include "applib/app_logging.h"
#include <stdio.h>
#include <string.h>
/*
* Incremental STM CRC32 implemented in software
*/
#define CRC_POLY 0x04C11DB7
static uint32_t crc_init(void) {
return 0xffffffff;
}
static uint32_t crc_update(uint32_t crc, const uint8_t *data, uint32_t length) {
const uint8_t num_remainder_bytes = length % 4;
uint32_t num_whole_word_bytes = length / 4;
while (num_whole_word_bytes--) {
crc = crc ^ *((uint32_t*)data);
for(int bit = 0; bit < 32; ++bit) {
if ((crc & 0x80000000) != 0) {
crc = (crc << 1) ^ CRC_POLY;
} else {
crc = (crc << 1);
}
}
data += 4;
}
if (num_remainder_bytes) {
uint32_t last_word = 0;
for (unsigned int i = 0; i < num_remainder_bytes; ++i) {
last_word = (last_word << 8) | data[i];
}
return crc_update(crc, (uint8_t*)&last_word, 4);
}
return crc & 0xffffffff;
}
/*
* Data Logging Test App
*/
struct DataLoggingInfo {
TextLayer text_layer;
char text[32];
int counter;
uint32_t crc;
DataLoggingSessionRef logging_session;
uint8_t item_size;
};
static struct {
Window window;
struct DataLoggingInfo info[3];
TextLayer log_layer;
} s_data;
static const int s_chunk_size = 80;
static void log_moar_data(struct DataLoggingInfo *info) {
uint8_t buf[s_chunk_size];
for (int i = 0; i < s_chunk_size; i++) {
buf[i] = (info->counter * s_chunk_size) + i;
}
info->crc = crc_update(info->crc, buf, s_chunk_size);
data_logging_log(info->logging_session, buf, s_chunk_size / info->item_size);
++info->counter;
}
static void handle_timer(void *ck) {
if (s_data.info[0].logging_session == NULL) {
// Sessions closed.
return;
}
struct DataLoggingInfo*info = ck;
log_moar_data(info);
const int num_chunks = 30;
snprintf(info->text, sizeof(info->text), "%lu (%i) %i/%i", info->crc, info->counter * s_chunk_size, info->counter, num_chunks);
text_layer_set_text(&info->text_layer, info->text);
if (info->counter < num_chunks) {
app_timer_register(1000 /* milliseconds */, handle_timer, info);
} else {
text_layer_set_text(&s_data.log_layer, "Done logging. Select to close.");
}
}
static void close_sessions(void) {
for (int i = 0; i < 3; ++i) {
data_logging_finish(s_data.info[i].logging_session);
s_data.info[i].logging_session = NULL;
}
text_layer_set_text(&s_data.log_layer, "Closed all logging sessions.");
}
static void start_logging(void) {
const uint8_t item_size[] = {4, 2, 16};
const DataLoggingItemType types[] = {DATA_LOGGING_INT, DATA_LOGGING_UINT, DATA_LOGGING_BYTE_ARRAY};
for (int i = 0; i < 3; ++i) {
text_layer_set_text(&s_data.info[i].text_layer, "Empty");
s_data.info[i].item_size = item_size[i];
s_data.info[i].logging_session = data_logging_create(i + 1, types[i], item_size[i], false);
}
// start timers
app_timer_register(2000 /* milliseconds */, handle_timer, (void *) &s_data.info[0]);
app_timer_register(1500 /* milliseconds */, handle_timer, (void *) &s_data.info[1]);
app_timer_register(4500 /* milliseconds */, handle_timer, (void *) &s_data.info[2]);
text_layer_set_text(&s_data.log_layer, "Logging...");
}
static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
if (s_data.info[0].logging_session) {
close_sessions();
} else {
start_logging();
}
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
}
static void handle_deinit(void) {
comm_session_set_responsiveness(comm_session_get_system_session(), BtConsumerApp,
ResponseTimeMax, MAX_PERIOD_RUN_FOREVER);
}
static void handle_init(void) {
dls_clear();
memset(&s_data, 0, sizeof(s_data));
// Init window
window_init(&s_data.window, "Logging Demo");
app_window_stack_push(&s_data.window, true /* Animated */);
window_set_click_config_provider_with_context(&s_data.window, click_config_provider,
&s_data.window);
const GRect *bounds = &s_data.window.layer.bounds;
for (int i = 0; i < 3; ++i) {
s_data.info[i].crc = crc_init();
text_layer_init(&s_data.info[i].text_layer, &GRect(0, i * 20, bounds->size.w, 20));
layer_add_child(&s_data.window.layer, &s_data.info[i].text_layer.layer);
}
text_layer_init(&s_data.log_layer, &GRect(0, bounds->size.w / 2, bounds->size.w,
bounds->size.w / 2));
layer_add_child(&s_data.window.layer, &s_data.log_layer.layer);
start_logging();
comm_session_set_responsiveness(comm_session_get_system_session(), BtConsumerApp,
ResponseTimeMax, MAX_PERIOD_RUN_FOREVER);
}
////////////////////
// App boilerplate
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* data_logging_test_get_info() {
static const PebbleProcessMdSystem s_app_info = {
// UUID: 01020304-0506-0708-0910-111213141516
.common.uuid = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16},
.common.main_func = &s_main,
.name = "Data Logging Test"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* data_logging_test_get_info();

View file

@ -0,0 +1,69 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "profile_mutexes_app.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "system/logging.h"
#include "os/mutex.h"
#include "system/profiler.h"
#include "kernel/util/sleep.h"
#include "services/common/new_timer/new_timer.h"
static Window *window;
static PebbleMutex *s_mutex;
static PebbleMutex *s_mutex2;
static void callback(void *data) {
PBL_LOG(LOG_LEVEL_DEBUG, "Locking mutex 2 (new timer)");
mutex_lock(s_mutex2);
PBL_LOG(LOG_LEVEL_DEBUG, "Locking mutex 1 (new timer)");
mutex_lock(s_mutex);
}
static void deadlock(void) {
s_mutex = mutex_create();
s_mutex2 = mutex_create();
TimerID timer = new_timer_create();
new_timer_start(timer, 10, callback, NULL, 0);
PBL_LOG(LOG_LEVEL_DEBUG, "Locking mutex 1");
mutex_lock(s_mutex);
psleep(20);
PBL_LOG(LOG_LEVEL_DEBUG, "Locking mutex 2");
mutex_lock(s_mutex2);
}
static void s_main(void) {
window = window_create();
app_window_stack_push(window, true /* Animated */);
deadlock();
app_event_loop();
}
const PebbleProcessMd* deadlock_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Deadlock"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* deadlock_get_app_info(void);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,410 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "dialogs_demo.h"
#include "applib/app.h"
#include "applib/graphics/gdraw_command_image.h"
#include "applib/graphics/gdraw_command_transforms.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/dialogs/dialog.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "applib/ui/dialogs/actionable_dialog.h"
#include "applib/ui/dialogs/expandable_dialog.h"
#include "applib/ui/window.h"
#include "applib/voice/transcription_dialog.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_menu_data_source.h"
#include "shell/normal/watchface.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "services/common/i18n/i18n.h"
#include "system/passert.h"
#include "util/size.h"
#include <stdio.h>
#include <string.h>
typedef struct DialogsData {
Window window;
MenuLayer menu_layer;
const char *lorem_ipsum;
const char *long_message;
uint32_t resource_id_80;
uint32_t resource_id_25;
} DialogsData;
/////////////////////////////
// Simple Dialog with timeout
static void prv_show_simple_dialog(DialogsData *data) {
SimpleDialog *simple_dialog = simple_dialog_create("Simple Dialog");
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
dialog_set_text(dialog, "Mama");
dialog_set_background_color(dialog, GColorRajah);
dialog_set_icon(dialog, data->resource_id_80);
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
app_simple_dialog_push(simple_dialog);
}
///////////////////////////////
// Simple Dialog with vibration
static void prv_show_simple_dialog_vibe(DialogsData *data) {
SimpleDialog *simple_dialog = simple_dialog_create("Simple Vibe Dialog");
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
dialog_set_text(dialog, "A Simple Dialog For Flow Testing!\nHello!");
dialog_set_background_color(dialog, GColorLavenderIndigo);
dialog_set_icon(dialog, data->resource_id_80);
dialog_set_vibe(dialog, true);
dialog_show_status_bar_layer(dialog, true);
app_simple_dialog_push(simple_dialog);
}
//////////////////////
// Confirmation Dialog
static void prv_confirm_click_handler(ClickRecognizerRef recognizer, void *context) {
app_window_stack_pop(true);
}
static void prv_confirm_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_confirm_click_handler);
}
static void prv_show_confirm_dialog(DialogsData *data) {
ActionableDialog *actionable_dialog = actionable_dialog_create("Confirm Dialog");
Dialog *dialog = actionable_dialog_get_dialog(actionable_dialog);
dialog_set_text(dialog, "Confirmation");
dialog_set_background_color(dialog, GColorGreen);
dialog_set_icon(dialog, data->resource_id_80);
dialog_show_status_bar_layer(dialog, true);
actionable_dialog_set_action_bar_type(actionable_dialog, DialogActionBarConfirm, NULL);
actionable_dialog_set_click_config_provider(actionable_dialog,
prv_confirm_config_provider);
app_actionable_dialog_push(actionable_dialog);
}
/////////////////
// Decline Dialog
static void prv_decline_click_handler(ClickRecognizerRef recognizer, void *context) {
app_window_stack_pop(true);
}
static void prv_decline_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_decline_click_handler);
}
static void prv_show_decline_dialog(DialogsData *data) {
ActionableDialog *actionable_dialog = actionable_dialog_create("Decline Dialog");
Dialog *dialog = actionable_dialog_get_dialog(actionable_dialog);
dialog_set_text(dialog, i18n_get("Decline dialog.", dialog));
dialog_set_background_color(dialog, GColorRed);
dialog_set_icon(dialog, data->resource_id_80);
actionable_dialog_set_action_bar_type(actionable_dialog, DialogActionBarDecline, NULL);
actionable_dialog_set_click_config_provider(actionable_dialog,
prv_decline_config_provider);
app_actionable_dialog_push(actionable_dialog);
}
//////////////////////////////////////////
// ActionableDialog with custom action bar
static void prv_custom_action_bar_click_up(ClickRecognizerRef recognizer, void *context) {
Dialog *dialog = context;
dialog_set_text(dialog, "The text has changed!");
layer_mark_dirty(&dialog->text_layer.layer);
}
static void prv_custom_action_bar_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_UP, prv_custom_action_bar_click_up);
}
static void prv_show_custom_actionable_dialog(DialogsData *data) {
ActionableDialog *actionable_dialog = actionable_dialog_create("Custom Actionable Dialog");
Dialog *dialog = actionable_dialog_get_dialog(actionable_dialog);
dialog_set_text(dialog, "Custom Actionable Dialog");
dialog_set_background_color(dialog, GColorRed);
dialog_set_icon(dialog, data->resource_id_80);
// Create custom action bar for the dialog.
// TODO destroy action bar / icon in unload callback
ActionBarLayer *custom_action_bar = action_bar_layer_create();
action_bar_layer_set_icon(custom_action_bar, BUTTON_ID_UP,
gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_CHECK));
action_bar_layer_set_context(custom_action_bar, dialog);
action_bar_layer_set_click_config_provider(custom_action_bar,
prv_custom_action_bar_config_provider);
actionable_dialog_set_action_bar_type(actionable_dialog, DialogActionBarCustom,
custom_action_bar);
app_actionable_dialog_push(actionable_dialog);
}
////////////////////
// Expandable Dialog
// Has a custom icon/clickhandler for the select button
static void prv_my_select_click_handler(ClickRecognizerRef recognizer, void *context) {
ExpandableDialog *expandable_dialog = context;
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_background_color(dialog, GColorRed);
}
static void prv_show_expandable_dialog(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_set_icon(dialog, data->resource_id_25);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_set_select_action(expandable_dialog,
RESOURCE_ID_ACTION_BAR_ICON_CHECK, prv_my_select_click_handler);
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////
// Expandable Dialog with header
static void prv_show_expandable_dialog_header(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_set_icon(dialog, data->resource_id_25);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_set_header(expandable_dialog, "Header");
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////
// Expandable Dialog with multi line header
static void prv_show_expandable_dialog_long_header(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_set_icon(dialog, data->resource_id_25);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_set_header(expandable_dialog, "A very long header");
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////////////
// Expandable Dialog no icon and header
static void prv_show_expandable_dialog_header_no_icon(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_set_header(expandable_dialog, "Header");
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////
// Expandable Dialog with header
static void prv_show_expandable_dialog_no_action_bar(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_set_icon(dialog, data->resource_id_25);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_show_action_bar(expandable_dialog, false);
expandable_dialog_set_header(expandable_dialog, "Header");
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////
// Expandable Dialog with icon
static void prv_show_expandable_dialog_no_icon(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, data->lorem_ipsum);
dialog_set_background_color(dialog, GColorLightGray);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_show_action_bar(expandable_dialog, true);
app_expandable_dialog_push(expandable_dialog);
}
///////////////////////////////////
// Expandable Dialog with no scroll
static void prv_show_expandable_dialog_no_scroll(DialogsData *data) {
ExpandableDialog *expandable_dialog = expandable_dialog_create("Expandable Dialog");
Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog);
dialog_set_text(dialog, "Look mah, no scroll!");
dialog_set_background_color(dialog, GColorLightGray);
dialog_show_status_bar_layer(dialog, true);
expandable_dialog_show_action_bar(expandable_dialog, true);
app_expandable_dialog_push(expandable_dialog);
}
////////////////////////////////
// Voice Dialog
static void prv_transcription_dialog_cb(void *context) {
DialogsData *data = context;
SimpleDialog *simple_dialog = simple_dialog_create("Simple Dialog");
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
dialog_set_text(dialog, "Pop!");
dialog_set_background_color(dialog, GColorRed);
dialog_set_icon(dialog, data->resource_id_80);
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
app_simple_dialog_push(simple_dialog);
}
static void prv_show_transcription_dialog(DialogsData *data) {
TranscriptionDialog *transcription_dialog = transcription_dialog_create();
static char transcription[500] = {0};
uint16_t len = strlen(data->long_message);
strncpy(transcription, data->long_message, len);
transcription_dialog_update_text(transcription_dialog, transcription, len);
app_transcription_dialog_push(transcription_dialog);
transcription_dialog_set_callback(transcription_dialog, prv_transcription_dialog_cb, data);
}
/////////////////////////////////////
// Set up dialog labels and callbacks
typedef struct DialogNode {
const char *label;
void (*show)(DialogsData *data);
} DialogNode;
static const DialogNode nodes[] = {
{ .label = "D1 - Confirm", .show = prv_show_confirm_dialog, },
{ .label = "D2 - Decline", .show = prv_show_decline_dialog, },
{ .label = "D3 - Actionable", .show = prv_show_custom_actionable_dialog, },
{ .label = "D4 - Expandable", .show = prv_show_expandable_dialog, },
{ .label = "D4 - Exp with header", .show = prv_show_expandable_dialog_header, },
{ .label = "D4 - Exp with long header", .show = prv_show_expandable_dialog_long_header, },
{ .label = "D5 - Simple Timeout", .show = prv_show_simple_dialog, },
{ .label = "D5 - Simple Vibe", .show = prv_show_simple_dialog_vibe, },
{ .label = "D6 - Exp no action bar", .show = prv_show_expandable_dialog_no_action_bar, },
{ .label = "D7 - Exp no icon", .show = prv_show_expandable_dialog_no_icon, },
{ .label = "D8 - Exp no scroll", .show = prv_show_expandable_dialog_no_scroll, },
{ .label = "D9 - Exp header only", .show = prv_show_expandable_dialog_header_no_icon },
{ .label = "D10 - Transcription", .show = prv_show_transcription_dialog, }
};
static const uint16_t NUM_ITEMS = ARRAY_LENGTH(nodes);
//////////////
// MenuLayer callbacks
static void prv_draw_row_callback(GContext* ctx, Layer *cell_layer, MenuIndex *cell_index,
DialogsData *data) {
menu_cell_basic_draw(ctx, cell_layer, nodes[cell_index->row].label, NULL, NULL);
}
static void prv_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index,
DialogsData *data) {
nodes[cell_index->row].show(data);
}
static uint16_t prv_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index,
DialogsData *data) {
return NUM_ITEMS;
}
///////////////////
// Window callbacks
static void prv_window_load(Window *window) {
DialogsData *data = window_get_user_data(window);
MenuLayer *menu_layer = &data->menu_layer;
menu_layer_init(menu_layer, &window->layer.bounds);
data->lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
"tempor incididunt ut labore et dolore magna aliqua. Utem ad happy.";
data->long_message = "Don't you see how great this is? You, you are a... Jesse look at me. "
"You... are a blowfish. A blowfish! Think about it. Small in stature, "
"not swift, not cunning. Easy prey for predators but the blowfish has a "
"secret weapon doesn't he. Doesn't he? What does the blowfish do, Jesse."
" What does the blowfish do? The blowfish puffs up, okay?";
data->resource_id_80 = RESOURCE_ID_GENERIC_CONFIRMATION_LARGE;
data->resource_id_25 = RESOURCE_ID_GENERIC_CONFIRMATION_TINY;
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback) prv_get_num_rows_callback,
.draw_row = (MenuLayerDrawRowCallback) prv_draw_row_callback,
.select_click = (MenuLayerSelectCallback) prv_select_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
}
static void prv_handle_init(void) {
DialogsData *data = app_zalloc_check(sizeof(DialogsData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Dialogs"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
static void prv_handle_deinit() {
DialogsData *data = app_state_get_user_data();
menu_layer_deinit(&data->menu_layer);
i18n_free_all(data);
app_free(data);
}
////////////////////
// App boilerplate
static void s_main(void) {
prv_handle_init();
app_event_loop();
prv_handle_deinit();
}
const PebbleProcessMd* dialogs_demo_get_app_info() {
static const PebbleProcessMdSystem s_app_md = {
.common = {
.main_func = s_main,
// UUID: ab470e5f-5ffd-46f2-9aa9-f48352ea5499
.uuid = {0xab, 0x47, 0x0e, 0x5f, 0x5f, 0xfd, 0x46, 0xf2,
0x9a, 0xa9, 0xf4, 0x83, 0x52, 0xea, 0x54, 0x99},
},
.name = "Dialogs",
};
return (const PebbleProcessMd*) &s_app_md;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* dialogs_demo_get_app_info();

View file

@ -0,0 +1,117 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "double_tap_test.h"
#include <stdio.h>
#include "applib/accel_service.h"
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "resource/system_resource.h"
#include "system/logging.h"
#include "kernel/pbl_malloc.h"
typedef struct {
Window window;
TextLayer thumbsup_layer;
TextLayer text_layer;
char text[40];
uint32_t count;
AppTimer *thumbsup_timer;
} AppData;
static void prv_hide_thumbsup(void *ctx) {
AppData *data = ctx;
text_layer_set_text(&data->thumbsup_layer, "");
}
static void prv_show_thumbsup(AppData *data) {
app_timer_cancel(data->thumbsup_timer);
text_layer_set_text(&data->thumbsup_layer, "👍");
data->thumbsup_timer = app_timer_register(1000, prv_hide_thumbsup, data);
}
static void prv_set_tap_text(AppData *data, uint32_t count, AccelAxisType axis) {
char axes[] = {'X', 'Y', 'Z'};
snprintf(data->text, sizeof(data->text), "Axis: %c\nDouble Taps: %6"PRIu32, axes[axis], count);
text_layer_set_text(&data->text_layer, data->text);
}
static void prv_window_load(Window *window) {
AppData *data = window_get_user_data(window);
GSize size = window->layer.frame.size;
TextLayer *thumbsup_layer = &data->thumbsup_layer;
text_layer_init(thumbsup_layer, &(GRect){{0, 1 * (size.h / 3)}, {size.w, 50}});
text_layer_set_font(thumbsup_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28));
text_layer_set_text_alignment(thumbsup_layer, GTextAlignmentCenter);
layer_add_child(&window->layer, (Layer *)thumbsup_layer);
TextLayer *text_layer = &data->text_layer;
text_layer_init(text_layer, &(GRect){{0, 2 * (size.h / 3)}, {size.w, 40}});
text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);
data->count = 0;
prv_set_tap_text(data, 0, 0);
layer_add_child(&window->layer, (Layer *)text_layer);
}
static void prv_handle_tap(AccelAxisType axis, int32_t direction) {
AppData *data = app_state_get_user_data();
prv_set_tap_text(data, ++data->count, axis);
prv_show_thumbsup(data);
}
static void prv_handle_init(void) {
AppData *data = (AppData*) app_malloc(sizeof(AppData));
app_state_set_user_data(data);
window_init(&data->window, WINDOW_NAME("Double Tap Test"));
window_set_user_data(&data->window, data);
window_set_window_handlers(&data->window, &(WindowHandlers) {
.load = prv_window_load });
const bool animated = true;
app_window_stack_push(&data->window, animated);
accel_double_tap_service_subscribe(prv_handle_tap);
}
static void prv_handle_deinit(void) {
AppData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
prv_handle_init();
app_event_loop();
prv_handle_deinit();
}
const PebbleProcessMd* double_tap_test_get_info() {
static const PebbleProcessMdSystem s_accel_config_info = {
.common.main_func = s_main,
.name = "Double Tap Test"
};
return (const PebbleProcessMd*) &s_accel_config_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* double_tap_test_get_info();

View file

@ -0,0 +1,103 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "text_layout.h"
#include "applib/accel_service.h"
#include "applib/app.h"
#include "applib/connection_service.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_manager.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include <stdio.h>
#include <string.h>
typedef struct {
Window window;
TextLayer count_layer;
TextLayer connected_layer;
char count_str[10];
int count;
} EventServiceAppData;
static void handle_tap(AccelAxisType axis, int32_t sign) {
EventServiceAppData *data = app_state_get_user_data();
++data->count;
snprintf(data->count_str, 10, "%d", data->count);
text_layer_set_text(&data->count_layer, data->count_str);
}
static void handle_bt_connection(bool connected) {
EventServiceAppData *data = app_state_get_user_data();
text_layer_set_text(&data->connected_layer, connected ? "connected" : "disconnected");
}
static void handle_deinit(void) {
EventServiceAppData *data = app_state_get_user_data();
app_free(data);
accel_tap_service_unsubscribe();
connection_service_unsubscribe();
}
static void handle_init(void) {
EventServiceAppData *data = app_malloc_check(sizeof(EventServiceAppData));
memset(data, 0, sizeof(EventServiceAppData));
app_state_set_user_data(data);
// Init window
window_init(&data->window, "Event Service Demo");
app_window_stack_push(&data->window, true /* Animated */);
// Init text layer
Layer *windowlayer = &data->window.layer;
const int16_t width = windowlayer->bounds.size.w - ACTION_BAR_WIDTH - 6;
text_layer_init(&data->count_layer, &GRect(0, 0, width, 20));
layer_add_child(&data->window.layer, &data->count_layer.layer);
text_layer_init(&data->connected_layer, &GRect(0, 20, width, 20));
layer_add_child(&data->window.layer, &data->connected_layer.layer);
text_layer_set_text(&data->count_layer, "No Presses");
text_layer_set_text(&data->connected_layer, "No connection event");
// subscribe to the accelerometer event stream
accel_tap_service_subscribe(&handle_tap);
ConnectionHandlers conn_handlers = {
.pebble_app_connection_handler = handle_bt_connection
};
connection_service_subscribe(conn_handlers);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* event_service_app_get_info() {
static const PebbleProcessMdSystem event_service_app_info = {
.common.main_func = &s_main,
.name = "Event Service App",
};
return (const PebbleProcessMd*) &event_service_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* event_service_app_get_info();

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "exit_app.h"
#include "applib/app_logging.h"
// Verify that applications can actually call stdlib's exit().
static void s_exit_app_main(void) {
// Make visible in the debugger if we are running privileged.
uint32_t value;
__asm volatile("mrs %0, control" : "=r" (value));
volatile bool is_privileged = !(value & 0x1);
APP_LOG(LOG_LEVEL_DEBUG, "Exit app is %sprivileged; now exiting", is_privileged ? "" : "not ");
}
const PebbleProcessMd *exit_app_get_app_info(void) {
static const PebbleProcessMdSystem s_exit_app_info = {
.common.main_func = s_exit_app_main,
.common.is_unprivileged = true,
.name = "Exit Test"
};
return (const PebbleProcessMd*) &s_exit_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd *exit_app_get_app_info();

View file

@ -0,0 +1,111 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "flash_demo.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "system/logging.h"
static Window *window;
#define BASE_ADDRESS 0x380000
static void test_write_short(void) {
uint16_t buffer;
flash_read_bytes((uint8_t*) &buffer, BASE_ADDRESS, sizeof(buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Addr 0x%x is 0x%"PRIx16, BASE_ADDRESS, buffer);
buffer = 0x0505;
flash_write_bytes((uint8_t*) &buffer, BASE_ADDRESS, sizeof(buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Addr 0x%x Written to 0x%x", BASE_ADDRESS, buffer);
uint8_t read_buffer = 0x0;
flash_read_bytes((uint8_t*) &read_buffer, BASE_ADDRESS, sizeof(read_buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Addr 0x%x is (8) 0x%"PRIx8, BASE_ADDRESS, read_buffer);
buffer = 0x0;
flash_read_bytes((uint8_t*) &buffer, BASE_ADDRESS, sizeof(buffer));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Addr 0x%x is (16) 0x%"PRIx16, BASE_ADDRESS, buffer);
}
static void test_write_bytes(void) {
for (int i = 1; i < 127; ++i) {
uint8_t data = i;
flash_write_bytes((uint8_t*) &data, BASE_ADDRESS + i, sizeof(data));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Wrote Addr 0x%x is 0x%"PRIx8, i, data);
}
for (int i = 0; i < 128; ++i) {
uint8_t data = 0;
flash_read_bytes((uint8_t*) &data, BASE_ADDRESS + i, sizeof(data));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read Addr 0x%x is (8) 0x%"PRIx8, i, data);
}
}
static void test_write_block(void) {
uint8_t data[64];
for (unsigned int i = 0; i < sizeof(data); ++i) {
data[i] = i;
}
flash_write_bytes(data, BASE_ADDRESS + 31, sizeof(data));
for (int i = 0; i < 128; ++i) {
uint8_t data = 0;
flash_read_bytes((uint8_t*) &data, BASE_ADDRESS + i, sizeof(data));
PBL_LOG(LOG_LEVEL_DEBUG, ">> Read Addr 0x%x is (8) 0x%"PRIx8, i, data);
}
}
static void do_flash_operation(void) {
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Flash operation time!");
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing 0x%x", BASE_ADDRESS);
flash_erase_sector_blocking(BASE_ADDRESS);
PBL_LOG(LOG_LEVEL_DEBUG, ">> Erasing 0x%x Done", BASE_ADDRESS);
test_write_short();
}
static void s_main(void) {
window = window_create();
app_window_stack_push(window, true /* Animated */);
do_flash_operation();
app_event_loop();
}
const PebbleProcessMd* flash_demo_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Flash Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* flash_demo_get_app_info(void);

View file

@ -0,0 +1,340 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "flash_diagnostic_app.h"
#include <stdio.h>
#include "applib/app.h"
#include "applib/ui/simple_menu_layer.h"
#include "applib/ui/ui.h"
#include "applib/ui/window.h"
#include "drivers/flash.h"
#include "drivers/task_watchdog.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "kernel/pebble_tasks.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "resource/resource_storage_flash.h"
#include "system/logging.h"
#include "system/passert.h"
#include "kernel/util/sleep.h"
#include "util/size.h"
#define NUM_REGIONS ARRAY_LENGTH(s_flash_regions)
#define FILE_WRITE_STRESS (NUM_REGIONS)
#define FILE_SUBSECTOR_STRESS (FILE_WRITE_STRESS + 1)
#define NUM_STRESS_TESTS (FILE_SUBSECTOR_STRESS - NUM_REGIONS + 1)
#define NUM_MENU_ITEMS (NUM_REGIONS + NUM_STRESS_TESTS)
struct Region {
char *name;
uint32_t begin;
uint32_t end;
};
static struct Region s_flash_regions[] = {
{
"System Resources",
FLASH_REGION_SYSTEM_RESOURCES_BANK_0_BEGIN,
FLASH_REGION_SYSTEM_RESOURCES_BANK_0_END
},
{
"System Resources",
FLASH_REGION_SYSTEM_RESOURCES_BANK_1_BEGIN,
FLASH_REGION_SYSTEM_RESOURCES_BANK_1_END
},
{
"File System",
FLASH_REGION_FILESYSTEM_BEGIN,
FLASH_REGION_FILESYSTEM_END
},
};
typedef struct {
Window window;
SimpleMenuLayer menu_layer;
SimpleMenuSection menu_section;
SimpleMenuItem menu_items[NUM_MENU_ITEMS];
} FlashDiagAppData;
typedef struct {
Window window;
TextLayer *text_layer;
int stress_iteration;
int stress_index;
} FlashStressWindow;
static bool check_region_erased(struct Region region) {
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Checking Erase ...");
bool success = true;
for (uint32_t i = region.begin; i < region.end; i += sizeof(uint32_t)) {
uint32_t read = 0;
flash_read_bytes((uint8_t *)&read, i, sizeof(read));
if (read != 0xffffffff) {
PBL_LOG_SYNC(LOG_LEVEL_INFO, ">>>> Address 0x%lx failed to erase: 0x%lx", i, read);
success = false;
}
}
return success;
}
// region: region to check (and possibly write)
// use_rand: write random values
// perform_writes: perform writes if true, else see if the region reads as 0
static bool check_region_write(struct Region region, bool use_rand,
bool perform_writes) {
bool success = true;
uint32_t write_rand = (use_rand && perform_writes) ? rand() : 0;
PBL_LOG_SYNC(LOG_LEVEL_INFO, "%sChecking 0x%lx over 0x%lx 0x%lx",
(perform_writes) ? "Writing and " : "", write_rand, region.begin,
region.end);
for (uint32_t i = region.begin; i < region.end; i += sizeof(uint32_t)) {
uint32_t write = write_rand;
uint32_t read = 0xffff;
if (perform_writes) {
flash_write_bytes((uint8_t *)&write, i, sizeof(write));
}
flash_read_bytes((uint8_t *)&read, i, sizeof(read));
if (read != write) {
PBL_LOG_SYNC(LOG_LEVEL_INFO, ">>>> Address 0x%lx failed to write: 0x%lx 0x%lx",
i, read, write);
success = false;
}
}
return success;
}
// Writes 0's to the first half of a flash sector and confirms that everything
// reads as 0. Then uses 8 subsector erases to erase the second half of the
// sector. Then re-reads the first half to see if any bits have flipped
static bool check_subsector_bitflip(struct Region region) {
#if !CAPABILITY_USE_PARALLEL_FLASH
bool success = true;
if (((region.end - region.begin) % (64 * 1024)) != 0) {
PBL_LOG(LOG_LEVEL_WARNING, "Test only works on 64k aligned regions");
return (false);
}
const uint32_t block_size = 64 * 1024;
const uint32_t write_size = 32 * 1024;
const uint32_t subsector_size = 4 * 1024;
for (uint32_t i = region.begin; i < region.end; i += block_size) {
struct Region write_region;
write_region.begin = i;
write_region.end = i + write_size;
if (!check_region_write(write_region, false, true)) {
success = false;
break;
}
uint32_t subsec_begin = block_size - write_size;
PBL_ASSERTN((subsec_begin % (32*1024)) == 0);
for (uint32_t subsec = subsec_begin; subsec < block_size;
subsec += subsector_size) {
uint32_t erase = subsec + i;
PBL_ASSERTN((erase % (4 * 1024)) == 0);
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Subsector Erase of 0x%lx", erase);
flash_erase_subsector_blocking(erase);
}
if (!check_region_write(write_region, false, false)) {
success = false;
break;
}
psleep(5);
}
return (success);
#else
PBL_LOG_SYNC(LOG_LEVEL_INFO, "Test not supported for parallel flash");
return (false);
#endif
}
static void menu_select_callback(int index, void *data) {
struct Region region = s_flash_regions[index];
PBL_LOG(LOG_LEVEL_INFO, ">>>> Erase %s", region.name);
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
PBL_LOG(LOG_LEVEL_INFO, ">>>> Checking '%s' is erased", region.name);
check_region_erased(region);
PBL_LOG(LOG_LEVEL_INFO, ">>>> Checking '%s' can write", region.name);
check_region_write(region, false, true);
PBL_LOG(LOG_LEVEL_INFO, ">>>> Done!");
}
FlashStressWindow stress_data;
static bool abort_stress_test;
static void update_text(int iter, int tot, bool failed) {
static char status[50];
snprintf(status, sizeof(status), "%d / %d %s", iter, tot,
failed ? "Failed Out" : "Complete");
text_layer_set_text(stress_data.text_layer, (char *)status);
}
static void app_timer_cb(void *data) {
static const int num_stress_iters = 1000;
struct Region region = s_flash_regions[2];
PBL_LOG(LOG_LEVEL_INFO, ">>>> %s %d", "Test Loop", stress_data.stress_iteration);
PBL_LOG(LOG_LEVEL_INFO, "Erasing 0x%lx to 0x%lx", region.begin, region.end);
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
bool failed = true;
if (stress_data.stress_index == FILE_WRITE_STRESS) {
failed = (!check_region_erased(region) || !check_region_write(region, true, true));
} else if (stress_data.stress_index == FILE_SUBSECTOR_STRESS) {
failed = (!check_region_erased(region) || !check_subsector_bitflip(region));
} else {
PBL_LOG(LOG_LEVEL_WARNING, "Unknown stress test %d!", stress_data.stress_index);
}
if (!abort_stress_test) {
update_text(++stress_data.stress_iteration, num_stress_iters, failed);
if (!failed && (stress_data.stress_iteration < num_stress_iters)) {
app_timer_register(1000, app_timer_cb, NULL); // allow for animation to complete
} else { // clean up state
flash_region_erase_optimal_range(region.begin, region.begin, region.end, region.end);
}
}
}
static void stress_window_load(Window *data) {
Layer *layer = window_get_root_layer(data);
const int16_t width = layer->frame.size.w - ACTION_BAR_WIDTH - 3;
stress_data.text_layer = text_layer_create(GRect(4, 44, width, 60));
TextLayer *text_layer = stress_data.text_layer;
text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
text_layer_set_background_color(text_layer, GColorClear);
layer_add_child(layer, text_layer_get_layer(text_layer));
text_layer_set_text(stress_data.text_layer, "Starting Stress Test");
abort_stress_test = false;
app_timer_register(500, app_timer_cb, NULL);
};
static void stress_window_unload(Window *data) {
abort_stress_test = true;
}
static void file_system_stress_callback(int index, void *data) {
stress_data.stress_iteration = 0;
stress_data.stress_index = index;
window_init(&stress_data.window, WINDOW_NAME("Stress Test"));
window_set_user_data(&stress_data.window, &stress_data);
window_set_window_handlers(&stress_data.window, &(WindowHandlers) {
.load = stress_window_load,
.unload = stress_window_unload
});
app_window_stack_push(&stress_data.window, true);
}
static void populate_menu(SimpleMenuSection *menu_section, SimpleMenuItem *menu_items) {
for (unsigned int i = 0; i < NUM_REGIONS; ++i) {
menu_items[i] = (SimpleMenuItem) {
.title = s_flash_regions[i].name,
.callback = menu_select_callback,
};
}
menu_items[FILE_WRITE_STRESS] = (SimpleMenuItem) {
.title = "File Stress",
.callback = file_system_stress_callback,
};
menu_items[FILE_SUBSECTOR_STRESS] = (SimpleMenuItem) {
.title = "Subsector Stress",
.callback = file_system_stress_callback,
};
menu_section->num_items = NUM_MENU_ITEMS;
menu_section->items = menu_items;
menu_section->title = "Flash Regions";
}
static void prv_window_load(Window *window) {
FlashDiagAppData *data = window_get_user_data(window);
populate_menu(&data->menu_section, data->menu_items);
Layer *root_layer = window_get_root_layer(window);
const GRect *bounds = &root_layer->bounds;
simple_menu_layer_init(&data->menu_layer, bounds, window, &data->menu_section, 1, NULL);
layer_add_child(root_layer, simple_menu_layer_get_layer(&data->menu_layer));
}
static void push_window(FlashDiagAppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Flash Diagnostic"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init(void) {
FlashDiagAppData *data = (FlashDiagAppData*) app_malloc_check(sizeof(FlashDiagAppData));
if (data == NULL) {
PBL_CROAK("Out of memory");
}
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit(void) {
FlashDiagAppData *data = app_state_get_user_data();
simple_menu_layer_deinit(&data->menu_layer);
app_free(data);
}
static void s_main(void) {
if (resource_storage_flash_get_unused_bank()->begin ==
s_flash_regions[0].begin) {
s_flash_regions[0].name = "Unused Resources";
} else {
s_flash_regions[1].name = "Unused Resources";
}
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* flash_diagnostic_app_get_info() {
static const PebbleProcessMdSystem s_flash_diagnostic_app_info = {
.common.main_func = s_main,
.name = "Flash Diagnostic"
};
return (const PebbleProcessMd*) &s_flash_diagnostic_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* flash_diagnostic_app_get_info();

View file

@ -0,0 +1,84 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "process_management/pebble_process_md.h"
#include "applib/app.h"
#include "system/logging.h"
#include "drivers/flash.h"
#include "drivers/rtc.h"
#include "flash_region/flash_region.h"
#include "system/passert.h"
#include "kernel/pbl_malloc.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/number_window.h"
#include "applib/ui/window_stack.h"
#include "FreeRTOS.h"
static NumberWindow number_window;
static uint32_t timed_read_bytes(uint32_t num_bytes) {
uint8_t *buffer = kernel_malloc_check(num_bytes);
time_t start_time_s;
uint16_t start_time_ms;
rtc_get_time_ms(&start_time_s, &start_time_ms);
flash_read_bytes(buffer, FLASH_REGION_FILESYSTEM_BEGIN, num_bytes);
time_t stop_time_s;
uint16_t stop_time_ms;
rtc_get_time_ms(&stop_time_s, &stop_time_ms);
kernel_free(buffer);
return ((stop_time_s * 1000 + stop_time_ms) - (start_time_s * 1000 + start_time_ms));
}
static void do_timed_read(NumberWindow *nw, void *data) {
uint32_t num_bytes = nw->value;
uint32_t predicted_time = num_bytes * 8 / 16000;
uint32_t time = timed_read_bytes(num_bytes);
PBL_LOG(LOG_LEVEL_DEBUG, "time to read %lu bytes: predicted %lu, actual %lu", num_bytes, predicted_time, time);
window_stack_remove(&number_window.window, false);
app_window_stack_push(&number_window.window, true);
}
#define NUM_BYTES 1000
static void handle_init(void) {
number_window_init(&number_window, "Num Writes", (NumberWindowCallbacks) {
.selected = (NumberWindowCallback) do_timed_read,
}, NULL);
number_window_set_min(&number_window, 1000);
number_window_set_max(&number_window, 1000000);
number_window_set_step_size(&number_window, 1000);
app_window_stack_push((Window *)&number_window, true);
}
static void handle_deinit(void) {
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* flash_prof_get_app_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Flash Prof"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* flash_prof_get_app_info();

View file

@ -0,0 +1,331 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This app only makes sense on Snowy, as it uses addresses and sector sizes that only make sense
// on our parallel flash hardware
#if CAPABILITY_USE_PARALLEL_FLASH
#include "flash_test.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/simple_menu_layer.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack.h"
#include "mfg/mfg_apps/mfg_flash_test.h"
#include "process_state/app_state/app_state.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "kernel/pbl_malloc.h"
#include <stdio.h>
enum FlashTestCaseStatus {
FLASH_TEST_STATUS_INIT = 0,
FLASH_TEST_STATUS_RUNNING = 1,
FLASH_TEST_STATUS_STOPPED = 2,
FLASH_TEST_STATUS_PASSED = 3,
FLASH_TEST_STATUS_FAILED = 4,
};
#define STATUS_TEXT_SIZE 18
struct FlashTestData {
Window window;
SimpleMenuLayer simple_menu_layer;
SimpleMenuSection menu_sections[1];
SimpleMenuItem menu_items[FLASH_TEST_CASE_NUM_MENU_ITEMS];
Window test_window;
TextLayer msg_text_layer[3];
TextLayer status_text_layer;
char status_text[STATUS_TEXT_SIZE];
FlashTestCaseType test_case;
uint8_t test_case_status;
};
/***********************************************************/
/******************* Window Related Functions **************/
/***********************************************************/
static void handle_timer(struct tm *tick_time, TimeUnits units_changed) {
struct FlashTestData *data = app_state_get_user_data();
// simply marking the window dirty will make everything update
if (data && (data->test_case_status != FLASH_TEST_STATUS_INIT))
{
layer_mark_dirty(&data->test_window.layer);
}
}
static void update_test_case_status(struct FlashTestData *data) {
switch (data->test_case_status) {
case FLASH_TEST_STATUS_INIT:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Initialized");
break;
case FLASH_TEST_STATUS_RUNNING:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Running");
break;
case FLASH_TEST_STATUS_STOPPED:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Stopped");
break;
case FLASH_TEST_STATUS_PASSED:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Passed");
break;
case FLASH_TEST_STATUS_FAILED:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Test Failed");
break;
default:
snprintf(data->status_text, STATUS_TEXT_SIZE, "Unknown Status");
PBL_LOG(LOG_LEVEL_DEBUG, "ERROR: Unknown Test Case Selected");
break;
}
text_layer_set_text(&data->status_text_layer, data->status_text);
}
static void test_window_load(Window *window) {
struct FlashTestData *data = app_state_get_user_data();
window_set_background_color(window, GColorBlack);
TextLayer *msg_text_layer = &data->msg_text_layer[0];
text_layer_init(msg_text_layer, &GRect(0, 12, window->layer.bounds.size.w, 18));
switch (data->test_case) {
case FLASH_TEST_CASE_RUN_DATA_TEST:
text_layer_set_text(msg_text_layer, "Data Bus Test");
break;
case FLASH_TEST_CASE_RUN_ADDR_TEST:
text_layer_set_text(msg_text_layer, "Addr Bus Test");
break;
case FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST:
text_layer_set_text(msg_text_layer, "Stress Addr Test");
break;
case FLASH_TEST_CASE_RUN_PERF_DATA_TEST:
text_layer_set_text(msg_text_layer, "Perf Data Test");
break;
case FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC:
case FLASH_TEST_CASE_RUN_SWITCH_MODE_SYNC_BURST:
text_layer_set_text(msg_text_layer, "Switch Mode");
break;
default:
text_layer_set_text(msg_text_layer, "Unknown Test");
break;
}
text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
text_layer_set_background_color(msg_text_layer, GColorBlack);
text_layer_set_text_color(msg_text_layer, GColorWhite);
text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
layer_add_child(&window->layer, &msg_text_layer->layer);
msg_text_layer = &data->msg_text_layer[1];
text_layer_init(msg_text_layer, &GRect(0, 32, window->layer.bounds.size.w, 18));
if (data->test_case >= FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC) {
text_layer_set_text(msg_text_layer, "Select To Confirm Switch");
}
else {
text_layer_set_text(msg_text_layer, "Press Select To Start");
}
text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
text_layer_set_background_color(msg_text_layer, GColorBlack);
text_layer_set_text_color(msg_text_layer, GColorWhite);
text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
layer_add_child(&window->layer, &msg_text_layer->layer);
msg_text_layer = &data->msg_text_layer[2];
text_layer_init(msg_text_layer, &GRect(0, 64, window->layer.bounds.size.w, 40));
if (data->test_case >= FLASH_TEST_CASE_RUN_SWITCH_MODE_ASYNC) {
text_layer_set_text(msg_text_layer, "Press Back To Exit");
}
else {
text_layer_set_text(msg_text_layer, "Press Up to Exit, Down to Stop Test");
}
text_layer_set_text_alignment(msg_text_layer, GTextAlignmentCenter);
text_layer_set_background_color(msg_text_layer, GColorBlack);
text_layer_set_text_color(msg_text_layer, GColorWhite);
text_layer_set_font(msg_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
layer_add_child(&window->layer, &msg_text_layer->layer);
TextLayer *status_text_layer = &data->status_text_layer;
text_layer_init(status_text_layer, &GRect(0, 106, window->layer.bounds.size.w, 40));
text_layer_set_text_alignment(status_text_layer, GTextAlignmentCenter);
text_layer_set_background_color(status_text_layer, GColorBlack);
text_layer_set_text_color(status_text_layer, GColorWhite);
text_layer_set_font(status_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD));
layer_add_child(&window->layer, &status_text_layer->layer);
update_test_case_status(data);
}
void flash_test_dismiss_window(void) {
struct FlashTestData *data = app_state_get_user_data();
const bool animated = true;
window_stack_remove(&data->test_window, animated);
}
static void up_click_handler(ClickRecognizerRef recognizer, void *unusued) {
struct FlashTestData *data = app_state_get_user_data();
if (data->test_case_status != FLASH_TEST_STATUS_RUNNING) {
flash_test_dismiss_window();
}
}
static void down_click_handler(ClickRecognizerRef recognizer, void *unusued) {
struct FlashTestData *data = app_state_get_user_data();
// Only stop stress test
if ((data->test_case == FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST) && (data->test_case_status == FLASH_TEST_STATUS_RUNNING)) {
data->test_case_status = FLASH_TEST_STATUS_STOPPED;
stop_flash_test_case();
update_test_case_status(data);
}
}
static void run_test(void* context) {
struct FlashTestData *data = app_state_get_user_data();
FlashTestErrorType status = FLASH_TEST_SUCCESS;
// Execute test - pass in 0 by default for iterations
status = run_flash_test_case(data->test_case, 0);
if (status == FLASH_TEST_SUCCESS) {
data->test_case_status = FLASH_TEST_STATUS_PASSED;
}
else {
PBL_LOG(LOG_LEVEL_DEBUG, ">>>>>FAILED TEST CASE<<<<<");
data->test_case_status = FLASH_TEST_STATUS_FAILED;
}
update_test_case_status(data);
}
static void select_click_handler(ClickRecognizerRef recognizer, void *unusued) {
struct FlashTestData *data = app_state_get_user_data();
if ((data->test_case == FLASH_TEST_CASE_RUN_STRESS_ADDR_TEST) && (data->test_case_status == FLASH_TEST_STATUS_RUNNING)) {
data->test_case_status = FLASH_TEST_STATUS_STOPPED;
stop_flash_test_case();
update_test_case_status(data);
}
else if (data->test_case_status == FLASH_TEST_STATUS_INIT) {
data->test_case_status = FLASH_TEST_STATUS_RUNNING;
update_test_case_status(data);
system_task_add_callback(run_test, NULL);
}
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler)up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler)down_click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler)select_click_handler);
}
static void flash_test_select_callback(int index, void *context) {
struct FlashTestData *data = (struct FlashTestData *) context;
data->test_case = index;
data->test_case_status = FLASH_TEST_STATUS_INIT;
// Display window for running test case
// Init the window
Window *test_window = &data->test_window;
window_init(test_window, WINDOW_NAME("Test Case"));
window_set_window_handlers(test_window, &(WindowHandlers) {
.load = test_window_load,
});
window_set_user_data(test_window, data);
window_set_click_config_provider(test_window, click_config_provider);
const bool animated = true;
app_window_stack_push(test_window, animated);
}
static void flash_test_window_load(Window *window) {
struct FlashTestData *data = (struct FlashTestData*) window_get_user_data(window);
// Configure menu items:
uint8_t num_items = 0;
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Data Test",
.callback = flash_test_select_callback};
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Address Test",
.callback = flash_test_select_callback};
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Stress Test",
.callback = flash_test_select_callback};
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "Run Perf Data Test",
.callback = flash_test_select_callback};
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "-->Async Mode",
.callback = flash_test_select_callback};
data->menu_items[num_items++] = (SimpleMenuItem) { .title = "-->Sync Burst Mode",
.callback = flash_test_select_callback};
data->menu_sections[0].num_items = num_items;
data->menu_sections[0].items = data->menu_items;
// Configure simple menu:
const GRect *bounds = &data->window.layer.bounds;
simple_menu_layer_init(&data->simple_menu_layer, bounds, window, data->menu_sections, 1, data);
layer_add_child(&data->window.layer, simple_menu_layer_get_layer(&data->simple_menu_layer));
}
static void handle_init(void) {
struct FlashTestData* data_ptr = app_malloc_check(sizeof(struct FlashTestData));
memset(data_ptr, 0, sizeof(struct FlashTestData));
app_state_set_user_data(data_ptr);
window_init(&data_ptr->window, WINDOW_NAME("Flash Test"));
window_set_user_data(&data_ptr->window, data_ptr);
window_set_window_handlers(&data_ptr->window, &(WindowHandlers){
.load = flash_test_window_load,
});
const bool animated = true;
app_window_stack_push(&data_ptr->window, animated);
tick_timer_service_subscribe(SECOND_UNIT, handle_timer);
}
static void handle_deinit(void) {
struct FlashTestData *data = app_state_get_user_data();
simple_menu_layer_deinit(&data->simple_menu_layer);
app_free(data);
stop_flash_test_case();
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* flash_test_demo_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Flash Test"
};
return (const PebbleProcessMd*) &s_app_info;
}
#endif // CAPABILITY_USE_PARALLEL_FLASH

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* flash_test_demo_get_app_info(void);

View file

@ -0,0 +1,275 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "fps_test_app.h"
#include "fps_test_app_bitmaps.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/text.h"
#include "applib/graphics/gtypes.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "applib/ui/bitmap_layer.h"
#include "applib/ui/menu_layer.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "system/profiler.h"
#include "util/size.h"
typedef struct AppData {
Window window;
BitmapLayer background_layer;
BitmapLayer topleft_layer;
MenuLayer action_list1;
MenuLayer action_list2;
ScrollLayerCallback prv_orig_content_offset_changed;
int64_t time_started;
uint32_t rendered_frames;
} AppData;
static int64_t prv_time_64(void) {
time_t s;
uint16_t ms;
rtc_get_time_ms(&s, &ms);
return (int64_t)s * 1000 + ms;
}
static void prv_redraw_timer_cb(void *cb_data) {
AppData *data = app_state_get_user_data();
layer_mark_dirty(&data->window.layer);
app_timer_register(0, prv_redraw_timer_cb, NULL);
}
/*****************************************************************************************
Stop our timer and display results.
A frame update consists of the following operations:
op_1) App renders to its own frame buffer
op_2) System copies the app frame buffer to the system frame buffer
op_3) System sends the system frame buffer to the display hardware (using DMA).
op_3 can happen in parallel with op_1, so the effective frame period is:
frame_period = MAX(op_1_time + op_2_time, op2_time + op_3_time)
This app measures op_1_time + op_2_time and does so by counting the number of times
the app window's update callback got called within a set amount of time. The window update
callback only does op1, but the app_render_handler() method in app.c insures that a window
update is not called again until op_2 has completed for the previous update. This throttling
if the app's window update also insures that:
(op_1_time + op_2_time) is always >= (op_2_time + op_3_time)
To measure op_1, we use a profiler timer node called "render". This timer
measures the amount of time we spend in the window_render() method.
To measure op_2, we use a profiler timer node called "framebuffer_prepare". This timer
measures the amount of time we spend copying the app's frame buffer to the system framebuffer
To measure op_3, we use a profiler timer node called "framebuffer_send". This profiler timer
measures the amount of time we spend waiting for a display DMA to complete.
op_1 can be computed from the app's update period - op_2_time
*/
static void prv_pop_all_windows_cb(void *cb_data) {
// Print profiler stats which include the time spent copying the app frame buffer to the
// system frame buffer and the time spent sending the system frame buffer to the display.
PROFILER_STOP;
PROFILER_PRINT_STATS;
AppData *data = app_state_get_user_data();
int64_t time_rendered = prv_time_64() - data->time_started;
PBL_LOG(LOG_LEVEL_INFO, "## %d frames rendered", (int)data->rendered_frames);
if (time_rendered) {
int frame_period = time_rendered/(int64_t)data->rendered_frames;
int fps = (int64_t)data->rendered_frames*1000/time_rendered;
PBL_LOG(LOG_LEVEL_INFO, "## at %d FPS (%d ms/frame)", fps, frame_period);
}
app_window_stack_pop_all(false);
}
static const char *prv_row_texts[] = {
"Row 1",
"Row 2",
"Row 3",
"Row 4",
"Row 5",
"Row 6",
};
static uint16_t prv_get_num_rows(struct MenuLayer *menu_layer, uint16_t section_index,
void *callback_context) {
return ARRAY_LENGTH(prv_row_texts);
}
static void prv_draw_row(GContext* ctx, const Layer *cell_layer, char const *title,
int16_t offset) {
// mostly copied from menu_cell_basic_draw_with_value
// (that unfortunately doesn't respect bounds.origin.x)
const int16_t title_height = 24;
GRect box = cell_layer->bounds;
box.origin.x += offset;
box.origin.y = (box.size.h - title_height) / 2;
box.size.w -= offset;
box.size.h = title_height + 4;
const GFont title_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
if (title) {
graphics_context_set_text_color_2bit(ctx, GColor2White);
graphics_draw_text(ctx, title, title_font, box,
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
}
void prv_draw_row_1(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *callback_context) {
const char *title = prv_row_texts[cell_index->row];
prv_draw_row(ctx, cell_layer, title, -cell_layer->frame.origin.y/4);
}
static void prv_draw_row_2(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *callback_context) {
const char *title = prv_row_texts[cell_index->row];
prv_draw_row(ctx, cell_layer, title, -cell_layer->frame.origin.y/4 + cell_layer->bounds.size.w);
}
static int16_t prv_get_separator_height(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *callback_context) {
return 0;
}
static void prv_window_update_proc(struct Layer *layer, GContext *ctx) {
AppData *data = app_state_get_user_data();
if (data->rendered_frames == 0) {
data->time_started = prv_time_64();
PROFILER_INIT;
PROFILER_START;
}
data->rendered_frames++;
}
static void prv_window_disapper(Window *window) {
}
void prv_syncing_content_offset_changed(struct ScrollLayer *scroll_layer, void *context) {
AppData *data = app_state_get_user_data();
data->prv_orig_content_offset_changed(scroll_layer, context);
GPoint offset = scroll_layer_get_content_offset(scroll_layer);
scroll_layer_set_content_offset(&data->action_list1.scroll_layer, offset, false);
}
static void prv_window_load(Window *window) {
// creates a structure as outlined at
// https://pebbletechnology.atlassian.net/wiki/display/DEV/3.0+Notifications+UI+MVP
// it's one full screen background image .background_layer,
// one image at the top left .topleft_layer,
// and two menu layers .action_list1 and .action_list2 that overlay each other
// some hackery with the two menu layers goes on to keep their scroll offest in sync
// and to have the inverter layer rendered only once
const int16_t navbar_width = s_fps_topleft_bitmap.bounds.size.w;
AppData *data = window_get_user_data(window);
const GRect *full_rect = &window->layer.bounds;
bitmap_layer_init(&data->background_layer, full_rect);
bitmap_layer_set_background_color_2bit(&data->background_layer, GColor2Black);
bitmap_layer_set_bitmap(&data->background_layer, &s_fps_background_bitmap);
layer_add_child(&window->layer, &data->background_layer.layer);
bitmap_layer_init(&data->topleft_layer, &GRect(0, 0, navbar_width, navbar_width));
bitmap_layer_set_background_color_2bit(&data->topleft_layer, GColor2White);
bitmap_layer_set_bitmap(&data->topleft_layer, &s_fps_topleft_bitmap);
// PBL_LOG(LOG_LEVEL_DEBUG, "heap_allocated: %d", s_fps_topleft_bitmap.is_heap_allocated);
// PBL_LOG(LOG_LEVEL_DEBUG, "bitmapformat: %d", s_fps_topleft_bitmap.format);
layer_add_child(&window->layer, &data->topleft_layer.layer);
const GRect menu_layer_rect =
GRect(navbar_width, 0, full_rect->size.w - navbar_width, full_rect->size.h);
menu_layer_init(&data->action_list1, &menu_layer_rect);
menu_layer_set_callbacks(&data->action_list1, NULL, &(MenuLayerCallbacks){
.get_num_rows = prv_get_num_rows,
.draw_row = prv_draw_row_1,
.get_separator_height = prv_get_separator_height,
});
layer_set_hidden(&data->action_list1.inverter.layer, true);
scroll_layer_set_shadow_hidden(&data->action_list1.scroll_layer, true);
layer_add_child(&window->layer, menu_layer_get_layer(&data->action_list1));
menu_layer_init(&data->action_list2, &menu_layer_rect);
menu_layer_set_callbacks(&data->action_list2, NULL, &(MenuLayerCallbacks){
.get_num_rows = prv_get_num_rows,
.draw_row = prv_draw_row_2,
.get_separator_height = prv_get_separator_height,
});
scroll_layer_set_shadow_hidden(&data->action_list2.scroll_layer, true);
data->prv_orig_content_offset_changed =
data->action_list2.scroll_layer.callbacks.content_offset_changed_handler;
data->action_list2.scroll_layer.callbacks.content_offset_changed_handler =
prv_syncing_content_offset_changed;
menu_layer_set_click_config_onto_window(&data->action_list2, window);
layer_add_child(&window->layer, menu_layer_get_layer(&data->action_list2));
// start infinite update loop
prv_redraw_timer_cb(NULL);
// run application for a given time, than terminate
app_timer_register(5000, prv_pop_all_windows_cb, NULL);
}
static void prv_window_unload(Window *window) {
AppData *data = window_get_user_data(window);
menu_layer_deinit(&data->action_list1);
menu_layer_deinit(&data->action_list2);
}
static void s_main(void) {
AppData *data = app_malloc_check(sizeof(AppData));
memset(data, 0, sizeof(AppData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("FPS test"));
window_set_user_data(window, data);
window_set_fullscreen(window, true);
layer_set_update_proc(&window->layer, prv_window_update_proc);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
.unload = prv_window_unload,
.disappear = prv_window_disapper,
});
app_window_stack_push(window, true);
PROFILER_INIT;
PROFILER_START;
app_event_loop();
}
const PebbleProcessMd* fps_test_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "FPS Test"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* fps_test_get_app_info(void);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,176 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gdrawmask_demo.h"
#include "applib/app.h"
#include "applib/app_light.h"
#include "process_state/app_state/app_state.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "syscall/syscall.h"
#include "util/size.h"
#include "util/trig.h"
typedef struct {
Window window;
} GDrawMaskDemoData;
static void prv_draw_text(GContext *ctx, const GRect *layer_bounds) {
graphics_context_set_text_color(ctx, GColorBlack);
const char *text = PBL_IF_RECT_ELSE("Masks are fun!", "\nMasks are fun!");
GTextAttributes *text_attributes = graphics_text_attributes_create();
const uint8_t inset = 4;
graphics_text_attributes_enable_screen_text_flow(text_attributes, inset);
graphics_draw_text(ctx, text, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD), *layer_bounds,
GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, text_attributes);
graphics_text_attributes_destroy(text_attributes);
}
void prv_fill_mask_shape(GContext *ctx, const GRect *layer_bounds, const GSize *shape_size,
int32_t current_angle) {
const GOvalScaleMode oval_scale_mode = GOvalScaleModeFitCircle;
// Calculate the radial rect
const GRect inset_layer_bounds = grect_inset((*layer_bounds), GEdgeInsets(shape_size->h / 2));
const GRect shape_rect = grect_centered_from_polar(inset_layer_bounds, oval_scale_mode,
current_angle, (*shape_size));
// Fill the radial circle
graphics_fill_oval(ctx, shape_rect, oval_scale_mode);
}
static void prv_layer_update_proc(Layer *layer, GContext *ctx) {
GDrawMaskDemoData *data = app_state_get_user_data();
if (!data) {
return;
}
const GRect layer_bounds = layer_get_bounds_by_value(layer);
// Fill the background
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_rect(ctx, &layer_bounds);
// Draw the text
prv_draw_text(ctx, &layer_bounds);
// Create the mask and start recording
const bool transparent = false;
GDrawMask *mask = graphics_context_mask_create(ctx, transparent);
graphics_context_mask_record(ctx, mask);
// The number of milliseconds it should take each of the shapes to make a full revolution
const uint16_t full_revolution_time_ms = 4000;
// Use the current system time to calculate the current animation progress
time_t system_time_seconds;
uint16_t system_time_ms;
sys_get_time_ms(&system_time_seconds, &system_time_ms);
const uint16_t current_time_progress_ms =
(uint16_t)((system_time_seconds % (full_revolution_time_ms / MS_PER_SECOND)) * MS_PER_SECOND +
(system_time_ms % MS_PER_SECOND));
const AnimationProgress animation_progress =
current_time_progress_ms * ANIMATION_NORMALIZED_MAX / full_revolution_time_ms;
const GColor mask_colors[3] = {
GColorLightGray,
GColorDarkGray,
GColorBlack
};
const unsigned int num_mask_levels = ARRAY_LENGTH(mask_colors);
const int16_t shape_width =
(int16_t)(MIN(layer_bounds.size.w, layer_bounds.size.h) / 2);
const GSize shape_size = GSize(shape_width, shape_width);
for (unsigned int i = 0; i < num_mask_levels; i++) {
// Calculate the angle of the mask shape using the current animation progress
// Offset the angle to space each of the mask shapes equally apart
const uint32_t starting_angle = (i * TRIG_MAX_ANGLE / num_mask_levels);
const int32_t progress_angle_delta =
animation_progress * TRIG_MAX_ANGLE / ANIMATION_NORMALIZED_MAX;
const int32_t current_angle = normalize_angle(starting_angle + progress_angle_delta);
// Set the color to fill, progressing through each of the mask levels
graphics_context_set_fill_color(ctx, mask_colors[i]);
// Fill the mask shape
prv_fill_mask_shape(ctx, &layer_bounds, &shape_size, current_angle);
}
// Activate the mask and fill the entire layer with a red rectangle
graphics_context_mask_use(ctx, mask);
graphics_context_set_fill_color(ctx, GColorRed);
graphics_fill_rect(ctx, &layer_bounds);
graphics_context_mask_destroy(ctx, mask);
}
static void prv_refresh_timer_callback(void *context) {
GDrawMaskDemoData *data = context;
layer_mark_dirty(window_get_root_layer(&data->window));
app_timer_register(ANIMATION_TARGET_FRAME_INTERVAL_MS, prv_refresh_timer_callback, data);
}
static void prv_window_load(Window *window) {
GDrawMaskDemoData *data = window_get_user_data(window);
Layer *window_root_layer = window_get_root_layer(window);
layer_set_update_proc(window_root_layer, prv_layer_update_proc);
app_timer_register(ANIMATION_TARGET_FRAME_INTERVAL_MS, prv_refresh_timer_callback, data);
}
static void handle_init(void) {
GDrawMaskDemoData *data = app_zalloc_check(sizeof(*data));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("GDrawMask Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers){
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
app_light_enable(true);
}
static void handle_deinit(void) {
app_light_enable(false);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd *gdrawmask_demo_get_app_info() {
static const PebbleProcessMdSystem gdrawmask_demo_app_info = {
.common.main_func = s_main,
.name = "GDrawMask Demo"
};
return (const PebbleProcessMd*) &gdrawmask_demo_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd *gdrawmask_demo_get_app_info();

View file

@ -0,0 +1,231 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
static GRect rect;
static GPoint center;
static GOvalScaleMode scale_mode;
static int inset;
static int outer_size;
static int inner_size;
static int32_t angle_start;
static int32_t angle_end;
static void prv_setup_data(GRect bounds) {
center = GPoint(bounds.origin.x + (bounds.size.w / 2),
bounds.origin.y + (bounds.size.h / 2));
rect = GRect(center.x - (outer_size / 2), center.y - (outer_size / 2),
outer_size, outer_size);
inset = outer_size - inner_size;
scale_mode = GOvalScaleModeFitCircle;
}
static void prv_setup_even_angles_inner(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 50;
inner_size = 35;
angle_start = (TRIG_MAX_ANGLE / 8) * 3;
angle_end = TRIG_MAX_ANGLE + TRIG_MAX_ANGLE / 8;
prv_setup_data(bounds);
}
static void prv_setup_odd_angles_inner(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 49;
inner_size = 34;
angle_start = (TRIG_MAX_ANGLE / 8) * 3;
angle_end = TRIG_MAX_ANGLE + TRIG_MAX_ANGLE / 8;
prv_setup_data(bounds);
}
static void prv_setup_even_inner(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 50;
inner_size = 35;
angle_start = TRIG_MAX_ANGLE / 4;
angle_end = TRIG_MAX_ANGLE;
prv_setup_data(bounds);
}
static void prv_setup_odd_inner(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 49;
inner_size = 34;
angle_start = TRIG_MAX_ANGLE / 4;
angle_end = TRIG_MAX_ANGLE;
prv_setup_data(bounds);
}
static void prv_setup_even_angles_full(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 50;
inner_size = 0;
angle_start = (TRIG_MAX_ANGLE / 8) * 3;
angle_end = TRIG_MAX_ANGLE + TRIG_MAX_ANGLE / 8;
prv_setup_data(bounds);
}
static void prv_setup_odd_angles_full(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 49;
inner_size = 0;
angle_start = (TRIG_MAX_ANGLE / 8) * 3;
angle_end = TRIG_MAX_ANGLE + TRIG_MAX_ANGLE / 8;
prv_setup_data(bounds);
}
static void prv_setup_even_full(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 50;
inner_size = 0;
angle_start = TRIG_MAX_ANGLE / 4;
angle_end = TRIG_MAX_ANGLE;
prv_setup_data(bounds);
}
static void prv_setup_odd_full(Window *window) {
Layer *window_layer = window_get_root_layer(window);
GRect bounds = window_layer->bounds;
// Parameters
outer_size = 49;
inner_size = 0;
angle_start = TRIG_MAX_ANGLE / 4;
angle_end = TRIG_MAX_ANGLE;
prv_setup_data(bounds);
}
static void prv_test_radial(Layer *layer, GContext* ctx) {
GColor color = { .argb = (uint8_t) rand() };
graphics_context_set_fill_color(ctx, color);
graphics_fill_radial(ctx, rect, GOvalScaleModeFillCircle, inset, angle_start, angle_end);
}
static void prv_test_circle(Layer *layer, GContext* ctx) {
GColor color = { .argb = (uint8_t) rand() };
graphics_context_set_fill_color(ctx, color);
graphics_fill_circle(ctx, center, (outer_size / 2));
}
GfxTest g_gfx_test_annulus_even_fill_angles = {
.name = "Annulus Even Angles",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_even_angles_inner,
};
GfxTest g_gfx_test_annulus_odd_fill_angles = {
.name = "Annulus Odd Angles",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_odd_angles_inner,
};
GfxTest g_gfx_test_annulus_even_fill = {
.name = "Annulus Even",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_even_inner,
};
GfxTest g_gfx_test_annulus_odd_fill = {
.name = "Annulus Odd",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_odd_inner,
};
GfxTest g_gfx_test_radial_even_fill_angles = {
.name = "Radial Even Angles",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_even_angles_full,
};
GfxTest g_gfx_test_radial_odd_fill_angles = {
.name = "Radial Odd Angles",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_odd_angles_full,
};
GfxTest g_gfx_test_radial_even_fill = {
.name = "Radial Even",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_even_full,
};
GfxTest g_gfx_test_radial_odd_fill = {
.name = "Radial Odd",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_radial,
.setup = prv_setup_odd_full,
};
GfxTest g_gfx_test_circle_even = {
.name = "Circle Even",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_circle,
.setup = prv_setup_even_full,
};
GfxTest g_gfx_test_circle_odd = {
.name = "Circle Odd",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test_circle,
.setup = prv_setup_odd_full,
};

View file

@ -0,0 +1,54 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
static void prv_test(Layer *layer, GContext* ctx);
GfxTest g_gfx_test_gpath_masking = {
.name = "GPath masking",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test,
};
static const GPathInfo s_triangle_mask = {
.num_points = 3,
.points = (GPoint[]) {
{0, 0},
{50, 50},
{50, -50}
}
};
static void prv_test(Layer *layer, GContext* ctx) {
GRect bounds = layer->bounds;
GPoint center = GPoint(bounds.size.w/2, bounds.size.h/2);
int outer_radius = 50;
int inner_radius = 35;
GPath *mask = gpath_create(&s_triangle_mask);
gpath_move_to(mask, center);
GColor color = { .argb = (uint8_t) rand() };
GColor bg_color = { .argb = (uint8_t) rand() };
graphics_context_set_fill_color(ctx, color);
graphics_fill_circle(ctx, center, outer_radius);
graphics_context_set_fill_color(ctx, bg_color);
graphics_fill_circle(ctx, center, inner_radius);
gpath_draw_filled(ctx, mask);
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// List of tests. The name given should match the name of the test definition struct in the form
// g_gfx_test_##name
GFX_TEST(single_line)
GFX_TEST(text)
GFX_TEST(text_clipping)
GFX_TEST(annulus_even_fill_angles)
GFX_TEST(annulus_odd_fill_angles)
GFX_TEST(annulus_even_fill)
GFX_TEST(annulus_odd_fill)
GFX_TEST(radial_even_fill_angles)
GFX_TEST(radial_odd_fill_angles)
GFX_TEST(radial_even_fill)
GFX_TEST(radial_odd_fill)
GFX_TEST(circle_odd)
GFX_TEST(circle_even)
GFX_TEST(gpath_masking)
GFX_TEST(rotated_bitmap_0_assign)
GFX_TEST(rotated_bitmap_0_set)
GFX_TEST(rotated_bitmap_45_assign)
GFX_TEST(rotated_bitmap_45_set)
GFX_TEST(rotated_bitmap_0_assign_64px)
GFX_TEST(rotated_bitmap_0_set_64px)
GFX_TEST(rotated_bitmap_45_assign_64px)
GFX_TEST(rotated_bitmap_45_set_64px)

View file

@ -0,0 +1,178 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
#include "util/trig.h"
static GBitmap *s_gfx_rotated_bitmap_bitmap;
static GBitmap *s_gfx_rotated_bitmap_64_bitmap;
static GPoint bitmap_center = {DISP_COLS / 2, DISP_ROWS / 2};
static GPoint bitmap_64_center = {64 / 2, 64 / 2};
static void prv_setup(Window *window) {
#if SCREEN_COLOR_DEPTH_BITS == 1
s_gfx_rotated_bitmap_bitmap = gbitmap_create_blank(GSize(DISP_COLS, DISP_ROWS),
GBitmapFormat1Bit);
s_gfx_rotated_bitmap_64_bitmap = gbitmap_create_blank(GSize(64, 64), GBitmapFormat1Bit);
uint32_t size_full = (DISP_COLS*DISP_ROWS)/8;
#else
s_gfx_rotated_bitmap_bitmap = gbitmap_create_blank(GSize(DISP_COLS, DISP_ROWS),
GBitmapFormat8Bit);
s_gfx_rotated_bitmap_64_bitmap = gbitmap_create_blank(GSize(64, 64), GBitmapFormat8Bit);
uint32_t size_full = (DISP_COLS*DISP_ROWS);
#endif
uint8_t *s_gfx_rotated_bitmap_pixels = s_gfx_rotated_bitmap_bitmap->addr;
// Init images
for (uint32_t index = 0; index < size_full; index++) {
s_gfx_rotated_bitmap_pixels[index] = (index % 2) ? 0xf0 : 0xcc;
}
#if SCREEN_COLOR_DEPTH_BITS == 1
uint32_t size_64 = (64*64)/8;
#else
uint32_t size_64 = (64*64);
#endif
uint8_t *s_gfx_rotated_bitmap_64_pixels = s_gfx_rotated_bitmap_64_bitmap->addr;
for (uint32_t index = 0; index < size_64; index++) {
s_gfx_rotated_bitmap_64_pixels[index] = (index % 2) ? 0xf0 : 0xcc;
}
}
static void prv_test_0_assign(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_bitmap,
GPointZero, 0, GPointZero);
}
static void prv_test_0_set(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_bitmap,
GPointZero, 0, GPointZero);
}
static void prv_test_45_assign(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_bitmap,
bitmap_center, DEG_TO_TRIGANGLE(45), bitmap_center);
}
static void prv_test_45_set(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_bitmap,
bitmap_center, DEG_TO_TRIGANGLE(45), bitmap_center);
}
static void prv_test_0_assign_64px(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_64_bitmap,
GPointZero, 0, GPointZero);
}
static void prv_test_0_set_64px(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_64_bitmap,
GPointZero, 0, GPointZero);
}
static void prv_test_45_assign_64px(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_64_bitmap,
bitmap_64_center, DEG_TO_TRIGANGLE(45), bitmap_64_center);
}
static void prv_test_45_set_64px(Layer *layer, GContext* ctx) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_rotated_bitmap(ctx, (GBitmap*)&s_gfx_rotated_bitmap_64_bitmap,
bitmap_64_center, DEG_TO_TRIGANGLE(45), bitmap_64_center);
}
static void prv_teardown(Window *window) {
gbitmap_destroy(s_gfx_rotated_bitmap_bitmap);
gbitmap_destroy(s_gfx_rotated_bitmap_64_bitmap);
}
GfxTest g_gfx_test_rotated_bitmap_0_assign = {
.name = "RotBit 0-A-full",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_0_assign,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_0_set = {
.name = "RotBit 0-S-full",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_0_set,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_45_assign = {
.name = "RotBit-45-A-full",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_45_assign,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_45_set = {
.name = "RotBit-45-S-full",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_45_set,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_0_assign_64px = {
.name = "RotBit-0-A-64px",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_0_assign_64px,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_0_set_64px = {
.name = "RotBit-0-S-64px",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_0_set_64px,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_45_assign_64px = {
.name = "RotBit-45-A-64px",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_45_assign_64px,
.setup = prv_setup,
.teardown = prv_teardown,
};
GfxTest g_gfx_test_rotated_bitmap_45_set_64px = {
.name = "RotBit-45-S-64px",
.duration = 5,
.unit_multiple = 1,
.test_proc = prv_test_45_set_64px,
.setup = prv_setup,
.teardown = prv_teardown,
};

View file

@ -0,0 +1,38 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
static void prv_test(Layer *layer, GContext* ctx);
GfxTest g_gfx_test_single_line = {
.name = "Single line",
.duration = 1,
.unit_multiple = 1,
.test_proc = prv_test,
};
static void prv_test(Layer *layer, GContext* ctx) {
GRect bounds = layer->bounds;
int16_t x1 = (rand() % bounds.size.w) + bounds.origin.x;
int16_t x2 = (rand() % bounds.size.w) + bounds.origin.x;
int16_t y1 = (rand() % bounds.size.h) + bounds.origin.y;
int16_t y2 = (rand() % bounds.size.h) + bounds.origin.y;
GColor color = { .argb = (uint8_t) rand() };
graphics_context_set_stroke_color(ctx, color);
graphics_draw_line(ctx, (GPoint){.x = x1, .y = y1}, (GPoint) {.x = x2, .y = y2});
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
static void prv_setup(Window *window);
static void prv_test(Layer *layer, GContext* ctx);
GfxTest g_gfx_test_text = {
.name = "Text",
.duration = 5,
.unit_multiple = 0, // Number of characters in test string - set later
.test_proc = prv_test,
.setup = prv_setup,
};
static GFont s_font;
static void prv_setup(Window *window) {
s_font = fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD);
}
static void prv_test(Layer *layer, GContext* ctx) {
const char *text_test_str = "Lorem ipsum dolor sit amet, ne choro argumentum est, quando latine "
"copiosae est ea, usu nonumes accusam te.";
g_gfx_test_text.unit_multiple = strlen(text_test_str);
GColor color = { .argb = (uint8_t) rand() };
graphics_context_set_text_color(ctx, color);
graphics_draw_text(ctx, text_test_str, s_font, layer->bounds,
GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL);
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
static void prv_setup(Window *window);
static void prv_test(Layer *layer, GContext* ctx);
static void prv_teardown(Window *window);
GfxTest g_gfx_test_text_clipping = {
.name = "Text Clipping",
.duration = 5,
.unit_multiple = 0, // Number of characters in test string - set later
.test_proc = prv_test,
.setup = prv_setup,
.teardown = prv_teardown,
};
static GFont s_font;
static Layer s_canvas;
static void prv_setup(Window *window) {
s_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
layer_init(&s_canvas, &GRect(40, 40, 80, 40));
layer_add_child(&window->layer, &s_canvas);
}
static void prv_test(Layer *layer, GContext* ctx) {
const char *text_test_str = "This is a test message that is really long!\"#$%&'()*+,-./01234"
"56789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrs"
"tuvwxyz{|}";
g_gfx_test_text_clipping.unit_multiple = strlen(text_test_str);
GColor color = { .argb = (uint8_t) rand() };
graphics_context_set_text_color(ctx, color);
GRect bounds = layer->bounds;
bounds.origin.y -= 150; // Drop y by 150 pixels so some data gets clipped
graphics_draw_text(ctx, text_test_str, s_font, bounds,
GTextOverflowModeWordWrap, GTextAlignmentLeft, NULL);
}
static void prv_teardown(Window *window) {
layer_remove_from_parent(&s_canvas);
}

View file

@ -0,0 +1,207 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "gfx_tests.h"
#include "applib/ui/app_window_stack.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/size.h"
#include "system/profiler.h"
#include <stdlib.h>
#include <stdio.h>
typedef struct {
Window menu_window;
Window test_window;
Window results_window;
MenuLayer test_menu;
TextLayer results_text;
char results_str[100];
GfxTest *current_test;
} AppData;
#define RAND_SEED (775762732) // Randomly selected
#define US_PER_MS (1000)
#define US_PER_S (1000 * 1000)
#define TARGET_FPS (30)
#define US_PER_FRAME (20 * US_PER_MS) // Upper bound on amount of time available to the rest of
// the system while a frame is being pushed out to the
// display with the cpu clock at 64MHz
#define GFX_TEST(name) extern GfxTest g_gfx_test_##name;
#include "gfx_test_list.h"
#undef GFX_TEST
#define GFX_TEST(name) &g_gfx_test_##name,
static GfxTest *s_tests[] = {
#include "gfx_test_list.h"
};
static void prv_pop_test_window(void *data) {
AppData *app_data = data;
app_window_stack_pop(false);
app_window_stack_push(&app_data->results_window, false);
}
static void prv_test_update_proc(Layer *layer, GContext* ctx) {
AppData *app_data = window_get_user_data(layer->window);
GfxTest *test = app_data->current_test;
srand(RAND_SEED); // Setup rand for routines that need it
if (test->setup) {
test->setup(layer->window);
}
PROFILER_INIT;
PROFILER_START;
while (PROFILER_NODE_GET_TOTAL_US(gfx_test_update_proc) < (test->duration * US_PER_S)) {
PROFILER_NODE_START(gfx_test_update_proc);
test->test_proc(layer, ctx);
PROFILER_NODE_STOP(gfx_test_update_proc);
}
PROFILER_STOP;
PROFILER_PRINT_STATS;
if (test->teardown) {
test->teardown(layer->window);
}
app_timer_register(0, prv_pop_test_window, app_data);
}
static void prv_start_test(GfxTest *test, AppData *app_data) {
app_data->current_test = test;
Window *window = &app_data->test_window;
window_init(window, WINDOW_NAME(test->name));
window_set_user_data(window, app_data);
window_set_fullscreen(window, true);
layer_set_update_proc((Layer *) window, prv_test_update_proc);
app_window_stack_push(window, false);
}
static uint16_t prv_get_num_rows(struct MenuLayer *menu_layer, uint16_t section,
void *callback_context) {
return (uint16_t) ARRAY_LENGTH(s_tests);
}
static void prv_draw_row(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *callback_context) {
PBL_ASSERTN(cell_index->row < ARRAY_LENGTH(s_tests));
menu_cell_title_draw(ctx, cell_layer, s_tests[cell_index->row]->name);
}
static void prv_click_handler(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *callback_context) {
PBL_ASSERTN(cell_index->row < ARRAY_LENGTH(s_tests));
prv_start_test(s_tests[cell_index->row], (AppData *)callback_context);
}
static void prv_handle_results_click(ClickRecognizerRef recognizer, void *context) {
app_window_stack_pop(false);
}
static void prv_results_window_load(Window *window) {
AppData *app_data = window_get_user_data(window);
uint32_t total_us = PROFILER_NODE_GET_TOTAL_US(gfx_test_update_proc);
uint32_t count = PROFILER_NODE_GET_COUNT(gfx_test_update_proc);
GfxTest *test = app_data->current_test;
uint32_t avg_us = (10 * total_us) / count; // multiply by 10 to get decimals
uint32_t per_frame = (100 * US_PER_FRAME) / avg_us; // multiply by 100 to get decimals and account
// for x10 in avg_us calc
uint32_t fps = (TARGET_FPS * 100 * US_PER_FRAME) / avg_us;
uint32_t unit_per_frame = ((uint64_t)test->unit_multiple * US_PER_FRAME * 100) / avg_us;
snprintf(app_data->results_str, sizeof(app_data->results_str),
"%10s\n"
"Avg (µs):\n%"PRIu32".%"PRIu32"\n"
"FPS:\n%"PRIu32".%"PRIu32"\n"
"Per frame @ 30fps:\n%"PRIu32".%"PRIu32"\n"
"Units per frame @ 30fps:\n%"PRIu32".%"PRIu32,
test->name, avg_us / 10, avg_us % 10, fps / 10, fps % 10, per_frame / 10, per_frame % 10,
unit_per_frame / 10, unit_per_frame % 10);
PBL_LOG(LOG_LEVEL_DEBUG, "results: %s", app_data->results_str);
text_layer_set_text(&app_data->results_text, app_data->results_str);
}
static void prv_results_window_unload(Window *window) {
AppData *app_data = window_get_user_data(window);
menu_layer_deinit(&app_data->test_menu);
}
static void prv_results_window_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_handle_results_click);
}
static void handle_init(void) {
AppData *app_data = app_malloc_check(sizeof(AppData));
// Menu window
Window *window = &app_data->menu_window;
window_init(window, WINDOW_NAME("GFX Test Framework"));
window_set_user_data(window, app_data);
window_set_fullscreen(window, false);
MenuLayer *menu = &app_data->test_menu;
menu_layer_init(menu, &window->layer.bounds);
menu_layer_set_callbacks(menu, app_data, &(MenuLayerCallbacks){
.get_num_rows = prv_get_num_rows,
.draw_row = prv_draw_row,
.select_click = prv_click_handler,
});
menu_layer_set_click_config_onto_window(menu, window);
layer_add_child((Layer *) window, (Layer *) menu);
app_window_stack_push(window, true);
// Results window
window = &app_data->results_window;
window_init(window, WINDOW_NAME("Test Results"));
window_set_user_data(window, app_data);
window_set_fullscreen(window, false);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_results_window_load,
.unload = prv_results_window_unload,
});
window_set_click_config_provider(window, prv_results_window_click_config_provider);
TextLayer *text = &app_data->results_text;
text_layer_init(text, &window->layer.bounds);
text_layer_set_text(text, "");
layer_add_child((Layer *) window, (Layer *) text);
}
static void s_main(void) {
handle_init();
app_event_loop();
}
const PebbleProcessMd* gfx_tests_get_app_info() {
static const PebbleProcessMdSystem gfx_tests_app_info = {
.name = "GFX Tests",
.common = {
.main_func = &s_main,
// UUID: 06a8126b-d805-4197-af6d-8df3c1efb8e4
.uuid = { 0x06, 0xa8, 0x12, 0x6b, 0xd8, 0x05, 0x41, 0x97, 0xaf, 0x6d, 0x8d, 0xf3,
0xc1, 0xef, 0xb8, 0xe4},
}
};
return (const PebbleProcessMd*) &gfx_tests_app_info;
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
// All graphics/UI includes needed for tests. Add here if more are needed.
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/text.h"
#include "applib/graphics/gtypes.h"
#include "applib/ui/layer.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/menu_layer.h"
#include "applib/fonts/fonts.h"
#include "util/trig.h"
#include "applib/graphics/gpath.h"
#include <stdint.h>
//! GFX test struct
typedef struct GfxTest {
char *name; //!< Name string
uint32_t duration; //!< Number of seconds to run the test for
uint32_t unit_multiple; //!< Number of actions per test iteration
LayerUpdateProc test_proc; //!< Test procedure
void (*setup)(Window *window); //!< Test setup function
void (*teardown)( Window *window); //!< Test teardown function
} GfxTest;
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* gfx_tests_get_app_info(void);

View file

@ -0,0 +1,201 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "grenade_launcher.h"
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/ui.h"
#include "applib/ui/window.h"
#include "applib/ui/window_private.h"
#include "drivers/flash.h"
#include "drivers/system_flash.h"
#include "flash_region/flash_region.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "services/common/system_task.h"
#include "services/runlevel.h"
#include "system/bootbits.h"
#include "system/firmware_storage.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include <stdio.h>
///////////
// Helpers
static void fw_update_reboot(void) {
PBL_LOG(LOG_LEVEL_DEBUG, "Rebooting to apply new firmware!");
boot_bit_set(BOOT_BIT_NEW_FW_AVAILABLE);
services_set_runlevel(RunLevel_BareMinimum);
system_reset();
}
static void erase_fw(const uint32_t start_address) {
// Erase flash
flash_erase_sector_blocking(start_address); // Set everything high
// Set up the firmware description
FirmwareDescription desc;
desc.description_length = sizeof(FirmwareDescription);
desc.firmware_length = (64 * 1024) - sizeof(FirmwareDescription);
desc.checksum = 0xdeadbeef;
// Write the firmware description
flash_write_bytes((uint8_t*)&desc, start_address, desc.description_length);
}
enum {
ERASE_NORMAL_FW = 1 << 0,
ERASE_RECOVERY_FW = 1 << 1,
ERASE_ALL = ERASE_NORMAL_FW | ERASE_RECOVERY_FW,
};
static void erase_callback(void *data) {
uintptr_t arg = (uintptr_t)data;
if (arg & ERASE_RECOVERY_FW) {
erase_fw(FLASH_REGION_SAFE_FIRMWARE_BEGIN);
}
if (arg & ERASE_NORMAL_FW) {
erase_fw(FLASH_REGION_FIRMWARE_SCRATCH_BEGIN);
system_flash_erase(FLASH_Sector_4);
system_flash_erase(FLASH_Sector_5);
system_flash_erase(FLASH_Sector_6);
system_flash_erase(FLASH_Sector_7);
}
fw_update_reboot();
}
static void crash(void *data) {
((void(*)(void))data)();
}
////////////////////
// UI Code
typedef struct {
Window window;
TextLayer text;
} AppData;
static void set_text(Window *window, char *message) {
AppData *data = window_get_user_data(window);
TextLayer *text = &data->text;
text_layer_set_text(text, message);
PBL_LOG(LOG_LEVEL_DEBUG, "%s", message);
}
static void up_click_handler(ClickRecognizerRef recognizer, Window *window) {
(void) recognizer;
set_text(window, "Erasing Normal+Sys firmware...");
system_task_add_callback(erase_callback, (void *)ERASE_NORMAL_FW);
}
#if 0
static void up_long_click_handler(ClickRecognizerRef recognizer, Window *window) {
(void) recognizer;
set_text(window, "Erasing All...");
system_task_add_callback(erase_callback, (void *)ERASE_ALL);
}
#endif
static void select_click_handler(ClickRecognizerRef recognizer, Window *window) {
(void) recognizer;
set_text(window, "Going down for reboot...");
system_task_add_callback(system_reset_callback, NULL);
}
static void down_click_handler(ClickRecognizerRef recognizer, Window *window) {
(void) recognizer;
set_text(window, "Erasing recovery firmware");
system_task_add_callback(erase_callback, (void *)ERASE_RECOVERY_FW);
}
static void back_click_handler(ClickRecognizerRef recognizer, Window *window) {
(void) recognizer;
set_text(window, "Boom!");
system_task_add_callback(crash, NULL);
}
static void config_provider(Window *window) {
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler) up_click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) select_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler) down_click_handler);
window_single_click_subscribe(BUTTON_ID_BACK, (ClickHandler) back_click_handler);
}
static void prv_window_load(Window *window) {
AppData *data = window_get_user_data(window);
TextLayer *text = &data->text;
text_layer_init(text, &window->layer.bounds);
text_layer_set_text(text, "UP: Erase Normal+Sys FW\nUP LONG:Erase Normal+Recov+Sys\nSEL: Reboot FW\nDOWN: Erase Recovery\nBACK: Crash");
text_layer_set_font(text, fonts_get_system_font(FONT_KEY_GOTHIC_14));
layer_add_child(&window->layer, &text->layer);
}
static void push_window(AppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Grenade Launcher"));
window_set_user_data(window, data);
window_set_overrides_back_button(window, true);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
window_set_click_config_provider(window, (ClickConfigProvider) config_provider);
window_set_fullscreen(window, true);
const bool animated = false;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init(void) {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit(void) {
AppData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* grenade_launcher_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Grenade Launcher"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* grenade_launcher_app_get_info();

View file

@ -0,0 +1,70 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "idl_demo.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "system/hexdump.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include "nanopb/simple.pb.h"
#include "nanopb/measurements.pb.h"
static void prv_init(void) {
SimpleMessage msg = {
.lucky_number = 42,
};
uint8_t *buffer = app_malloc(30);
pb_ostream_t s = pb_ostream_from_buffer(buffer, 30);
pb_encode(&s, SimpleMessage_fields, &msg);
PBL_LOG(LOG_LEVEL_DEBUG, "Encoded message, size: %u bytes", s.bytes_written);
PBL_HEXDUMP(LOG_LEVEL_DEBUG, buffer, s.bytes_written);
app_state_set_user_data(buffer);
}
static void prv_deinit(void) {
SimpleMessage msg = {0};
uint8_t *buffer = app_state_get_user_data();
pb_istream_t s = pb_istream_from_buffer(buffer, 30);
pb_decode(&s, SimpleMessage_fields, &msg);
PBL_LOG(LOG_LEVEL_DEBUG, "The lucky number is %"PRId32, msg.lucky_number);
}
static void prv_app_main(void) {
prv_init();
Window *window = window_create();
app_window_stack_push(window, false /*animated*/);
app_event_loop();
prv_deinit();
}
const PebbleProcessMd *idl_demo_get_app_info() {
static const PebbleProcessMdSystem s_app_data = {
.common = {
.main_func = prv_app_main,
// UUID: 101a32d95-1234-46d4-1234-854cc62f97f9
.uuid = {0x99, 0xa3, 0x2d, 0x95, 0x12, 0x34, 0x46, 0xd4,
0x12, 0x34, 0x85, 0x4c, 0xc6, 0x2f, 0x97, 0xf9},
},
.name = "IDL Demo",
};
return (const PebbleProcessMd *)&s_app_data;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* idl_demo_get_app_info(void);

View file

@ -0,0 +1,90 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_app.h"
#include <bluetooth/reconnect.h>
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/tick_timer_service.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
static unsigned int s_progress_count = 0;
struct AppState {
Window window;
};
static void prv_window_load(Window *window) {
struct AppState* data = window_get_user_data(window);
(void)data;
}
static void push_window(struct AppState *data) {
Window* window = &data->window;
window_init(window, WINDOW_NAME("Kill BT Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_second_tick(struct tm *tick_time, TimeUnits units_changed) {
PBL_LOG(LOG_LEVEL_DEBUG, "Try to kill the BT:%d", s_progress_count);
s_progress_count += 1;
bt_ctl_reset_bluetooth();
}
static void handle_init(void) {
struct AppState* data = app_malloc_check(sizeof(struct AppState));
app_state_set_user_data(data);
tick_timer_service_subscribe(SECOND_UNIT, handle_second_tick);
push_window(data);
}
static void handle_deinit(void) {
struct AppState* data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* kill_bt_app_get_info() {
static const PebbleProcessMdSystem kill_bt_app_info = {
.name = "Kill BT Test",
.common.main_func = s_main,
};
return (const PebbleProcessMd*) &kill_bt_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* kill_bt_app_get_info();

View file

@ -0,0 +1,115 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "kino_layer_demo.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/kino/kino_layer.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "util/size.h"
typedef struct {
Window window;
KinoLayer kino_layer;
int resource_index;
} KinoLayerDemoData;
uint32_t resources[] = {
RESOURCE_ID_RESULT_SENT_LARGE,
RESOURCE_ID_GENERIC_QUESTION_LARGE,
RESOURCE_ID_VOICE_MICROPHONE_LARGE,
};
static void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) {
KinoLayerDemoData *data = context;
kino_layer_pause(&data->kino_layer);
int index = ++data->resource_index % ARRAY_LENGTH(resources);
kino_layer_set_reel_with_resource(&data->kino_layer, resources[index]);
kino_layer_play(&data->kino_layer);
}
static void prv_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler);
window_set_click_context(BUTTON_ID_SELECT, context);
}
static void prv_window_appear(Window *window) {
KinoLayerDemoData *data = window_get_user_data(window);
kino_layer_play(&data->kino_layer);
}
static void prv_window_load(Window *window) {
KinoLayerDemoData *data = window_get_user_data(window);
Layer *root = window_get_root_layer(window);
// init the kino layer
kino_layer_init(&data->kino_layer, &root->bounds);
layer_add_child(root, &data->kino_layer.layer);
// create the first kino reel
kino_layer_set_reel_with_resource(&data->kino_layer, resources[0]);
}
static void prv_init(void) {
KinoLayerDemoData *data = app_malloc_check(sizeof(KinoLayerDemoData));
memset(data, 0, sizeof(KinoLayerDemoData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Kino Layer"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
.appear = prv_window_appear,
});
window_set_click_config_provider_with_context(window, prv_click_config_provider, data);
app_window_stack_push(window, true /*animated*/);
}
static void prv_deinit(void) {
KinoLayerDemoData *data = app_state_get_user_data();
kino_layer_deinit(&data->kino_layer);
app_free(data);
}
///////////////////////////
// app boilerplate
///////////////////////////
static void s_main(void) {
prv_init();
app_event_loop();
prv_deinit();
}
const PebbleProcessMd *kino_layer_demo_get_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common = {
.main_func = s_main,
// UUID: 67a32d95-ef69-46d4-a0b9-854cc62f97fa
.uuid = {0x12, 0xa3, 0x2d, 0x95, 0xef, 0x69, 0x46, 0xd4,
0xa0, 0xb9, 0x85, 0x4c, 0xc6, 0x2f, 0x97, 0xfa},
},
.name = "KinoLayer Demo",
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd *kino_layer_demo_get_info(void);

View file

@ -0,0 +1,66 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/number_window.h"
#include "process_management/pebble_process_md.h"
#include "process_state/app_state/app_state.h"
#include "services/common/light.h"
#include <stdint.h>
static void selected_pwm_percentage(NumberWindow *nw, void *ctx) {
uint8_t val = number_window_get_value(nw);
backlight_set_intensity_percent(val);
}
static void handle_init(void) {
NumberWindow *light_num_window = number_window_create("Light Config",
(NumberWindowCallbacks) { .selected = selected_pwm_percentage },
NULL);
app_state_set_user_data(light_num_window);
uint8_t scale_granularity = 5; // 5 percent at a time
uint8_t curr_percent = scale_granularity * ((backlight_get_intensity_percent() +
scale_granularity - 1) / scale_granularity);
number_window_set_value(light_num_window, curr_percent);
number_window_set_max(light_num_window, 100);
number_window_set_min(light_num_window, 0);
number_window_set_step_size(light_num_window, scale_granularity);
app_window_stack_push(number_window_get_window(light_num_window), true);
}
static void handle_deinit(void) {
NumberWindow *data = app_state_get_user_data();
number_window_destroy(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* light_config_get_info() {
static const PebbleProcessMdSystem s_accel_config_info = {
.common.main_func = s_main,
.name = "Light Config"
};
return (const PebbleProcessMd*) &s_accel_config_info;
}

View file

@ -0,0 +1,224 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "menu_app.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "system/passert.h"
#include <stdio.h>
#define BUFFER_SIZE 25
static const uint8_t s_music_launcher_icon_pixels[] = {
0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0x3f, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, /* bytes 0 - 16 */
0x7f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x18, 0x00, 0x7f, 0x00, 0x1f, 0x00, /* bytes 16 - 32 */
0x7f, 0xf0, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, /* bytes 32 - 48 */
0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, /* bytes 48 - 64 */
0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x7f, 0x7c, 0x00, 0x00, /* bytes 64 - 80 */
0x03, 0x3c, 0x00, 0x00, 0x01, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x80, 0x00, 0x00, 0x3c, 0xc0, 0x00, /* bytes 80 - 96 */
0x00, 0x7e, 0xe0, 0x00, 0x00, 0xff, 0xff, 0x00, 0x81, 0xff, 0xff, 0x00,
};
static const GBitmap s_music_launcher_icon_bitmap = {
.addr = (void*) &s_music_launcher_icon_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 24, .h = 27 },
},
};
typedef struct {
Window window;
MenuLayer menu_layer;
GBitmap icon;
Window detail_window;
TextLayer detail_text;
char detail_text_buffer[50];
} AppData;
static uint16_t get_num_sections_callback(struct MenuLayer *menu_layer, AppData *data) {
(void)data;
(void)menu_layer;
return 4;
}
static uint16_t get_num_rows_callback(struct MenuLayer *menu_layer, uint16_t section_index, AppData *data) {
(void)data;
(void)menu_layer;
switch (section_index) {
default:
case 0: return 2;
case 1: return 3;
case 2: return 4;
case 3: return 5;
}
}
static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, AppData *data) {
(void)data;
(void)menu_layer;
(void)cell_index;
// Variable row heights demo:
switch (cell_index->row % 3) {
default:
case 0:
return 44;
case 1:
return 64;
case 2:
return 84;
}
}
static int16_t get_header_height_callback(struct MenuLayer *menu_layer, uint16_t section_index, AppData *data) {
(void)data;
(void)menu_layer;
(void)section_index;
return MENU_CELL_BASIC_HEADER_HEIGHT;
}
static void draw_row_callback(GContext* ctx, Layer *cell_layer, MenuIndex *cell_index, AppData *data) {
char title[BUFFER_SIZE];
char subtitle[BUFFER_SIZE];
switch (cell_index->row % 2) {
case 0:
sniprintf(title, BUFFER_SIZE, "Title %i/%i ", cell_index->section, cell_index->row);
sniprintf(subtitle, BUFFER_SIZE, "Subtitle %i/%i", cell_index->section, cell_index->row);
menu_cell_basic_draw(ctx, cell_layer, title, subtitle, (GBitmap*)&s_music_launcher_icon_bitmap);
break;
default:
case 1:
sniprintf(title, BUFFER_SIZE, "Only Title %i/%i", cell_index->section, cell_index->row);
menu_cell_title_draw(ctx, cell_layer, title);
break;
}
(void)data;
}
static void draw_header_callback(GContext* ctx, Layer *cell_layer, uint16_t section_index, AppData *data) {
char title[BUFFER_SIZE];
sniprintf(title, BUFFER_SIZE, "Section Header (%i)", section_index);
menu_cell_basic_header_draw(ctx, cell_layer, title);
(void)data;
}
static void detail_window_load(Window *window) {
AppData *data = window_get_user_data(window);
TextLayer *text_layer = &data->detail_text;
text_layer_init(text_layer, &window->layer.bounds);
text_layer_set_text(text_layer, data->detail_text_buffer);
layer_add_child(&window->layer, &text_layer->layer);
}
static void push_detail_window(AppData *data, MenuIndex *index, bool is_long_click) {
sniprintf(data->detail_text_buffer, 50, "SELECTION:\n\nSection %i, Row %i\nLong click: %c", index->section, index->row, is_long_click ? 'Y' : 'N');
Window *detail_window = &data->detail_window;
window_init(detail_window, WINDOW_NAME("Demo Menu Detail"));
window_set_user_data(detail_window, data);
window_set_window_handlers(detail_window, &(WindowHandlers) {
.load = detail_window_load,
});
const bool animated = true;
app_window_stack_push(detail_window, animated);
}
static void select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, AppData *data) {
push_detail_window(data, cell_index, false);
(void)menu_layer;
}
static void select_long_callback(MenuLayer *menu_layer, MenuIndex *cell_index, AppData *data) {
push_detail_window(data, cell_index, true);
(void)menu_layer;
}
static void prv_window_load(Window *window) {
AppData *data = window_get_user_data(window);
MenuLayer *menu_layer = &data->menu_layer;
menu_layer_init(menu_layer, &window->layer.bounds);
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_num_sections = (MenuLayerGetNumberOfSectionsCallback) get_num_sections_callback,
.get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback) get_num_rows_callback,
.get_cell_height = (MenuLayerGetCellHeightCallback) get_cell_height_callback,
.get_header_height = (MenuLayerGetHeaderHeightCallback) get_header_height_callback,
.draw_row = (MenuLayerDrawRowCallback) draw_row_callback,
.draw_header = (MenuLayerDrawHeaderCallback) draw_header_callback,
.select_click = (MenuLayerSelectCallback) select_callback,
.select_long_click = (MenuLayerSelectCallback) select_long_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
}
static void push_window(AppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Demo Menu"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init(void) {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit(void) {
AppData *data = app_state_get_user_data();
menu_layer_deinit(&data->menu_layer);
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* menu_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "MenuLayer Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}
#undef BUFFER_SIZE

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* menu_app_get_info();

View file

@ -0,0 +1,151 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "menu_layer_right_icon.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "system/logging.h"
#include "system/passert.h"
#include <stdio.h>
#define NUM_MENU_SECTIONS 1
#define NUM_MENU_ITEMS 4
typedef struct {
Window window;
MenuLayer menu_layer;
GBitmap checked_icon;
} AppData;
static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) {
return NUM_MENU_SECTIONS;
}
static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index,
void *data) {
return NUM_MENU_ITEMS;
}
static int16_t menu_get_header_height_callback(MenuLayer *menu_layer, uint16_t section_index,
void *data) {
return MENU_CELL_BASIC_HEADER_HEIGHT;
}
static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index,
void *data) {
AppData *app_data = (AppData *) data;
switch (cell_index->row) {
case 0:
menu_cell_basic_draw_icon_right(ctx, cell_layer, "First Item", NULL,
&app_data->checked_icon);
break;
case 1:
menu_cell_basic_draw_icon_right(ctx, cell_layer, "Second Item", NULL,
&app_data->checked_icon);
break;
case 2:
menu_cell_basic_draw(ctx, cell_layer, "Third Item", NULL,
&app_data->checked_icon);
break;
case 3:
menu_cell_basic_draw_icon_right(ctx, cell_layer, "Fourth Item", "with a subtitle",
&app_data->checked_icon);
break;
}
}
static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) {
return;
}
static int16_t menu_get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *callback_context) {
switch(cell_index->row) {
case 0: return menu_cell_small_cell_height();
case 1: return menu_cell_basic_cell_height();
case 2: return menu_cell_small_cell_height();
case 3: return menu_cell_basic_cell_height();
default:
return menu_cell_basic_cell_height();
}
}
static void prv_window_load(Window *window) {
PBL_LOG(LOG_LEVEL_INFO, "WINDOW LOADING");
AppData *data = window_get_user_data(window);
gbitmap_init_with_resource(&data->checked_icon, RESOURCE_ID_CHECKBOX_ICON_CHECKED);
MenuLayer *menu_layer = &data->menu_layer;
menu_layer_init(menu_layer, &window->layer.bounds);
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_num_sections = (MenuLayerGetNumberOfSectionsCallback) menu_get_num_sections_callback,
.get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback) menu_get_num_rows_callback,
.get_cell_height = (MenuLayerGetCellHeightCallback) menu_get_cell_height_callback,
.draw_row = (MenuLayerDrawRowCallback) menu_draw_row_callback,
.select_click = menu_select_callback,
});
menu_layer_set_highlight_colors(&data->menu_layer, GColorJaegerGreen, GColorWhite);
menu_layer_set_click_config_onto_window(menu_layer, window);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
}
static void push_window(AppData *data) {
PBL_LOG(LOG_LEVEL_INFO, "PUSHING WINDOW");
Window *window = &data->window;
window_init(window, WINDOW_NAME("Demo Menu"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init() {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit() {
AppData *data = app_state_get_user_data();
menu_layer_deinit(&data->menu_layer);
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* menu_layer_right_icon_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "MenuLayer Right Icon Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* menu_layer_right_icon_app_get_info();

View file

@ -0,0 +1,119 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "menu_overflow_app.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "applib/ui/menu_layer.h"
static Window *window;
static MenuLayer *menu_layer;
static char *section_names[] = { "Movies", "Books", "Video Games", "Television", "Alcohol" };
static char *row_names[5][2] = {
{ "Avengers", "Eden of the East" },
{ "A Song of Ice and Fire", "Lord of the Rings" },
{ "Team Fortress 2", "Super Meat Boy" },
{ "Sunny in Philadelphia", "Gotham" },
{ "Beer", "Vodka" }
};
////////////////////
// MenuLayer construction and callback
static uint16_t prv_menu_get_num_sections_callback(struct MenuLayer *menu_layer,
void *callback_context) {
return 5;
}
static uint16_t prv_menu_get_num_rows_callback(struct MenuLayer *menu_layer,
uint16_t section_index, void *callback_context) {
return 2;
}
static int16_t prv_menu_get_header_height_callback(struct MenuLayer *menu_layer,
uint16_t section_index, void *callback_context) {
return 15;
}
static int16_t prv_menu_get_cell_height_callback(struct MenuLayer *menu_layer,
MenuIndex *cell_index, void *callback_context) {
return 20;
}
static int16_t prv_menu_get_separator_height_callback(struct MenuLayer *menu_layer,
MenuIndex *cell_index, void *callback_context) {
return 10;
}
static void prv_menu_draw_header_callback(GContext *ctx, const Layer *cell_layer,
uint16_t section_index, void *callback_context) {
menu_cell_basic_header_draw(ctx, cell_layer, section_names[section_index]);
}
static void prv_menu_draw_row_callback(GContext *ctx, const Layer *cell_layer,
MenuIndex *cell_index, void *callback_context) {
graphics_context_set_text_color(ctx, GColorBlack);
graphics_draw_text(ctx, row_names[cell_index->section][cell_index->row],
fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), GRect(4, 2, 136, 22),
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
////////////////////
// App boilerplate
static void init(void) {
window = window_create();
menu_layer = menu_layer_create(window_get_root_layer(window)->bounds);
menu_layer_set_callbacks(menu_layer, NULL, &(MenuLayerCallbacks) {
.get_num_sections = prv_menu_get_num_sections_callback,
.get_num_rows = prv_menu_get_num_rows_callback,
.get_header_height = prv_menu_get_header_height_callback,
.get_cell_height = prv_menu_get_cell_height_callback,
.get_separator_height = prv_menu_get_separator_height_callback,
.draw_header = prv_menu_draw_header_callback,
.draw_row = prv_menu_draw_row_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
layer_add_child(window_get_root_layer(window), menu_layer_get_layer(menu_layer));
app_window_stack_push(window, true);
}
static void deinit(void) {
menu_layer_destroy(menu_layer);
window_destroy(window);
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* menu_overflow_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Menu Overflow"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* menu_overflow_app_get_info();

View file

@ -0,0 +1,361 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "menu_round_app.h"
#include "applib/app.h"
#include "applib/graphics/gdraw_command_image.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/size.h"
#include <stdio.h>
// Menu Detail
//////////////////
typedef enum {
MenuLayerStyleTitle = 0,
MenuLayerStyleTitleAndSubtitle,
MenuLayerStyleTitleAndIconOnRight,
MenuLayerStyleTitleAndSubtitleAndValue,
MenuLayerStyleTitleAndSubtitleAndIcon,
} MenuLayerStyle;
typedef struct {
char *title;
char *subtitle;
char *value;
} MenuDetailRowData;
typedef struct {
Window window;
MenuLayer menu_layer;
StatusBarLayer status_bar_layer;
MenuLayerStyle style;
} MenuDetailWindowData;
static const MenuDetailRowData menu_detail_row_data_notifications[] = {
{"Liron Damir", "Late again. Sorry, I'll be on time in the future.", NULL},
{"Angela Tam", "Late again? Can you be on time for once?", NULL},
{"Eric Migicovsky", "Friday meeting will be held in the big room.", NULL},
{"Intagram", "Keep scrolling down.", NULL},
{"Liron Levak", "That's not my name.", NULL},
{"Kimberly North West Kardashian", "I broke the Internet again.", NULL},
{"Henry Damir", "That's not my name.", NULL},
{"Kevin Conley", "Wubalubadubdub!", NULL},
};
static const MenuDetailRowData menu_detail_row_data_days[] = {
{"Monday", NULL, NULL},
{"Tuesday", NULL, NULL},
{"Wednesday", NULL, NULL},
{"Thursday", NULL, NULL},
{"Friday", NULL, NULL},
{"Saturday", NULL, NULL},
{"Sunday", NULL, NULL},
};
static const MenuDetailRowData menu_detail_row_data_alarms[] = {
{"8:00 AM", "Workdays", "ON"},
{"10:00 AM", "Sat, Sun, Mon", "OFF"},
{"11:30 AM", "Weekends", "ON"},
{"5:00 PM", "Weekdays", "ON"},
};
typedef struct {
const MenuDetailRowData *rows;
uint16_t num_rows;
int16_t selected_cell_height;
int16_t unselected_cell_height;
GColor highlight_background_color;
} MenuDetailInfo;
static MenuDetailInfo prv_get_row_details_for_style(MenuLayerStyle style) {
switch (style) {
case MenuLayerStyleTitle:
return (MenuDetailInfo) {
.rows = menu_detail_row_data_notifications,
.num_rows = ARRAY_LENGTH(menu_detail_row_data_notifications),
.selected_cell_height = MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT,
.unselected_cell_height = MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT,
.highlight_background_color = GColorFolly,
};
case MenuLayerStyleTitleAndSubtitle:
return (MenuDetailInfo) {
.rows = menu_detail_row_data_notifications,
.num_rows = ARRAY_LENGTH(menu_detail_row_data_notifications),
.selected_cell_height = MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT,
.unselected_cell_height = MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT,
.highlight_background_color = GColorIslamicGreen,
};
case MenuLayerStyleTitleAndSubtitleAndIcon:
return (MenuDetailInfo) {
.rows = menu_detail_row_data_notifications,
.num_rows = ARRAY_LENGTH(menu_detail_row_data_notifications),
.selected_cell_height = MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT,
.unselected_cell_height = MENU_CELL_ROUND_UNFOCUSED_SHORT_CELL_HEIGHT,
.highlight_background_color = GColorFolly,
};
case MenuLayerStyleTitleAndIconOnRight:
return (MenuDetailInfo) {
.rows = menu_detail_row_data_days,
.num_rows = ARRAY_LENGTH(menu_detail_row_data_days),
.selected_cell_height = menu_cell_basic_cell_height(),
.unselected_cell_height = MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT,
.highlight_background_color = GColorIslamicGreen,
};
case MenuLayerStyleTitleAndSubtitleAndValue:
return (MenuDetailInfo) {
.rows = menu_detail_row_data_alarms,
.num_rows = ARRAY_LENGTH(menu_detail_row_data_alarms),
.selected_cell_height = MENU_CELL_ROUND_FOCUSED_SHORT_CELL_HEIGHT,
.unselected_cell_height = MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT,
.highlight_background_color = GColorIslamicGreen,
};
default:
WTF;
}
}
static int16_t prv_get_cell_height_for_menu_layer(MenuLayer *menu_layer, MenuIndex *cell_index,
MenuLayerStyle style) {
const MenuDetailInfo row_details = prv_get_row_details_for_style(style);
return menu_layer_is_index_selected(menu_layer, cell_index) ?
row_details.selected_cell_height :
row_details.unselected_cell_height;
}
static int16_t prv_menu_detail_get_cell_height(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *context) {
MenuDetailWindowData *data = context;
return prv_get_cell_height_for_menu_layer(menu_layer, cell_index, data->style);
}
static uint16_t prv_menu_detail_get_num_rows_callback(MenuLayer *menu_layer,
uint16_t section_index,
void *context) {
MenuDetailWindowData *data = context;
return prv_get_row_details_for_style(data->style).num_rows;
}
static void prv_menu_detail_draw_row(GContext *ctx, const Layer *cell_layer,
MenuDetailRowData *row_data, MenuLayerStyle style) {
const GFont title_font = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD);
switch (style) {
case MenuLayerStyleTitle: {
menu_cell_basic_draw_custom(ctx, cell_layer, title_font, row_data->title, title_font, NULL,
NULL, NULL, NULL, false /* icon_on_right */,
GTextOverflowModeWordWrap);
break;
}
case MenuLayerStyleTitleAndSubtitle: {
char *subtitle = menu_cell_layer_is_highlighted(cell_layer) ? row_data->subtitle : NULL;
menu_cell_basic_draw(ctx, cell_layer, row_data->title, subtitle, NULL);
break;
}
case MenuLayerStyleTitleAndIconOnRight: {
GBitmap *radio_button = gbitmap_create_with_resource(RESOURCE_ID_CHECKED_RADIO_BUTTON);
menu_cell_basic_draw_icon_right(ctx, cell_layer, row_data->title, row_data->subtitle,
radio_button);
gbitmap_destroy(radio_button);
break;
}
case MenuLayerStyleTitleAndSubtitleAndValue: {
const GFont subtitle_font = fonts_get_system_font(FONT_KEY_GOTHIC_14);
menu_cell_basic_draw_custom(ctx, cell_layer, title_font, row_data->title, title_font,
row_data->value, subtitle_font, row_data->subtitle, NULL, false,
GTextOverflowModeFill);
break;
}
case MenuLayerStyleTitleAndSubtitleAndIcon: {
GBitmap *icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_MENU_ICON_TICTOC_WATCH);
menu_cell_basic_draw(ctx, cell_layer, row_data->title, row_data->subtitle, icon_bitmap);
gbitmap_destroy(icon_bitmap);
break;
}
default:
WTF;
}
}
static void prv_menu_detail_draw_row_callback(GContext* ctx, const Layer *cell_layer,
MenuIndex *cell_index, void *context) {
MenuDetailWindowData *data = context;
const MenuDetailInfo menu_info = prv_get_row_details_for_style(data->style);
MenuDetailRowData row_data = menu_info.rows[cell_index->row];
prv_menu_detail_draw_row(ctx, cell_layer, &row_data, data->style);
}
static void prv_detail_window_load(Window *window) {
MenuDetailWindowData *data = window_get_user_data(window);
MenuLayer *menu_layer = &data->menu_layer;
const GRect menu_layer_frame = grect_inset_internal(window->layer.bounds, 0,
STATUS_BAR_LAYER_HEIGHT);
menu_layer_init(menu_layer, &menu_layer_frame);
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_cell_height = prv_menu_detail_get_cell_height,
.get_num_rows = prv_menu_detail_get_num_rows_callback,
.draw_row = prv_menu_detail_draw_row_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
menu_layer_set_selected_index(menu_layer, MenuIndex(0, 1), MenuRowAlignCenter, false);
const MenuDetailInfo menu_info = prv_get_row_details_for_style(data->style);
menu_layer_set_highlight_colors(menu_layer, menu_info.highlight_background_color, GColorWhite);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
StatusBarLayer *status_bar = &data->status_bar_layer;
status_bar_layer_init(status_bar);
status_bar_layer_set_colors(status_bar, GColorClear, GColorBlack);
layer_add_child(&window->layer, &status_bar->layer);
}
static void prv_detail_window_unload(Window *window) {
MenuDetailWindowData *data = window_get_user_data(window);
menu_layer_deinit(&data->menu_layer);
app_free(data);
}
static void prv_push_detail_window(MenuLayerStyle menu_layer_style) {
MenuDetailWindowData *data = app_zalloc_check(sizeof(MenuDetailWindowData));
data->style = menu_layer_style;
Window *window = &data->window;
window_init(window, WINDOW_NAME("MenuLayer Round Demo Detail Menu"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_detail_window_load,
.unload = prv_detail_window_unload,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
// Menu Chooser
//////////////////
typedef struct {
Window window;
MenuLayer menu_layer;
StatusBarLayer status_bar_layer;
} MenuChooserData;
typedef struct {
char *title;
MenuLayerStyle style;
} MenuChooserRowData;
static const MenuChooserRowData menu_chooser_row_data[] = {
{"Title Only", MenuLayerStyleTitle},
{"Title & Subtitle", MenuLayerStyleTitleAndSubtitle},
{"Title & Right Icon", MenuLayerStyleTitleAndIconOnRight},
{"Title, Sub, Value", MenuLayerStyleTitleAndSubtitleAndValue},
{"Title, Sub, Icon", MenuLayerStyleTitleAndSubtitleAndIcon},
};
static int16_t prv_menu_chooser_get_cell_height(struct MenuLayer *menu_layer, MenuIndex *cell_index,
void *context) {
return prv_get_cell_height_for_menu_layer(menu_layer, cell_index, MenuLayerStyleTitle);
}
static uint16_t prv_menu_chooser_get_num_rows_callback(struct MenuLayer *menu_layer,
uint16_t section_index,
void *context) {
return ARRAY_LENGTH(menu_chooser_row_data);
}
static void prv_menu_chooser_draw_row_callback(GContext* ctx, const Layer *cell_layer,
MenuIndex *cell_index, void *context) {
MenuChooserRowData row_data = menu_chooser_row_data[cell_index->row];
menu_cell_basic_draw(ctx, cell_layer, row_data.title, NULL, NULL);
}
static void prv_menu_chooser_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index,
void *context) {
prv_push_detail_window(menu_chooser_row_data[cell_index->row].style);
}
static void prv_window_load(Window *window) {
MenuChooserData *data = window_get_user_data(window);
MenuLayer *menu_layer = &data->menu_layer;
const GRect menu_layer_frame = grect_inset_internal(window->layer.bounds, 0,
STATUS_BAR_LAYER_HEIGHT);
menu_layer_init(menu_layer, &menu_layer_frame);
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_cell_height = prv_menu_chooser_get_cell_height,
.get_num_rows = prv_menu_chooser_get_num_rows_callback,
.draw_row = prv_menu_chooser_draw_row_callback,
.select_click = prv_menu_chooser_select_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
menu_layer_set_selected_index(menu_layer, MenuIndex(0, 1), MenuRowAlignCenter, false);
menu_layer_set_highlight_colors(menu_layer, GColorPictonBlue, GColorWhite);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
StatusBarLayer *status_bar = &data->status_bar_layer;
status_bar_layer_init(status_bar);
status_bar_layer_set_colors(status_bar, GColorClear, GColorBlack);
layer_add_child(&window->layer, &status_bar->layer);
}
static void prv_window_unload(Window *window) {
MenuChooserData *data = window_get_user_data(window);
menu_layer_deinit(&data->menu_layer);
}
// App boilerplate
////////////////////
static void prv_init(void) {
MenuChooserData *data = app_zalloc_check(sizeof(MenuChooserData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("MenuLayer Round Demo Chooser Menu"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
.unload = prv_window_unload,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
static void prv_deinit(void) {
MenuChooserData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
prv_init();
app_event_loop();
prv_deinit();
}
const PebbleProcessMd* menu_round_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "MenuLayer Round Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* menu_round_app_get_info();

View file

@ -0,0 +1,122 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "morph_square_demo.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/kino/kino_layer.h"
#include "applib/ui/kino/kino_reel/morph_square.h"
#include "applib/ui/kino/kino_reel/transform.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
typedef struct {
Window window;
KinoLayer icon_layer;
KinoReel *icon_reel;
} MorphSquareDemoData;
static void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) {
MorphSquareDemoData *data = context;
kino_player_rewind(kino_layer_get_player(&data->icon_layer));
kino_layer_play(&data->icon_layer);
}
static void prv_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler);
window_set_click_context(BUTTON_ID_UP, context);
window_set_click_context(BUTTON_ID_SELECT, context);
window_set_click_context(BUTTON_ID_DOWN, context);
}
static void prv_window_load(Window *window) {
MorphSquareDemoData *data = window_get_user_data(window);
Layer *window_layer = window_get_root_layer(window);
kino_layer_init(&data->icon_layer, &window_layer->bounds);
KinoReel *from_image = kino_reel_create_with_resource(RESOURCE_ID_NOTIFICATION_GENERIC_LARGE);
KinoReel *to_image = kino_reel_create_with_resource(RESOURCE_ID_GENERIC_CONFIRMATION_LARGE);
KinoReel *icon_reel = kino_reel_morph_square_create(from_image, true);
kino_reel_transform_set_to_reel(icon_reel, to_image, true);
kino_reel_transform_set_transform_duration(icon_reel, 10000);
kino_layer_set_reel(&data->icon_layer, icon_reel, true);
layer_add_child(window_layer, (Layer *)&data->icon_layer);
}
static void prv_window_appear(Window *window) {
MorphSquareDemoData *data = window_get_user_data(window);
kino_layer_play(&data->icon_layer);
}
static void prv_window_unload(Window *window) {
MorphSquareDemoData *data = window_get_user_data(window);
kino_layer_deinit(&data->icon_layer);
}
static void prv_init(void) {
MorphSquareDemoData *data = app_zalloc_check(sizeof(MorphSquareDemoData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Morph Square Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
.appear = prv_window_appear,
.unload = prv_window_unload,
});
window_set_click_config_provider_with_context(window, prv_click_config_provider, data);
const bool animated = true;
app_window_stack_push(window, animated);
}
static void prv_deinit(void) {
MorphSquareDemoData *data = app_state_get_user_data();
app_free(data);
}
///////////////////////////
// App boilerplate
///////////////////////////
static void s_main(void) {
prv_init();
app_event_loop();
prv_deinit();
}
const PebbleProcessMd *morph_square_demo_get_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common = {
.main_func = s_main,
// UUID: 6447c83d-52b7-4579-8817-8c7ec5927cbe
.uuid = {0x64, 0x47, 0xc8, 0x3d, 0x52, 0xb7, 0x45, 0x79,
0x88, 0x17, 0x8c, 0x7e, 0xc5, 0x92, 0x7c, 0xbe},
},
.name = "Morph Square Demo",
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd *morph_square_demo_get_info(void);

View file

@ -0,0 +1,295 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "movable_line.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
static Window *s_window;
typedef enum PixelBit {
PIXEL_BIT_BOTH,
PIXEL_BIT_MSB,
PIXEL_BIT_LSB,
PIXEL_BIT_COUNT
} PixelBit;
typedef enum LineHue {
HUE_RED,
HUE_GREEN,
HUE_BLUE,
HUE_WHITE,
HUE_COUNT
} LineHue;
typedef enum LineAttribute {
ATTRIBUTE_HUE,
ATTRIBUTE_PIXEL_BIT,
ATTRIBUTE_X,
ATTRIBUTE_Y,
ATTRIBUTE_COUNT
} LineAttribute;
typedef struct AppData {
Layer *canvas_layer;
// UI state
LineAttribute selection;
// Line attributes
PixelBit pixel_bit;
LineHue hue;
GPoint intersection;
} AppData;
static void up_handler(ClickRecognizerRef recognizer, void *context) {
// Increment the selected attribute
AppData *data = window_get_user_data(s_window);
switch (data->selection) {
case ATTRIBUTE_HUE:
if (data->hue <= 0) {
data->hue = HUE_COUNT - 1;
} else {
--data->hue;
}
break;
case ATTRIBUTE_PIXEL_BIT:
if (data->pixel_bit <= 0) {
data->pixel_bit = PIXEL_BIT_COUNT - 1;
} else {
--data->pixel_bit;
}
break;
case ATTRIBUTE_X:
if (data->intersection.x > 0) {
--data->intersection.x;
}
break;
case ATTRIBUTE_Y:
if (data->intersection.y > 0) {
--data->intersection.y;
}
break;
default:
break;
}
layer_mark_dirty(data->canvas_layer);
}
static void down_handler(ClickRecognizerRef recognizer, void *context) {
// Decrement the selected attribute
AppData *data = window_get_user_data(s_window);
GRect bounds = data->canvas_layer->bounds;
switch (data->selection) {
case ATTRIBUTE_HUE:
++data->hue;
if (data->hue >= HUE_COUNT) {
data->hue = 0;
}
break;
case ATTRIBUTE_PIXEL_BIT:
++data->pixel_bit;
if (data->pixel_bit >= PIXEL_BIT_COUNT) {
data->pixel_bit = 0;
}
break;
case ATTRIBUTE_X:
if (data->intersection.x < bounds.size.w - 1) {
++data->intersection.x;
}
break;
case ATTRIBUTE_Y:
if (data->intersection.y < bounds.size.h - 1) {
++data->intersection.y;
}
break;
default:
break;
}
layer_mark_dirty(data->canvas_layer);
}
static void select_handler(ClickRecognizerRef recognizer, void *context) {
// Cycle through the attributes
AppData *data = window_get_user_data(s_window);
++data->selection;
if (data->selection >= ATTRIBUTE_COUNT) {
data->selection = 0;
}
layer_mark_dirty(data->canvas_layer);
}
static void click_config_provider(void *context) {
window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_handler);
window_single_repeating_click_subscribe(BUTTON_ID_SELECT, 100, select_handler);
window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 100, down_handler);
}
static void draw_ui_element(GContext *ctx, GRect bounds, const char *text,
bool chosen, bool selected) {
GFont font = fonts_get_system_font(chosen || selected?
FONT_KEY_GOTHIC_14_BOLD
: FONT_KEY_GOTHIC_14);
if (chosen && selected) {
// Draw rectangle behind text, invert text color
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_round_rect(ctx, &bounds, 2, GCornersAll);
graphics_context_set_text_color(ctx, GColorBlack);
} else {
graphics_context_set_text_color(ctx, GColorWhite);
}
graphics_draw_text(ctx, text, font, bounds, GTextOverflowModeFill,
GTextAlignmentCenter, NULL);
}
static void canvas_update_proc(Layer *layer, GContext* ctx) {
AppData *data = window_get_user_data(s_window);
GRect bounds = layer->bounds;
// Fill background
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, &bounds);
// Draw UI
draw_ui_element(ctx, GRect(30, 80, 20, 20), "R", data->hue == HUE_RED,
data->selection == ATTRIBUTE_HUE);
draw_ui_element(ctx, GRect(50, 80, 20, 20), "G", data->hue == HUE_GREEN,
data->selection == ATTRIBUTE_HUE);
draw_ui_element(ctx, GRect(70, 80, 20, 20), "B", data->hue == HUE_BLUE,
data->selection == ATTRIBUTE_HUE);
draw_ui_element(ctx, GRect(90, 80, 20, 20), "W", data->hue == HUE_WHITE,
data->selection == ATTRIBUTE_HUE);
draw_ui_element(ctx, GRect(30, 100, 35, 20), "Both",
data->pixel_bit == PIXEL_BIT_BOTH,
data->selection == ATTRIBUTE_PIXEL_BIT);
draw_ui_element(ctx, GRect(65, 100, 30, 20), "MSB",
data->pixel_bit == PIXEL_BIT_MSB,
data->selection == ATTRIBUTE_PIXEL_BIT);
draw_ui_element(ctx, GRect(95, 100, 30, 20), "LSB",
data->pixel_bit == PIXEL_BIT_LSB,
data->selection == ATTRIBUTE_PIXEL_BIT);
char text[6];
snprintf(text, sizeof(text), "x=%"PRId16, data->intersection.x);
draw_ui_element(ctx, GRect(30, 120, 40, 20), text,
data->selection == ATTRIBUTE_X,
data->selection == ATTRIBUTE_X);
snprintf(text, sizeof(text), "y=%"PRId16, data->intersection.y);
draw_ui_element(ctx, GRect(70, 120, 40, 20), text,
data->selection == ATTRIBUTE_Y,
data->selection == ATTRIBUTE_Y);
// Draw the lines
uint8_t saturation;
if (data->pixel_bit == PIXEL_BIT_MSB) {
saturation = 0b10101010;
} else if (data->pixel_bit == PIXEL_BIT_LSB) {
saturation = 0b01010101;
} else {
saturation = 0b11111111;
}
uint8_t r = 0, g = 0, b = 0;
if (data->hue == HUE_RED) {
r = 1;
} else if (data->hue == HUE_GREEN) {
g = 1;
} else if (data->hue == HUE_BLUE) {
b = 1;
} else if (data->hue == HUE_WHITE) {
r = 1;
g = 1;
b = 1;
}
GColor line_color = GColorFromRGB(r * saturation, g * saturation,
b * saturation);
graphics_context_set_stroke_color(ctx, line_color);
graphics_draw_line(ctx, GPoint(0, data->intersection.y),
GPoint(bounds.size.w, data->intersection.y));
graphics_draw_line(ctx, GPoint(data->intersection.x, 0),
GPoint(data->intersection.x, bounds.size.h));
}
static void main_window_load(Window *window) {
AppData *data = window_get_user_data(s_window);
Layer *window_layer = window_get_root_layer(window);
GRect window_bounds = window_layer->bounds;
data->canvas_layer = layer_create(GRect(0, 0, window_bounds.size.w,
window_bounds.size.h));
layer_set_update_proc(data->canvas_layer, canvas_update_proc);
layer_add_child(window_layer, data->canvas_layer);
}
static void main_window_unload(Window *window) {
AppData *data = window_get_user_data(s_window);
layer_destroy(data->canvas_layer);
}
static void init(void) {
AppData *data = task_zalloc(sizeof(AppData));
if (!data) {
return;
}
s_window = window_create();
window_set_user_data(s_window, data);
window_set_fullscreen(s_window, true);
window_set_window_handlers(s_window, &(WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload,
});
window_set_click_config_provider(s_window, click_config_provider);
const bool animated = true;
app_window_stack_push(s_window, animated);
}
static void deinit(void) {
AppData *data = window_get_user_data(s_window);
task_free(data);
window_destroy(s_window);
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* movable_line_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Movable Line"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* movable_line_get_app_info();

View file

@ -0,0 +1,76 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "number_field_app.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "system/passert.h"
typedef struct {
NumberWindow num;
} AppData;
static void selected(NumberWindow *nw, void *ctx) {
PBL_LOG(LOG_LEVEL_DEBUG, "selected: %"PRId32, number_window_get_value(nw));
const bool animated = true;
app_window_stack_pop(animated);
(void)ctx;
}
static void handle_init(void) {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
number_window_init(&data->num, "Some Number",
(NumberWindowCallbacks) { .selected = selected },
data);
number_window_set_min(&data->num, 10);
number_window_set_max(&data->num, 100);
number_window_set_step_size(&data->num, 5);
const bool animated = true;
app_window_stack_push(&data->num.window, animated);
}
static void handle_deinit(void) {
AppData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* number_field_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "NumberField Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* number_field_app_get_info();

View file

@ -0,0 +1,101 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "option_menu_demo.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/option_menu_window.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "resource/resource_ids.auto.h"
#include "system/logging.h"
#include "util/size.h"
const char *s_strings[] = {
"One",
"Two",
"Three",
"Four",
};
static void prv_menu_select(OptionMenu *option_menu, int selection, void *context) {
PBL_LOG(LOG_LEVEL_DEBUG, "Option Menu Demo: selected %d", selection);
}
static uint16_t prv_menu_get_num_rows(OptionMenu *option_menu, void *context) {
return ARRAY_LENGTH(s_strings);
}
static void prv_menu_draw_row(OptionMenu *option_menu, GContext *ctx, const Layer *cell_layer,
const GRect *text_frame, uint32_t row, bool selected, void *context) {
option_menu_system_draw_row(option_menu, ctx, cell_layer, text_frame, s_strings[row], selected,
context);
}
static void prv_menu_unload(OptionMenu *option_menu, void *context) {
option_menu_destroy(option_menu);
}
static void prv_init(void) {
OptionMenu *option_menu = option_menu_create();
const OptionMenuConfig config = {
.title = "Option Menu",
.choice = OPTION_MENU_CHOICE_NONE,
.status_colors = { GColorDarkGray, GColorWhite },
.highlight_colors = { PBL_IF_COLOR_ELSE(GColorCobaltBlue, GColorBlack), GColorWhite },
.icons_enabled = true
};
option_menu_configure(option_menu, &config);
option_menu_set_callbacks(option_menu, &(OptionMenuCallbacks) {
.select = prv_menu_select,
.get_num_rows = prv_menu_get_num_rows,
.draw_row = prv_menu_draw_row,
.unload = prv_menu_unload,
}, option_menu);
const bool animated = true;
app_window_stack_push(&option_menu->window, animated);
}
static void prv_deinit(void) {
}
///////////////////////////
// App boilerplate
///////////////////////////
static void s_main(void) {
prv_init();
app_event_loop();
prv_deinit();
}
const PebbleProcessMd *option_menu_demo_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common = {
.main_func = s_main,
// UUID: e8f5d3cc-76ad-4575-97da-6d2049a1b3a4
.uuid = {0xe8, 0xf5, 0xd3, 0xcc, 0x76, 0xad, 0x45, 0x75,
0x97, 0xda, 0x6d, 0x20, 0x49, 0xa1, 0xb3, 0xa4},
},
.name = "Option Menu Demo",
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd *option_menu_demo_get_app_info(void);

View file

@ -0,0 +1,574 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pebble_colors.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include <stdio.h>
#define ALPHA_0 0x00
#define ALPHA_33 0x40
#define ALPHA_66 0x80
#define ALPHA_100 0xC0
static const int TARGET_FPS = 30;
static Window *s_window;
static Layer *s_canvas_layer;
static uint8_t *s_color_table = NULL;
// Sorted by Hue, Value, Saturation
static uint8_t color_table_hvs[] = {
0x00,
0x15,
0x10,
0x2a,
0x25,
0x20,
0x3f,
0x3a,
0x35,
0x30,
0x34,
0x24,
0x39,
0x38,
0x14,
0x29,
0x28,
0x3e,
0x3d,
0x3c,
0x2c,
0x18,
0x2d,
0x1c,
0x04,
0x19,
0x08,
0x2e,
0x1d,
0x0c,
0x0d,
0x09,
0x1e,
0x0e,
0x05,
0x1a,
0x0a,
0x2f,
0x1f,
0x0f,
0x0b,
0x06,
0x1b,
0x07,
0x01,
0x16,
0x02,
0x2b,
0x17,
0x03,
0x13,
0x12,
0x27,
0x23,
0x11,
0x26,
0x22,
0x3b,
0x37,
0x33,
0x32,
0x21,
0x36,
0x31,
};
// Sorted by Hue, Saturation, Value
static uint8_t color_table_hsv[] = {
0x00,
0x15,
0x2a,
0x3f,
0x3a,
0x25,
0x35,
0x10,
0x20,
0x30,
0x34,
0x39,
0x24,
0x38,
0x3e,
0x29,
0x3d,
0x14,
0x28,
0x3c,
0x2c,
0x2d,
0x18,
0x1c,
0x2e,
0x19,
0x1d,
0x04,
0x08,
0x0c,
0x0d,
0x1e,
0x09,
0x0e,
0x2f,
0x1a,
0x1f,
0x05,
0x0a,
0x0f,
0x0b,
0x1b,
0x06,
0x07,
0x2b,
0x16,
0x17,
0x01,
0x02,
0x03,
0x13,
0x27,
0x12,
0x23,
0x3b,
0x26,
0x37,
0x11,
0x22,
0x33,
0x32,
0x36,
0x21,
0x31,
};
typedef enum {
PROPERTY_FG_COLOR,
PROPERTY_BG_COLOR,
PROPERTY_ALPHA,
// Add more above here
PROPERTY_MAX,
PROPERTY_COLOR_TABLE, // Currently don't allow to switch the color table order
} ColorProperties;
typedef struct AppData {
uint8_t property;
GColor bg_color;
GColor fg_color;
uint8_t alpha;
TextLayer *alpha_text;
char alpha_text_buffer[30];
TextLayer *fg_text;
char fg_text_buffer[5];
TextLayer *fg_color_text;
char fg_color_text_buffer[20];
uint8_t fg_color_index;
TextLayer *bg_text;
char bg_text_buffer[5];
TextLayer *bg_color_text;
char bg_color_text_buffer[20];
uint8_t bg_color_index;
} AppData;
static void set_text_element(TextLayer *text_layer, char *text_buffer, bool highlight) {
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD);
text_layer_set_font(text_layer, font);
if (highlight) {
text_layer_set_background_color(text_layer, GColorWhite);
text_layer_set_text_color(text_layer, GColorBlack);
} else {
text_layer_set_background_color(text_layer, GColorClear);
text_layer_set_text_color(text_layer, GColorWhite);
}
text_layer_set_text_alignment(text_layer, GTextAlignmentLeft);
text_layer_set_text(text_layer, text_buffer);
}
static void set_text_layers(AppData *data) {
// Set Alpha Text
uint8_t alpha_percent = 0;
if (data->alpha == ALPHA_100) {
alpha_percent = 100;
} else if (data->alpha == ALPHA_66) {
alpha_percent = 50; // FIXME: Currently don't support 66
} else if (data->alpha == ALPHA_33) {
alpha_percent = 25; // FIXME: Currenlty don't support 33
} else if (data->alpha == ALPHA_0) {
alpha_percent = 0;
}
snprintf(data->alpha_text_buffer, sizeof(data->alpha_text_buffer), " a = %d %%", alpha_percent);
if (data->property == PROPERTY_ALPHA) {
set_text_element(data->alpha_text, data->alpha_text_buffer, true);
} else {
set_text_element(data->alpha_text, data->alpha_text_buffer, false);
}
// Set FG Text
strncpy(data->fg_text_buffer, "FG =", sizeof(data->fg_text_buffer));
if (data->property == PROPERTY_FG_COLOR) {
set_text_element(data->fg_text, data->fg_text_buffer, true);
} else {
set_text_element(data->fg_text, data->fg_text_buffer, false);
}
// Set BG Text
strncpy(data->bg_text_buffer, "BG =", sizeof(data->bg_text_buffer));
if (data->property == PROPERTY_BG_COLOR) {
set_text_element(data->bg_text, data->bg_text_buffer, true);
} else {
set_text_element(data->bg_text, data->bg_text_buffer, false);
}
// Set FG Color Text
uint8_t fg_color = data->fg_color.argb;
snprintf(data->fg_color_text_buffer, sizeof(data->fg_color_text_buffer), "FG = 0x%02x", fg_color);
if (data->property == PROPERTY_FG_COLOR) {
set_text_element(data->fg_color_text, data->fg_color_text_buffer, true);
} else {
set_text_element(data->fg_color_text, data->fg_color_text_buffer, false);
}
// Set BG Color Text
uint8_t bg_color = data->bg_color.argb;
snprintf(data->bg_color_text_buffer, sizeof(data->bg_color_text_buffer), "BG = 0x%02x", bg_color);
if (data->property == PROPERTY_BG_COLOR) {
set_text_element(data->bg_color_text, data->bg_color_text_buffer, true);
} else {
set_text_element(data->bg_color_text, data->bg_color_text_buffer, false);
}
layer_mark_dirty(text_layer_get_layer(data->alpha_text));
layer_mark_dirty(text_layer_get_layer(data->fg_text));
layer_mark_dirty(text_layer_get_layer(data->bg_text));
}
static void draw_color_point(GContext* ctx, AppData *data, GPoint point) {
GColor fg_color = (GColor){ .argb = (data->fg_color.argb | ALPHA_100) };
GColor bg_color = (GColor){ .argb = (data->bg_color.argb | ALPHA_100) };
uint8_t alpha = data->alpha;
if (alpha == ALPHA_100) {
graphics_context_set_stroke_color(ctx, fg_color);
graphics_draw_pixel(ctx, point);
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y));
graphics_draw_pixel(ctx, GPoint(point.x, point.y + 1));
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y + 1));
} else if (alpha == ALPHA_66) {
graphics_context_set_stroke_color(ctx, fg_color);
graphics_draw_pixel(ctx, point);
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y + 1));
graphics_context_set_stroke_color(ctx, bg_color);
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y));
graphics_draw_pixel(ctx, GPoint(point.x, point.y + 1));
} else if (alpha == ALPHA_33) {
graphics_context_set_stroke_color(ctx, fg_color);
graphics_draw_pixel(ctx, point);
graphics_context_set_stroke_color(ctx, bg_color);
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y));
graphics_draw_pixel(ctx, GPoint(point.x, point.y + 1));
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y + 1));
} else if (alpha == ALPHA_0) {
graphics_context_set_stroke_color(ctx, bg_color);
graphics_draw_pixel(ctx, point);
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y));
graphics_draw_pixel(ctx, GPoint(point.x, point.y + 1));
graphics_draw_pixel(ctx, GPoint(point.x + 1, point.y + 1));
}
}
static void draw_color_rect(GContext* ctx, AppData *data, GRect rect) {
uint16_t width = rect.size.w;
uint16_t height = rect.size.h;
for (uint16_t row = 0; row < height; row += 2) {
for (uint16_t col = 0; col < width; col += 2) {
GPoint point = GPoint(rect.origin.x + col, rect.origin.y + row);
draw_color_point(ctx, data, point);
}
}
}
static void draw_boxes(GContext* ctx, AppData *data) {
// Draw border
if (gcolor_equal(data->fg_color, GColorBlack)) {
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_round_rect(ctx, &GRect(35, 1, 32, 22), 4, GCornersAll);
}
// Draw foreground color
GColor fg_color = (GColor){ .argb = (data->fg_color.argb | ALPHA_100) };
graphics_context_set_fill_color(ctx, fg_color);
graphics_fill_round_rect(ctx, &GRect(36, 2, 30, 20), 4, GCornersAll);
// Draw border
if (gcolor_equal(data->bg_color, GColorBlack)) {
graphics_context_set_fill_color(ctx, GColorWhite);
graphics_fill_round_rect(ctx, &GRect(35, 47, 32, 22), 4, GCornersAll);
}
// Draw background color
GColor bg_color = (GColor){ .argb = (data->bg_color.argb | ALPHA_100) };
graphics_context_set_fill_color(ctx, bg_color);
graphics_fill_round_rect(ctx, &GRect(36, 48, 30, 20), 4, GCornersAll);
}
#define COLOR_BAR_WIDTH 4
#define COLOR_BAR_HEIGHT 24
#define ROW_LENGTH 32
static void draw_color_wheel_box(GContext* ctx, AppData *data) {
GPoint origin = GPoint(8, 114);
GColor compare_color = GColorClear;
uint8_t color_index_match = 0;
uint16_t height_offset = 0;
bool compare = true;
if (data->property == PROPERTY_FG_COLOR) {
compare_color = data->fg_color;
} else if (data->property == PROPERTY_BG_COLOR) {
compare_color = data->bg_color;
} else {
compare = false;
}
for (uint8_t row = 0; row < 2; row++) {
for (uint8_t color_index = row * ROW_LENGTH; color_index < (row + 1) * ROW_LENGTH;
color_index++) {
GColor color = (GColor){.argb = (s_color_table[color_index] | ALPHA_100)};
if (compare && gcolor_equal(color, compare_color)) {
color_index_match = color_index - (row * ROW_LENGTH);
height_offset = row * (COLOR_BAR_HEIGHT + 4);
}
GRect box = GRect(origin.x + (4 * (color_index - (row * ROW_LENGTH))),
origin.y + (row * (COLOR_BAR_HEIGHT + 4)),
COLOR_BAR_WIDTH, COLOR_BAR_HEIGHT);
graphics_context_set_fill_color(ctx, color);
graphics_fill_rect(ctx, &box);
}
}
// Draw border
if (compare) {
const GRect box = GRect(origin.x + 4*color_index_match - 1, origin.y - 1 + height_offset,
COLOR_BAR_WIDTH + 2, COLOR_BAR_HEIGHT + 2);
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_draw_rect(ctx, &box);
}
}
static void up_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
if (data->property == PROPERTY_FG_COLOR) {
data->fg_color_index = (data->fg_color_index + 1) & 0x3F;
data->fg_color.argb = (s_color_table[data->fg_color_index] | ALPHA_100);
} else if (data->property == PROPERTY_BG_COLOR) {
data->bg_color_index = (data->bg_color_index + 1) & 0x3F;
data->bg_color.argb = (s_color_table[data->bg_color_index] | ALPHA_100);
} else if (data->property == PROPERTY_ALPHA) {
data->alpha = (data->alpha + 0x40) & ALPHA_100;
set_text_layers(data);
} else if (data->property == PROPERTY_COLOR_TABLE) {
if (s_color_table == color_table_hsv) {
s_color_table = color_table_hvs;
} else if (s_color_table == color_table_hvs) {
s_color_table = color_table_hsv;
}
}
layer_mark_dirty(s_canvas_layer);
}
static void select_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
data->property = (data->property + 1) % PROPERTY_MAX;
layer_mark_dirty(s_canvas_layer);
}
static void down_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
if (data->property == PROPERTY_FG_COLOR) {
data->fg_color_index = (data->fg_color_index - 1) & 0x3F;
data->fg_color.argb = (s_color_table[data->fg_color_index] | ALPHA_100);
} else if (data->property == PROPERTY_BG_COLOR) {
data->bg_color_index = (data->bg_color_index - 1) & 0x3F;
data->bg_color.argb = (s_color_table[data->bg_color_index] | ALPHA_100);
} else if (data->property == PROPERTY_ALPHA) {
data->alpha = (data->alpha - 0x40) & ALPHA_100;
set_text_layers(data);
} else if (data->property == PROPERTY_COLOR_TABLE) {
if (s_color_table == color_table_hsv) {
s_color_table = color_table_hvs;
} else if (s_color_table == color_table_hvs) {
s_color_table = color_table_hsv;
}
}
layer_mark_dirty(s_canvas_layer);
}
static void click_config_provider(void *context) {
window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_handler);
window_single_repeating_click_subscribe(BUTTON_ID_SELECT, 100, select_handler);
window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 100, down_handler);
}
static void layer_update_proc(Layer *layer, GContext* ctx) {
AppData *data = window_get_user_data(s_window);
graphics_context_set_fill_color(ctx, GColorBlack);
graphics_fill_rect(ctx, &layer->bounds);
draw_boxes(ctx, data);
set_text_layers(data);
GFont font = fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD);
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_draw_rect(ctx, &GRect(71, 0, 73, 111)); // Border around BG
graphics_context_set_fill_color(ctx, data->bg_color);
graphics_fill_rect(ctx, &GRect(72, 0, 72, 110));
graphics_context_set_text_color(ctx, GColorWhite);
graphics_draw_text(ctx, "BG", font, GRect(72, 110 - 16, 20, 16),
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
draw_color_rect(ctx, data, GRect(92, 0, 62, 90));
graphics_context_set_text_color(ctx, GColorWhite);
if (data->alpha < ALPHA_100) {
graphics_draw_text(ctx, "FG+BG", font, GRect(92, 90 - 16, 62, 16),
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
else {
graphics_draw_text(ctx, "FG", font, GRect(92, 90 - 16, 62, 16),
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
if (data->alpha < ALPHA_100) {
graphics_context_set_fill_color(ctx, data->fg_color);
graphics_fill_rect(ctx, &GRect(124, 0, 20, 40));
graphics_context_set_text_color(ctx, GColorWhite);
graphics_draw_text(ctx, "FG", font, GRect(124, 40 - 16, 20, 16),
GTextOverflowModeFill, GTextAlignmentLeft, NULL);
}
draw_color_wheel_box(ctx, data);
}
static void main_window_load(Window *window) {
AppData *data = window_get_user_data(s_window);
Layer *window_layer = window_get_root_layer(window);
GRect window_bounds = window_layer->bounds;
// Create Layer
s_canvas_layer = layer_create(GRect(0, 0, window_bounds.size.w, window_bounds.size.h));
layer_add_child(window_layer, s_canvas_layer);
// Set the update_proc
layer_set_update_proc(s_canvas_layer, layer_update_proc);
data->fg_text = text_layer_create(GRect(2, 2, 28, 20));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(data->fg_text));
data->fg_color_text = text_layer_create(GRect(2, 24, 64, 20));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(data->fg_color_text));
data->fg_color_index = 8;
data->bg_text = text_layer_create(GRect(2, 48, 28, 20));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(data->bg_text));
data->bg_color_text = text_layer_create(GRect(2, 70, 64, 20));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(data->bg_color_text));
data->bg_color_index = 0;
data->alpha_text = text_layer_create(GRect(2, 92, 64, 20));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(data->alpha_text));
// Other properties
s_color_table = color_table_hsv;
data->alpha = ALPHA_100;
data->fg_color.argb = s_color_table[data->fg_color_index] | ALPHA_100;
data->bg_color = GColorBlack;
set_text_layers(data);
}
static void main_window_unload(Window *window) {
// Destroy Layer
layer_destroy(s_canvas_layer);
}
static void init(void) {
AppData *data = task_malloc(sizeof(AppData));
if (!data) {
return;
}
memset(data, 0x00, sizeof(AppData));
s_window = window_create();
window_set_user_data(s_window, data);
window_set_fullscreen(s_window, true);
window_set_window_handlers(s_window, &(WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload,
});
window_set_click_config_provider(s_window, click_config_provider);
const bool animated = true;
app_window_stack_push(s_window, animated);
}
static void deinit(void) {
AppData *data = window_get_user_data(s_window);
task_free(data);
window_destroy(s_window);
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* pebble_colors_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Pebble Colors"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* pebble_colors_get_app_info();

View file

@ -0,0 +1,551 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pebble_shapes.h"
#include "applib/app.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gpath.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/gtransform.h"
#include "applib/graphics/text.h"
#include "util/trig.h"
#include "applib/ui/action_bar_layer.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
typedef enum {
POINT,
LINE,
SQUARE,
RECTANGLE,
RECTANGLE_ROUND,
CIRCLE,
GPATH_TRIANGLE,
GPATH_OPEN_BUCKET,
MAX_SHAPES
} DrawShape;
#define NUM_SHAPES MAX_SHAPES
static GColor s_display_colors[NUM_SHAPES];
#define MAX_STROKE_WIDTH 20
static const GPathInfo s_triangle_path = {
3,
(GPoint[]) {
{ -10, 0 },
{ 0, 10 },
{ 10, 0 }
}
};
static const GPathInfo s_bucket_path = {
4,
(GPoint[]) {
{ -10, 0 },
{ -10, 30 },
{ 10, 30 },
{ 10, 0 }
}
};
static const int TARGET_FPS = 40;
static const int PIXEL_SPEED_PER_FRAME = 2;
#define ANGLE_DEGREES_TO_TRIG_ANGLE(angle) (((angle % 360) * TRIG_MAX_ANGLE) / 360)
#define MAX_SCALE 10
typedef enum {
APP_STATE_FILL_NON_AA,
APP_STATE_FILL_AA,
APP_STATE_DRAW_NON_AA_NO_SW,
APP_STATE_DRAW_AA_NO_SW,
APP_STATE_DRAW_NON_AA_SW,
APP_STATE_DRAW_AA_SW,
// Add more above
APP_STATE_NUM_STATES
} AppStateIndex;
typedef struct AppData {
Window window;
// Point properties
GPoint point_p0;
int16_t point_velocity_x;
int16_t point_velocity_y;
// Line properties
GPoint line_p0;
GPoint line_p1;
int16_t line_velocity_x;
int16_t line_velocity_y;
// Square properties
GRect square;
int16_t square_velocity_x;
int16_t square_velocity_y;
// Rectangle properties
GRect rect;
int16_t rect_velocity_x;
int16_t rect_velocity_y;
// Rectangle Round properties
GRect rectr;
uint16_t rectr_radius;
uint16_t rectr_corners_index;
int16_t rectr_velocity_x;
int16_t rectr_velocity_y;
// Circle properties
GPoint circle_origin;
uint16_t circle_radius;
int16_t circle_velocity_x;
int16_t circle_velocity_y;
GColor circle_color;
// Triangle
GPath *triangle;
GPoint triangle_offset;
int16_t triangle_velocity_x;
int16_t triangle_velocity_y;
// Bucket
GPath *bucket;
GPoint bucket_offset;
int16_t bucket_velocity_x;
int16_t bucket_velocity_y;
// Generic properties that can be changed as property_index changes
bool fill;
int16_t color_index;
int64_t time_started;
uint32_t rendered_frames;
bool moving;
AppStateIndex state_index;
int16_t stroke_width;
bool antialiased;
} AppData;
static void log_state(AppData *data) {
switch (data->state_index) {
case APP_STATE_FILL_NON_AA:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Fill Non-Antialiased; SW: N/A (but currently: %d)",
data->stroke_width);
break;
case APP_STATE_FILL_AA:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Fill Antialiased; SW: N/A (but currently: %d)",
data->stroke_width);
break;
case APP_STATE_DRAW_NON_AA_NO_SW:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Draw Non-Antialiased; SW: N/A (but currently: %d)",
data->stroke_width);
break;
case APP_STATE_DRAW_AA_NO_SW:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Draw Antialiased; SW: N/A (but currently: %d)",
data->stroke_width);
break;
case APP_STATE_DRAW_NON_AA_SW:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Draw Non-Antialiased; SW: %d", data->stroke_width);
break;
case APP_STATE_DRAW_AA_SW:
PBL_LOG(LOG_LEVEL_DEBUG, "State: Draw Antialiased; SW: %d", data->stroke_width);
break;
default:
PBL_LOG(LOG_LEVEL_DEBUG, "Unknown State");
break;
}
}
static bool stroke_width_enabled(AppStateIndex state_index) {
return ((state_index == APP_STATE_DRAW_AA_SW) || (state_index == APP_STATE_DRAW_NON_AA_SW));
}
static void update_state(AppData *data, AppStateIndex state_index) {
data->state_index = state_index;
data->fill = ((data->state_index == APP_STATE_FILL_NON_AA) ||
(data->state_index == APP_STATE_FILL_AA));
data->antialiased = ((data->state_index == APP_STATE_DRAW_AA_NO_SW) ||
(data->state_index == APP_STATE_DRAW_AA_SW) ||
(data->state_index == APP_STATE_FILL_AA));
}
static void back_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = app_state_get_user_data();
update_state(data, ((data->state_index - 1) % APP_STATE_NUM_STATES));
log_state(data);
}
static void up_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = app_state_get_user_data();
if (stroke_width_enabled(data->state_index)) {
data->stroke_width++;
if (data->stroke_width >= MAX_STROKE_WIDTH) {
data->stroke_width = 1;
}
}
log_state(data);
}
static void select_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = app_state_get_user_data();
update_state(data, ((data->state_index + 1) % APP_STATE_NUM_STATES));
log_state(data);
}
static void down_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = app_state_get_user_data();
data->moving = !data->moving;
log_state(data);
}
static void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_BACK, back_handler);
window_single_click_subscribe(BUTTON_ID_UP, up_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, select_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_handler);
}
static void prv_move_shape(AppData *data) {
for (uint8_t shape = POINT; shape < MAX_SHAPES; shape++) {
if (shape == POINT) {
// Move the point 4*X per Y
data->point_p0.x += (data->point_velocity_x * PIXEL_SPEED_PER_FRAME * 4);
if (data->point_p0.x < 0 || data->point_p0.x > data->window.layer.bounds.size.w) {
data->point_velocity_x = data->point_velocity_x * -1;
}
data->point_p0.y += (data->point_velocity_y * PIXEL_SPEED_PER_FRAME);
if (data->point_p0.y < 0 || data->point_p0.y > data->window.layer.bounds.size.h) {
data->point_velocity_y = data->point_velocity_y * -1;
}
} else if (shape == LINE) {
// Move the line 2*X per Y
data->line_p0.x += (data->line_velocity_x * PIXEL_SPEED_PER_FRAME * 2);
data->line_p1.x += (data->line_velocity_x * PIXEL_SPEED_PER_FRAME * 2);
if (data->line_p0.x < 0 || data->line_p0.x > data->window.layer.bounds.size.w ||
data->line_p1.x < 0 || data->line_p1.x > data->window.layer.bounds.size.w ) {
data->line_velocity_x = data->line_velocity_x * -1;
}
data->line_p0.y += (data->line_velocity_y * PIXEL_SPEED_PER_FRAME);
data->line_p1.y += (data->line_velocity_y * PIXEL_SPEED_PER_FRAME);
if (data->line_p0.y < 0 || data->line_p0.y > data->window.layer.bounds.size.h ||
data->line_p1.y < 0 || data->line_p1.y > data->window.layer.bounds.size.h ) {
data->line_velocity_y = data->line_velocity_y * -1;
}
} else if (shape == SQUARE) {
// Move the square X per Y
data->square.origin.x += (data->square_velocity_x * PIXEL_SPEED_PER_FRAME);
if (data->square.origin.x < 0 ||
data->square.origin.x + data->square.size.w > data->window.layer.bounds.size.w) {
data->square_velocity_x = data->square_velocity_x * -1;
}
data->square.origin.y += (data->square_velocity_y * PIXEL_SPEED_PER_FRAME);
if (data->square.origin.y < 0 ||
data->square.origin.y + data->square.size.h > data->window.layer.bounds.size.h) {
data->square_velocity_y = data->square_velocity_y * -1;
}
} else if (shape == RECTANGLE) {
// Move the rectangle X per 2*Y
data->rect.origin.x += (data->rect_velocity_x * PIXEL_SPEED_PER_FRAME);
if (data->rect.origin.x < 0 ||
data->rect.origin.x + data->rect.size.w > data->window.layer.bounds.size.w) {
data->rect_velocity_x = data->rect_velocity_x * -1;
}
data->rect.origin.y += (data->rect_velocity_y * PIXEL_SPEED_PER_FRAME * 2);
if (data->rect.origin.y < 0 ||
data->rect.origin.y + data->rect.size.h > data->window.layer.bounds.size.h) {
data->rect_velocity_y = data->rect_velocity_y * -1;
}
} else if (shape == RECTANGLE_ROUND) {
// Move rounded line X per 4*Y
data->rectr.origin.x += (data->rectr_velocity_x * PIXEL_SPEED_PER_FRAME);
if (data->rectr.origin.x < 0 ||
data->rectr.origin.x + data->rectr.size.w > data->window.layer.bounds.size.w) {
data->rectr_velocity_x = data->rectr_velocity_x * -1;
}
data->rectr.origin.y += (data->rectr_velocity_y * PIXEL_SPEED_PER_FRAME * 4);
if (data->rectr.origin.y < 0 ||
data->rectr.origin.y + data->rectr.size.h > data->window.layer.bounds.size.h) {
data->rectr_velocity_y = data->rectr_velocity_y * -1;
}
} else if (shape == CIRCLE) {
// Move the line X per Y
data->circle_origin.x += (data->circle_velocity_x * PIXEL_SPEED_PER_FRAME);
if (data->circle_origin.x - data->circle_radius < 0 ||
data->circle_origin.x + data->circle_radius > data->window.layer.bounds.size.w) {
data->circle_velocity_x = data->circle_velocity_x * -1;
data->circle_color.argb = (((data->circle_color.argb + 1) & 0x3F) | 0xC0);
}
data->circle_origin.y += (data->circle_velocity_y * PIXEL_SPEED_PER_FRAME);
if (data->circle_origin.y - data->circle_radius < 0 ||
data->circle_origin.y + data->circle_radius > data->window.layer.bounds.size.h) {
data->circle_velocity_y = data->circle_velocity_y * -1;
data->circle_color.argb = (((data->circle_color.argb + 1) & 0x3F) | 0xC0);
}
} else if (shape == GPATH_TRIANGLE) {
// Move the line 3*X per Y
data->triangle_offset.x += (data->triangle_velocity_x * PIXEL_SPEED_PER_FRAME * 3);
if (data->triangle_offset.x < 0 ||
data->triangle_offset.x > data->window.layer.bounds.size.w) {
data->triangle_velocity_x = data->triangle_velocity_x * -1;
}
data->triangle_offset.y += (data->triangle_velocity_y * PIXEL_SPEED_PER_FRAME);
if (data->triangle_offset.y < 0 ||
data->triangle_offset.y > data->window.layer.bounds.size.h) {
data->triangle_velocity_y = data->triangle_velocity_y * -1;
}
gpath_move_to(data->triangle, data->triangle_offset);
} else if (shape == GPATH_OPEN_BUCKET) {
// Move the line 2*X per 3*Y
data->bucket_offset.x += (data->bucket_velocity_x * PIXEL_SPEED_PER_FRAME * 2);
if (data->bucket_offset.x < 0 ||
data->bucket_offset.x > data->window.layer.bounds.size.w) {
data->bucket_velocity_x = data->bucket_velocity_x * -1;
}
data->bucket_offset.y += (data->bucket_velocity_y * PIXEL_SPEED_PER_FRAME * 3);
if (data->bucket_offset.y < 0 ||
data->bucket_offset.y > data->window.layer.bounds.size.h) {
data->bucket_velocity_y = data->bucket_velocity_y * -1;
}
gpath_move_to(data->bucket, data->bucket_offset);
}
}
}
static void draw_shape(GContext* ctx, AppData *data, DrawShape shape, GColor color) {
graphics_context_set_fill_color(ctx, color);
graphics_context_set_stroke_color(ctx, color);
if (data->fill) {
if (shape == POINT) {
graphics_draw_pixel(ctx, data->point_p0);
} else if (shape == LINE) {
graphics_draw_line(ctx, data->line_p0, data->line_p1);
} else if (shape == SQUARE) {
graphics_fill_rect(ctx, &data->square);
} else if (shape == RECTANGLE) {
graphics_fill_rect(ctx, &data->rect);
} else if (shape == RECTANGLE_ROUND) {
graphics_fill_round_rect(ctx, &data->rectr, data->rectr_radius, GCornersAll);
} else if (shape == CIRCLE) {
graphics_context_set_fill_color(ctx, data->circle_color);
graphics_context_set_stroke_color(ctx, data->circle_color);
graphics_fill_circle(ctx, data->circle_origin, data->circle_radius);
} else if (shape == GPATH_TRIANGLE) {
gpath_draw_filled(ctx, data->triangle);
} else if (shape == GPATH_OPEN_BUCKET) {
gpath_draw_filled(ctx, data->bucket);
}
} else {
if (shape == POINT) {
graphics_draw_pixel(ctx, data->point_p0);
} else if (shape == LINE) {
graphics_draw_line(ctx, data->line_p0, data->line_p1);
} else if (shape == SQUARE) {
graphics_draw_rect(ctx, &data->square);
} else if (shape == RECTANGLE) {
graphics_draw_rect(ctx, &data->rect);
} else if (shape == RECTANGLE_ROUND) {
graphics_draw_round_rect(ctx, &data->rectr, data->rectr_radius);
} else if (shape == CIRCLE) {
graphics_context_set_fill_color(ctx, data->circle_color);
graphics_context_set_stroke_color(ctx, data->circle_color);
graphics_draw_circle(ctx, data->circle_origin, data->circle_radius);
} else if (shape == GPATH_TRIANGLE) {
gpath_draw_outline(ctx, data->triangle);
} else if (shape == GPATH_OPEN_BUCKET) {
gpath_draw_outline_open(ctx, data->bucket);
}
}
}
static int64_t prv_time_64(void) {
time_t s;
uint16_t ms;
rtc_get_time_ms(&s, &ms);
return (int64_t)s * 1000 + ms;
}
static void layer_update_proc(Layer *layer, GContext* ctx) {
AppData *data = app_state_get_user_data();
graphics_context_set_fill_color(ctx, GColorBlack);
if (stroke_width_enabled(data->state_index)) {
graphics_context_set_stroke_width(ctx, data->stroke_width);
} else {
graphics_context_set_stroke_width(ctx, 1);
}
graphics_context_set_antialiased(ctx, data->antialiased);
graphics_fill_rect(ctx, &layer->bounds);
uint8_t color_index = 0;
for (uint8_t shape_index = POINT; shape_index < MAX_SHAPES; shape_index++) {
draw_shape(ctx, data, (DrawShape)shape_index, (GColor)s_display_colors[color_index++]);
}
if (data->rendered_frames == 0) {
data->time_started = prv_time_64();
} else {
int64_t time_rendered = prv_time_64() - data->time_started;
if ((data->rendered_frames % 64) == 0) {
PBL_LOG(LOG_LEVEL_DEBUG, "## %d frames rendered", (int)data->rendered_frames);
PBL_LOG(LOG_LEVEL_DEBUG, "## at %"PRIu32" FPS",
(uint32_t)((uint64_t)data->rendered_frames*1000/time_rendered));
}
}
data->rendered_frames++;
}
static void timer_callback(void *cb_data) {
AppData *data = app_state_get_user_data();
if (data->moving) {
prv_move_shape(data);
}
layer_mark_dirty(&data->window.layer);
app_timer_register(1000 / TARGET_FPS, timer_callback, NULL);
}
static void init(void) {
AppData *data = task_malloc_check(sizeof(AppData));
memset(data, 0x00, sizeof(AppData));
s_display_colors[0] = GColorWhite;
s_display_colors[1] = GColorRed;
s_display_colors[2] = GColorGreen;
s_display_colors[3] = GColorBlue;
s_display_colors[4] = (GColor)((uint8_t)0b11111100);
s_display_colors[5] = (GColor)((uint8_t)0b11001111);
s_display_colors[6] = (GColor)((uint8_t)0b11110101);
s_display_colors[7] = GColorWhite;
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Shapes"));
window_set_user_data(window, data);
window_set_fullscreen(window, true);
layer_set_update_proc(&window->layer, layer_update_proc);
window_set_click_config_provider(window, click_config_provider);
const bool animated = true;
app_window_stack_push(window, animated);
// Initialize shapes
// Point properties
data->point_p0 = GPoint(1, 1);
data->point_velocity_x = 1;
data->point_velocity_y = 1;
// Line properties
data->line_p0 = GPoint(0, 0);
data->line_p1 = GPoint(10, 10);
data->line_velocity_x = 1;
data->line_velocity_y = 1;
// Square properties
data->square = GRect(100, 50, 20, 20);
data->square_velocity_x = 1;
data->square_velocity_y = 1;
// Rectangle properties
data->rect = GRect(80, 0, 30, 50);
data->rect_velocity_x = 1;
data->rect_velocity_y = 1;
// Rectangle Round properties
data->rectr = GRect(20, 20, 20, 30);
data->rectr_radius = 5;
data->rectr_corners_index = GCornersAll;
data->rectr_velocity_x = 1;
data->rectr_velocity_y = 1;
// Circle properties
data->circle_origin = GPoint(50, 50);
data->circle_radius = 20;
data->circle_velocity_x = 1;
data->circle_velocity_y = 1;
data->circle_color = s_display_colors[5];
// Triangle
data->triangle = gpath_create(&s_triangle_path);
data->triangle_offset = GPoint(10, 80);
gpath_move_to(data->triangle, data->triangle_offset);
data->triangle_velocity_x = 1;
data->triangle_velocity_y = 1;
// Open bucket
data->bucket = gpath_create(&s_bucket_path);
data->bucket_offset = GPoint(20, 30);
gpath_move_to(data->bucket, data->bucket_offset);
data->bucket_velocity_x = 1;
data->bucket_velocity_y = 1;
// Other properties
update_state(data, APP_STATE_FILL_NON_AA);
data->stroke_width = 1;
data->moving = true;
app_timer_register(33, timer_callback, NULL);
}
static void deinit(void) {
AppData *data = app_state_get_user_data();
task_free(data);
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* pebble_shapes_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Pebble Shapes"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* pebble_shapes_get_app_info();

View file

@ -0,0 +1,215 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "persist_app.h"
#include "applib/app.h"
#include "process_state/app_state/app_state.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "applib/persist.h"
#include <stdio.h>
#define BUFFER_SIZE 25
static const uint8_t s_music_launcher_icon_pixels[] = {
0xff, 0xff, 0x1f, 0x00, 0xff, 0xff, 0x01, 0x00, 0xff, 0x3f, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, /* bytes 0 - 16 */
0x7f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x18, 0x00, 0x7f, 0x00, 0x1f, 0x00, /* bytes 16 - 32 */
0x7f, 0xf0, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, /* bytes 32 - 48 */
0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, /* bytes 48 - 64 */
0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x1f, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x7f, 0x7c, 0x00, 0x00, /* bytes 64 - 80 */
0x03, 0x3c, 0x00, 0x00, 0x01, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x80, 0x00, 0x00, 0x3c, 0xc0, 0x00, /* bytes 80 - 96 */
0x00, 0x7e, 0xe0, 0x00, 0x00, 0xff, 0xff, 0x00, 0x81, 0xff, 0xff, 0x00,
};
static const GBitmap s_music_launcher_icon_bitmap = {
.addr = (void*) &s_music_launcher_icon_pixels,
.row_size_bytes = 4,
.info_flags = 0x1000,
.bounds = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 24, .h = 27 },
},
};
typedef struct {
Window window;
MenuLayer menu_layer;
GBitmap icon;
Window detail_window;
TextLayer detail_text;
char detail_text_buffer[50];
} AppData;
static const uint32_t COUNT_PKEY = 1;
static uint16_t get_num_sections_callback(struct MenuLayer *menu_layer, AppData *data) {
(void)data;
(void)menu_layer;
return 1;
}
static uint16_t get_num_rows_callback(struct MenuLayer *menu_layer, uint16_t section_index, AppData *data) {
(void)data;
(void)menu_layer;
switch (section_index) {
default:
case 0: return 3;
}
}
static int16_t get_header_height_callback(struct MenuLayer *menu_layer, uint16_t section_index, AppData *data) {
(void)data;
(void)menu_layer;
(void)section_index;
return MENU_CELL_BASIC_HEADER_HEIGHT;
}
static void draw_row_callback(GContext* ctx, Layer *cell_layer, MenuIndex *cell_index, AppData *data) {
(void)data;
switch (cell_index->row) {
case 0: {
int num_beers = persist_read_int(COUNT_PKEY);
char title[50];
snprintf(title, sizeof(title), "%d Bottles", num_beers);
menu_cell_basic_draw(ctx, cell_layer, title, "of beer on the wall", (GBitmap*)&s_music_launcher_icon_bitmap);
break;
}
case 1:
menu_cell_title_draw(ctx, cell_layer, "Order More");
break;
case 2:
menu_cell_title_draw(ctx, cell_layer, "Drink!");
break;
}
}
static void draw_header_callback(GContext* ctx, Layer *cell_layer, uint16_t section_index, AppData *data) {
(void)section_index;
(void)data;
menu_cell_basic_header_draw(ctx, cell_layer, "Beer Counter");
}
static void select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, AppData *data) {
(void)menu_layer;
(void)data;
switch (cell_index->row) {
case 1: {
int num_beers = persist_read_int(COUNT_PKEY);
int status = persist_write_int(COUNT_PKEY, num_beers+1);
PBL_LOG(LOG_LEVEL_DEBUG, "argh %d %d", num_beers, status);
menu_layer_reload_data(menu_layer);
break;
}
case 2: {
int num_beers = persist_read_int(COUNT_PKEY);
persist_write_int(COUNT_PKEY, num_beers-1);
menu_layer_reload_data(menu_layer);
break;
}
}
}
static void select_long_callback(MenuLayer *menu_layer, MenuIndex *cell_index, AppData *data) {
(void)menu_layer;
(void)data;
switch (cell_index->row) {
case 1: {
int num_beers = persist_read_int(COUNT_PKEY);
persist_write_int(COUNT_PKEY, num_beers + (500 + rand() % 500));
menu_layer_reload_data(menu_layer);
break;
}
case 2: {
int num_beers = persist_read_int(COUNT_PKEY);
persist_write_int(COUNT_PKEY, num_beers - (500 + rand() % 500));
menu_layer_reload_data(menu_layer);
}
}
}
static void prv_window_load(Window *window) {
AppData *data = window_get_user_data(window);
MenuLayer *menu_layer = &data->menu_layer;
menu_layer_init(menu_layer, &window->layer.bounds);
menu_layer_set_callbacks(menu_layer, data, &(MenuLayerCallbacks) {
.get_num_sections = (MenuLayerGetNumberOfSectionsCallback) get_num_sections_callback,
.get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback) get_num_rows_callback,
.get_header_height = (MenuLayerGetHeaderHeightCallback) get_header_height_callback,
.draw_row = (MenuLayerDrawRowCallback) draw_row_callback,
.draw_header = (MenuLayerDrawHeaderCallback) draw_header_callback,
.select_click = (MenuLayerSelectCallback) select_callback,
.select_long_click = (MenuLayerSelectCallback) select_long_callback,
});
menu_layer_set_click_config_onto_window(menu_layer, window);
layer_add_child(&window->layer, menu_layer_get_layer(menu_layer));
}
static void push_window(AppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Demo Menu"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init() {
AppData *data = (AppData *)app_zalloc_check(sizeof(AppData));
app_state_set_user_data(data);
push_window(data);
const int exist_result = persist_exists(COUNT_PKEY);
PBL_LOG(LOG_LEVEL_DEBUG, "- exist_result %d", exist_result);
if (exist_result == false) {
PBL_LOG(LOG_LEVEL_DEBUG, "- writing...");
persist_write_int(COUNT_PKEY, 10);
}
}
static void handle_deinit() {
AppData *data = app_state_get_user_data();
menu_layer_deinit(&data->menu_layer);
app_free(data);
}
static void s_main() {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* persist_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Persist Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}
#undef BUFFER_SIZE

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* persist_app_get_info();

View file

@ -0,0 +1,74 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "profile_mutexes_app.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "applib/ui/window_stack.h"
#include "system/logging.h"
#include "os/mutex.h"
#include "system/profiler.h"
static Window *window;
static PebbleMutex *s_mutex;
static PebbleRecursiveMutex *s_rmutex;
static void profile_mutexes(void) {
PBL_LOG(LOG_LEVEL_DEBUG, "INITIALIZING PROFILER FOR MUTEXES!");
PROFILER_INIT;
PROFILER_START;
s_mutex = mutex_create();
for (int i=0; i < 10000; i++) {
mutex_lock(s_mutex);
mutex_unlock(s_mutex);
}
mutex_destroy(s_mutex);
s_mutex = NULL;
s_rmutex = mutex_create_recursive();
for (int i=0; i < 10000; i++) {
mutex_lock_recursive(s_rmutex);
}
for (int i=0; i < 10000; i++) {
mutex_unlock_recursive(s_rmutex);
}
mutex_destroy((PebbleMutex *)s_rmutex);
s_rmutex = NULL;
PROFILER_STOP;
PROFILER_PRINT_STATS;
}
static void s_main(void) {
window = window_create();
app_window_stack_push(window, true /* Animated */);
profile_mutexes();
app_event_loop();
}
const PebbleProcessMd* profile_mutexes_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "Profile Mutexes"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* profile_mutexes_get_app_info(void);

View file

@ -0,0 +1,118 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_app.h"
#include "applib/app.h"
#include "applib/fonts/fonts.h"
#include "applib/tick_timer_service.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#include "util/math.h"
#define PROGRESS_STEP 2
typedef struct {
Window window;
ProgressLayer progress_layer;
int progress;
} ProgressAppData;
static void prv_select_click_handler(ClickRecognizerRef recognizer, void *context) {
// Reset
ProgressAppData *data = context;
data->progress = MIN_PROGRESS_PERCENT;
progress_layer_set_progress(&data->progress_layer, data->progress);
}
static void prv_up_click_handler(ClickRecognizerRef recognizer, void *context) {
// Increment
ProgressAppData *data = context;
data->progress = MIN(MAX_PROGRESS_PERCENT, data->progress + PROGRESS_STEP);
progress_layer_set_progress(&data->progress_layer, data->progress);
}
static void prv_down_click_handler(ClickRecognizerRef recognizer, void *context) {
// Decrement
ProgressAppData *data = context;
data->progress = MAX(MIN_PROGRESS_PERCENT, data->progress - PROGRESS_STEP);
progress_layer_set_progress(&data->progress_layer, data->progress);
}
static void prv_click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_select_click_handler);
window_single_repeating_click_subscribe(BUTTON_ID_UP, 200, prv_up_click_handler);
window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 200, prv_down_click_handler);
}
static void prv_window_load(Window *window) {
ProgressAppData *data = window_get_user_data(window);
data->progress = (MIN_PROGRESS_PERCENT + MAX_PROGRESS_PERCENT) / 2;
ProgressLayer* progress_layer = &data->progress_layer;
const GRect *frame = &window_get_root_layer(window)->frame;
static const uint32_t MARGIN = 20;
static const uint32_t HEIGHT = PBL_IF_COLOR_ELSE(6, 7);
const GRect progress_bounds = GRect(MARGIN, (frame->size.h - HEIGHT) / 2,
frame->size.w - (2 * MARGIN), HEIGHT);
progress_layer_init(progress_layer, &progress_bounds);
progress_layer_set_progress(progress_layer, data->progress);
layer_add_child(&window->layer, &progress_layer->layer);
progress_layer_set_corner_radius(progress_layer, PBL_IF_COLOR_ELSE(2, 3));
}
static void handle_init(void) {
ProgressAppData *data = app_zalloc_check(sizeof(ProgressAppData));
app_state_set_user_data(data);
Window* window = &data->window;
window_init(window, WINDOW_NAME("Progress Demo"));
window_set_user_data(window, data);
window_set_click_config_provider_with_context(window, prv_click_config_provider, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
static void handle_deinit(void) {
struct AppState* data = app_state_get_user_data();
tick_timer_service_unsubscribe();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* progress_app_get_info() {
static const PebbleProcessMdSystem progress_app_info = {
.common.main_func = &s_main,
.name = "Progress Bar Test"
};
return (const PebbleProcessMd*) &progress_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* progress_app_get_info();

View file

@ -0,0 +1,130 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "scroll_app.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_management/app_manager.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
typedef struct {
Window window;
ScrollLayer scroll_layer;
TextLayer text;
InverterLayer inverter;
} ScrollAppData;
static void select_click_handler(ClickRecognizerRef recognizer, ScrollAppData *data) {
PBL_LOG(LOG_LEVEL_DEBUG, "SELECT clicked!");
(void)data;
(void)recognizer;
}
#if 0
static void select_long_click_handler(ClickRecognizerRef recognizer, ScrollAppData *data) {
PBL_LOG(LOG_LEVEL_DEBUG, "SELECT long clicked!");
(void)data;
(void)recognizer;
}
#endif
static void click_config_provider(ScrollAppData *data) {
// The config that gets passed in, has already the UP and DOWN buttons configured
// to scroll up and down. It's possible to override that here, if needed.
// Configure how the SELECT button should behave:
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) select_click_handler);
window_long_click_subscribe(BUTTON_ID_SELECT, 0, (ClickHandler) select_click_handler, NULL);
}
static void prv_window_load(Window *window) {
ScrollAppData *data = window_get_user_data(window);
ScrollLayer *scroll_layer = &data->scroll_layer;
scroll_layer_init(scroll_layer, &window->layer.bounds);
scroll_layer_set_click_config_onto_window(scroll_layer, window);
scroll_layer_set_callbacks(scroll_layer, (ScrollLayerCallbacks) {
.click_config_provider = (ClickConfigProvider) click_config_provider,
});
scroll_layer_set_context(scroll_layer, data);
scroll_layer_set_content_size(scroll_layer, GSize(window->layer.bounds.size.w, 500));
const GRect max_text_bounds = GRect(0, 0, window->layer.bounds.size.w, 500);
TextLayer *text = &data->text;
text_layer_init(text, &max_text_bounds);
text_layer_set_text(text, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam quam tellus, fermentum quis vulputate quis, vestibulum interdum sapien. Vestibulum lobortis pellentesque pretium. Quisque ultricies purus eu orci convallis lacinia. Cras a urna mi. Donec convallis ante id dui dapibus nec ullamcorper erat egestas. Aenean a mauris a sapien commodo lacinia. Sed posuere mi vel risus congue ornare. Curabitur leo nisi, euismod ut pellentesque sed, suscipit sit amet lorem. Aliquam eget sem vitae sem aliquam ornare. In sem sapien, imperdiet eget pharetra a, lacinia ac justo. Suspendisse at ante nec felis facilisis eleifend.");
// Trim text layer and scroll content to fit text box
GSize max_size = text_layer_get_content_size(app_state_get_graphics_context(), text);
text_layer_set_size(text, max_size);
static const int vert_scroll_padding = 4;
scroll_layer_set_content_size(scroll_layer, GSize(window->layer.bounds.size.w,
max_size.h + vert_scroll_padding));
scroll_layer_add_child(scroll_layer, &text->layer);
InverterLayer *inverter = &data->inverter;
inverter_layer_init(inverter, &GRect(15, 15, 30, 30));
scroll_layer_add_child(scroll_layer, &inverter->layer);
layer_add_child(&window->layer, &scroll_layer->layer);
}
static void push_window(ScrollAppData *data) {
Window *window = &data->window;
window_init(window, WINDOW_NAME("Scroll Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
});
const bool animated = true;
app_window_stack_push(window, animated);
}
////////////////////
// App boilerplate
static void handle_init(void) {
ScrollAppData *data = app_malloc_check(sizeof(ScrollAppData));
app_state_set_user_data(data);
push_window(data);
}
static void handle_deinit(void) {
ScrollAppData *data = app_state_get_user_data();
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* scroll_app_get_info() {
static const PebbleProcessMdSystem s_scroll_app_info = {
.common.main_func = &s_main,
.name = "Scroller"
};
return (const PebbleProcessMd*) &s_scroll_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* scroll_app_get_info();

View file

@ -0,0 +1,103 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "simple_menu_app.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "services/common/poll_remote.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/size.h"
typedef struct {
Window window;
SimpleMenuLayer menu;
} AppData;
static void callback_a(int index, void *ctx) {
(void)index;
(void)ctx;
PBL_LOG(LOG_LEVEL_DEBUG, "A called back");
}
static void other_callback(int index, void *ctx) {
(void)ctx;
PBL_LOG(LOG_LEVEL_DEBUG, "other callback: %d", index);
}
static void poll_callback(int index, void *ctx) {
poll_remote_send_request(POLL_REMOTE_SERVICE_MAIL);
}
static const SimpleMenuItem s_menu_items[] = {
{ "Poll Mail", "", NULL, poll_callback },
{ "Title A", "Callback A", NULL, callback_a },
{ "Another Title", NULL, NULL, other_callback },
{ "Last Title", "Last subtitle", NULL, other_callback }
};
static const SimpleMenuSection s_menu_sections[] = {{ .title = NULL, .items = s_menu_items, .num_items = ARRAY_LENGTH(s_menu_items) }};
static void prv_window_load(Window *window) {
AppData *data = window_get_user_data(window);
SimpleMenuLayer *menu = &data->menu;
simple_menu_layer_init(menu, &(GRect){{0, 0}, window->layer.frame.size}, window, s_menu_sections,
ARRAY_LENGTH(s_menu_sections), data);
layer_add_child(&window->layer, simple_menu_layer_get_layer(menu));
}
static void handle_init(void) {
AppData *data = app_malloc_check(sizeof(AppData));
app_state_set_user_data(data);
Window *window = &data->window;
window_init(window, WINDOW_NAME("Simple Menu Demo"));
window_set_user_data(window, data);
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load
});
const bool animated = true;
app_window_stack_push(window, animated);
}
static void handle_deinit(void) {
AppData *data = app_state_get_user_data();
simple_menu_layer_deinit(&data->menu);
app_free(data);
}
static void s_main(void) {
handle_init();
app_event_loop();
handle_deinit();
}
const PebbleProcessMd* simple_menu_app_get_info() {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = &s_main,
.name = "SimpleMenuLayer Demo"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* simple_menu_app_get_info();

View file

@ -0,0 +1,139 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "statusbar_demo.h"
#include "kernel/pbl_malloc.h"
#include "services/common/i18n/i18n.h"
#include "applib/app.h"
#include "applib/ui/ui.h"
#include "util/size.h"
typedef struct StatusBarDemoWindow {
Window window;
TextLayer text;
StatusBarLayer status_bar;
} StatusBarDemoWindow;
static Window * prv_window_create(void);
static void prv_handle_click(ClickRecognizerRef ref, void *context) {
Window *window = prv_window_create();
app_window_stack_push(window, true);
}
static void prv_click_config_proivider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, prv_handle_click);
}
static void prv_window_unload(Window *window) {
StatusBarDemoWindow *demo_window = (StatusBarDemoWindow *)window;
status_bar_layer_deinit(&demo_window->status_bar);
text_layer_deinit(&demo_window->text);
window_deinit(window);
}
static Window *prv_window_create(void) {
typedef struct Description {
char *debug_name;
bool full_screen;
uint8_t window_color;
bool status_bar;
uint8_t status_bar_color;
} Description;
static const Description descriptions[] = {
{
.debug_name = "non-full-screen (legacy status bar)",
.window_color = GColorRedARGB8,
},
{
.debug_name = "non-full-screen (legacy status bar)",
.window_color = GColorBlueARGB8,
},
{
.debug_name = "full-screen (transparent status bar)",
.full_screen = true,
.window_color = GColorRedARGB8,
.status_bar = true,
},
{
.debug_name = "full-screen (opaque status bar)",
.full_screen = true,
.window_color = GColorBlueARGB8,
.status_bar = true,
.status_bar_color = GColorOrangeARGB8,
},
{
.debug_name = "full-screen (no status bar)",
.full_screen = true,
.window_color = GColorGreenARGB8,
},
};
Description description = descriptions[app_window_stack_count() % ARRAY_LENGTH(descriptions)];
const GColor8 window_color = (GColor8){.argb = description.window_color};
const GColor8 status_bar_color = (GColor8){.argb = description.status_bar_color};
StatusBarDemoWindow *window = task_zalloc_check(sizeof(*window));
Window *result = &window->window;
window_init(result, description.debug_name);
window_set_fullscreen(result, description.full_screen);
window_set_background_color(result, window_color);
text_layer_init(&window->text, &GRect(0, 40, 144, 40));
text_layer_set_text(&window->text, description.debug_name);
layer_add_child(&window->window.layer, &window->text.layer);
StatusBarLayer *status_bar = &window->status_bar;
status_bar_layer_init(status_bar);
if (description.status_bar) {
status_bar_layer_set_colors(status_bar, status_bar_color,
gcolor_legible_over(status_bar_color));
layer_add_child(&window->window.layer, &status_bar->layer);
}
window_set_click_config_provider(result, prv_click_config_proivider);
window_set_window_handlers(&window->window, &(WindowHandlers){
.unload = prv_window_unload,
});
return result;
}
static void prv_handle_init(void) {
Window *window = prv_window_create();
app_window_stack_push(window, true);
}
static void prv_main(void) {
prv_handle_init();
app_event_loop();
}
const PebbleProcessMd* statusbar_demo_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common = {
.main_func = prv_main,
// UUID: dfcafc64-0af1-4e4a-8e03-1901b54335c5
.uuid = {0xdf, 0xca, 0xfc, 0x64, 0xa, 0xf1, 0x4e, 0x4a,
0x8e, 0x3, 0x19, 0x1, 0xb5, 0x43, 0x35, 0xc5},
},
.name = "StatusBar Demo",
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* statusbar_demo_get_app_info(void);

View file

@ -0,0 +1,449 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stroke_width.h"
#include <inttypes.h>
#include <stdbool.h>
#include "applib/app.h"
#include "applib/pbl_std/pbl_std.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "util/trig.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window.h"
#include "kernel/pbl_malloc.h"
#include "process_state/app_state/app_state.h"
#include "system/logging.h"
#define STEP_ROTATION_ANGLE (TRIG_MAX_ANGLE / 360) // 1 degree
#define MIN_OPS 0
#define OP_ROTATE 0
#define OP_CHANGE_WIDTH 1
#define OP_MOVE_P1_X 2
#define OP_MOVE_P1_Y 3
#define OP_MOVE_P2_X 4
#define OP_MOVE_P2_Y 5
#define OP_TEST 6
#define OP_TEST2 7
#define OP_TEST3 8
#define OP_TEST4 9
#define OP_TEST5 10
#define OP_ROTATE2 11
#define OP_ROTATE3 12
#define MAX_OPS 13
#define MIN_STROKE 1
#define MAX_STROKE 100
static Window *s_window;
extern void prv_draw_stroked_line_precise(GContext* ctx, GPointPrecise p0, GPointPrecise p1,
uint8_t width, bool anti_aliased);
typedef struct AppData {
Layer *canvas_layer;
Layer *debug_layer;
int stroke_width;
int size;
uint32_t rotation_angle;
GPoint p1;
GPoint p2;
int8_t operation;
} AppData;
static AppTimer *timer;
static void up_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
switch (data->operation) {
case OP_ROTATE:
data->rotation_angle = (data->rotation_angle + STEP_ROTATION_ANGLE) % TRIG_MAX_ANGLE;
break;
case OP_CHANGE_WIDTH:
case OP_TEST:
case OP_TEST2:
case OP_TEST3:
case OP_TEST4:
case OP_ROTATE2:
case OP_ROTATE3:
if (data->stroke_width < MAX_STROKE) data->stroke_width++;
break;
case OP_TEST5:
if (data->size < 100) data->size++;
break;
case OP_MOVE_P1_X:
data->p1.x++;
break;
case OP_MOVE_P1_Y:
data->p1.y++;
break;
case OP_MOVE_P2_X:
data->p2.x++;
break;
case OP_MOVE_P2_Y:
data->p2.y++;
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid operation type: %d", data->operation);
break;
}
layer_mark_dirty(data->canvas_layer);
PBL_LOG(LOG_LEVEL_DEBUG, "line(p1(%d, %d), p2(%d, %d), width=%d), angle=%d)",
data->p1.x, data->p1.y, data->p2.x, data->p2.y, data->stroke_width,
(int)(data->rotation_angle * 360 / TRIG_MAX_ANGLE));
}
static void down_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
switch (data->operation) {
case OP_ROTATE:
data->rotation_angle = (data->rotation_angle - STEP_ROTATION_ANGLE) % TRIG_MAX_ANGLE;
break;
case OP_CHANGE_WIDTH:
case OP_TEST:
case OP_TEST2:
case OP_TEST3:
case OP_TEST4:
case OP_ROTATE2:
case OP_ROTATE3:
if (data->stroke_width > MIN_STROKE) data->stroke_width--;
break;
case OP_TEST5:
if (data->size > 1) data->size--;
break;
case OP_MOVE_P1_X:
data->p1.x--;
break;
case OP_MOVE_P1_Y:
data->p1.y--;
break;
case OP_MOVE_P2_X:
data->p2.x--;
break;
case OP_MOVE_P2_Y:
data->p2.y--;
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Invalid operation type: %d", data->operation);
break;
}
layer_mark_dirty(data->canvas_layer);
PBL_LOG(LOG_LEVEL_DEBUG, "line(p1(%d, %d), p2(%d, %d), width=%d), angle=%d)",
data->p1.x, data->p1.y, data->p2.x, data->p2.y, data->stroke_width,
(int)(data->rotation_angle * 360 / TRIG_MAX_ANGLE));
}
static void select_handler(ClickRecognizerRef recognizer, void *context) {
AppData *data = window_get_user_data(s_window);
data->operation = (data->operation + 1) % MAX_OPS;
if (data->operation < MIN_OPS) data->operation = MIN_OPS;
layer_mark_dirty(data->canvas_layer);
PBL_LOG(LOG_LEVEL_DEBUG, "current operation type: %d", data->operation);
}
static void click_config_provider(void *context) {
window_single_repeating_click_subscribe(BUTTON_ID_UP, 100, up_handler);
window_single_repeating_click_subscribe(BUTTON_ID_SELECT, 100, select_handler);
window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 100, down_handler);
}
static void debug_layer_update_proc(Layer *layer, GContext* ctx) {
// For frame/bounds/clipping rect debugging
AppData *data = window_get_user_data(s_window);
const GRect *bounds = &data->canvas_layer->bounds;
const GRect *frame = &data->canvas_layer->frame;
graphics_context_set_stroke_color(ctx, GColorGreen);
graphics_draw_rect(ctx, &GRect(bounds->origin.x + frame->origin.x,
bounds->origin.y + frame->origin.y,
bounds->size.w, bounds->size.h));
graphics_context_set_stroke_color(ctx, GColorRed);
graphics_draw_rect(ctx, frame);
}
static void canvas_update_proc(Layer *layer, GContext* ctx) {
AppData *data = window_get_user_data(s_window);
GColor main_color = (GColor)GColorRoseVale;
graphics_context_set_stroke_color(ctx, main_color);
graphics_context_set_fill_color(ctx, main_color);
if (data->operation == OP_TEST) {
int x1 = 50,
x2 = 100,
y1 = 40,
y2 = 120;
GPoint p0 = GPoint(x1, y1);
GPoint p1 = GPoint(x1, y2);
GPoint p2 = GPoint(x2, y2);
GPoint p3 = GPoint(x2, y1);
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, p0, p1);
graphics_draw_line(ctx, p1, p2);
graphics_draw_line(ctx, p2, p3);
graphics_draw_line(ctx, p3, p0);
return;
}
if (data->operation == OP_TEST2) {
int x1 = 50,
x2 = 100,
y1 = 40,
y2 = 120;
GPoint p0 = GPoint(x1, y1);
GPoint p1 = GPoint(x1, y2);
GPoint p2 = GPoint(x2, y2);
GPoint p3 = GPoint(x2, y1);
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, p0, p1);
graphics_draw_line(ctx, p2, p3);
return;
}
if (data->operation == OP_TEST3) {
int x1 = 50,
x2 = 100,
y1 = 40,
y2 = 120;
GPoint p0 = GPoint(x1, y1);
GPoint p1 = GPoint(x1, y2);
GPoint p2 = GPoint(x2, y2);
GPoint p3 = GPoint(x2, y1);
// graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, p1, p2);
graphics_draw_line(ctx, p3, p0);
/*
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, GPoint(70, 100), GPoint(70, 101));
*/
return;
}
if (data->operation == OP_TEST4 || data->operation == OP_TEST5) {
/*
GRect bounds = layer->bounds;
GPoint center = GPoint(bounds.size.w/2, bounds.size.h/2);
int radius = 40;
graphics_context_set_stroke_color(ctx, GColorWhite);
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_circle(ctx, center, data->size);
*/
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, GPoint(70, 100), GPoint(70, 101));
return;
}
if (data->operation == OP_ROTATE2) {
int line_length = 60;
time_t now;
uint16_t now_ms = time_ms(&now, NULL);
uint32_t seconds = pbl_override_localtime(&now)->tm_sec;
uint32_t miliseconds = seconds * 1000 + now_ms;
uint32_t rotation = miliseconds * TRIG_MAX_ANGLE / (60 * 1000);
GPointPrecise p0;
GPointPrecise p1;
p0.x.raw_value = (data->canvas_layer->bounds.size.w / 2) * FIXED_S16_3_ONE.raw_value;
p0.y.raw_value = (data->canvas_layer->bounds.size.h / 2) * FIXED_S16_3_ONE.raw_value;
p1.x.raw_value = (sin_lookup(rotation) * (line_length * FIXED_S16_3_ONE.raw_value)
/ TRIG_MAX_RATIO) + p0.x.raw_value;
p1.y.raw_value = (-cos_lookup(rotation) * (line_length * FIXED_S16_3_ONE.raw_value)
/ TRIG_MAX_RATIO) + p0.y.raw_value;
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
if (data->stroke_width >= 2) {
graphics_line_draw_precise_stroked_aa(ctx, p0, p1, data->stroke_width);
} else {
graphics_draw_line(ctx, GPoint(p0.x.integer, p0.y.integer),
GPoint(p1.x.integer, p1.y.integer));
}
return;
}
if (data->operation == OP_ROTATE3) {
int line_length = 60;
time_t now;
uint16_t now_ms = time_ms(&now, NULL);
uint32_t seconds = pbl_override_localtime(&now)->tm_sec;
uint32_t miliseconds = seconds * 1000 + now_ms;
uint32_t rotation = miliseconds * TRIG_MAX_ANGLE / (60 * 1000);
GPointPrecise p0;
GPointPrecise p1;
p0.x.raw_value = (data->canvas_layer->bounds.size.w / 2) * FIXED_S16_3_ONE.raw_value;
p0.y.raw_value = (data->canvas_layer->bounds.size.h / 2) * FIXED_S16_3_ONE.raw_value;
p1.x.raw_value = (sin_lookup(rotation) * (line_length * FIXED_S16_3_ONE.raw_value)
/ TRIG_MAX_RATIO) + p0.x.raw_value;
p1.y.raw_value = (-cos_lookup(rotation) * (line_length * FIXED_S16_3_ONE.raw_value)
/ TRIG_MAX_RATIO) + p0.y.raw_value;
graphics_context_set_antialiased(ctx, false);
graphics_context_set_stroke_width(ctx, data->stroke_width);
if (data->stroke_width >= 2) {
graphics_line_draw_precise_stroked_non_aa(ctx, p0, p1, data->stroke_width);
} else {
graphics_draw_line(ctx, GPoint(p0.x.integer, p0.y.integer),
GPoint(p1.x.integer, p1.y.integer));
}
return;
}
int x1, y1, x2, y2;
if (data->operation == OP_ROTATE) {
int line_length = 60;
x1 = data->canvas_layer->bounds.size.w / 2;
y1 = data->canvas_layer->bounds.size.h / 2;
x2 = (sin_lookup(data->rotation_angle) * line_length / TRIG_MAX_RATIO) + x1;
y2 = (-cos_lookup(data->rotation_angle) * line_length / TRIG_MAX_RATIO) + y1;
} else {
x1 = data->p1.x;
y1 = data->p1.y;
x2 = data->p2.x;
y2 = data->p2.y;
}
graphics_context_set_antialiased(ctx, true);
graphics_context_set_stroke_width(ctx, data->stroke_width);
graphics_draw_line(ctx, GPoint(x1, y1), GPoint(x2, y2));
}
static void main_window_load(Window *window) {
AppData *data = window_get_user_data(s_window);
Layer *window_layer = window_get_root_layer(window);
window_set_background_color(window, GColorBlack);
GRect window_bounds = window_layer->bounds;
data->debug_layer = layer_create(window_bounds);
layer_set_update_proc(data->debug_layer, debug_layer_update_proc);
layer_add_child(window_layer, data->debug_layer);
data->canvas_layer = layer_create(GRect(10, 10, window_bounds.size.w - 20,
window_bounds.size.h - 20));
layer_set_update_proc(data->canvas_layer, canvas_update_proc);
layer_add_child(window_layer, data->canvas_layer);
// For drawing_rect testing...
// layer_set_bounds(data->canvas_layer, &GRect(0, 0,
// window_bounds.size.w - 20, window_bounds.size.h - 20));
data->stroke_width = 10;
}
void timer_callback(void *d) {
AppData *data = window_get_user_data(s_window);
layer_mark_dirty(data->canvas_layer);
timer = app_timer_register(30, timer_callback, NULL);
}
static void main_window_unload(Window *window) {
AppData *data = window_get_user_data(s_window);
layer_destroy(data->canvas_layer);
layer_destroy(data->debug_layer);
}
static void init(void) {
AppData *data = task_zalloc(sizeof(AppData));
if (!data) {
return;
}
s_window = window_create();
window_set_user_data(s_window, data);
window_set_fullscreen(s_window, true);
window_set_window_handlers(s_window, &(WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload,
});
data->p1.x = 10;
data->p1.y = 30;
data->p2.x = 100;
data->p2.y = 120;
data->size = 40;
data->operation = MIN_OPS;
window_set_click_config_provider(s_window, click_config_provider);
const bool animated = true;
app_window_stack_push(s_window, animated);
timer = app_timer_register(30, timer_callback, NULL);
}
static void deinit(void) {
AppData *data = window_get_user_data(s_window);
task_free(data);
window_destroy(s_window);
}
static void s_main(void) {
init();
app_event_loop();
deinit();
}
const PebbleProcessMd* stroke_width_get_app_info(void) {
static const PebbleProcessMdSystem s_app_info = {
.common.main_func = s_main,
.name = "Stroke Width"
};
return (const PebbleProcessMd*) &s_app_info;
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "process_management/pebble_process_md.h"
const PebbleProcessMd* stroke_width_get_app_info();

Some files were not shown because too many files have changed in this diff Show more