mirror of
https://github.com/google/pebble.git
synced 2025-06-08 03:03:11 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
135
src/fw/apps/core_apps/panic_window_app.c
Normal file
135
src/fw/apps/core_apps/panic_window_app.c
Normal 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;
|
||||
}
|
||||
|
22
src/fw/apps/core_apps/panic_window_app.h
Normal file
22
src/fw/apps/core_apps/panic_window_app.h
Normal 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();
|
||||
|
274
src/fw/apps/core_apps/progress_ui_app.c
Normal file
274
src/fw/apps/core_apps/progress_ui_app.c
Normal 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;
|
||||
}
|
31
src/fw/apps/core_apps/progress_ui_app.h
Normal file
31
src/fw/apps/core_apps/progress_ui_app.h
Normal 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();
|
194
src/fw/apps/core_apps/spinner_ui_window.c
Normal file
194
src/fw/apps/core_apps/spinner_ui_window.c
Normal 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;
|
||||
}
|
27
src/fw/apps/core_apps/spinner_ui_window.h
Normal file
27
src/fw/apps/core_apps/spinner_ui_window.h
Normal 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);
|
Loading…
Add table
Add a link
Reference in a new issue