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