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,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 "progress_window.h"
#include "applib/applib_malloc.auto.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/app_window_stack.h"
#include "applib/ui/window_private.h"
#include "applib/ui/window_stack.h"
#include "kernel/pbl_malloc.h"
#include "services/common/compositor/compositor_transitions.h"
#include "services/normal/timeline/timeline_resources.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#define SCROLL_OUT_MS 250
#define BAR_HEIGHT PROGRESS_SUGGESTED_HEIGHT
#define BAR_WIDTH 80
#define BAR_TO_TRANS_MS 160
#define TRANS_TO_DOT_MS 90
#define DOT_TRANSITION_RADIUS 13
#define DOT_COMPOSITOR_RADIUS 7
#define DOT_OFFSET 25
#define FAKE_PROGRESS_UPDATE_INTERVAL 200
#define FAKE_PROGRESS_UPDATE_AMOUNT 2
#define INITIAL_PERCENT 0
static void prv_finished(ProgressWindow *data, bool success) {
if (data->callbacks.finished) {
data->callbacks.finished(data, success, data->context);
}
}
///////////////////////////////
// Animation Related Functions
///////////////////////////////
static void prv_animation_stopped_success(Animation *animation, bool finished, void *context) {
ProgressWindow *data = context;
const bool success = true;
prv_finished(data, success);
}
static void prv_finished_failure_callback(void *data) {
const bool success = false;
prv_finished(data, success);
}
static void prv_show_peek_layer(ProgressWindow *data) {
if (data->is_peek_layer_used) {
Layer *root_layer = window_get_root_layer(&data->window);
PeekLayer *peek_layer = &data->peek_layer;
peek_layer_play(peek_layer);
layer_add_child(root_layer, (Layer *)peek_layer);
const int standing_ms = 1 * MS_PER_SECOND;
data->peek_layer_timer = evented_timer_register(PEEK_LAYER_UNFOLD_DURATION + standing_ms,
false, prv_finished_failure_callback, data);
} else {
prv_finished_failure_callback(data);
}
}
static void prv_animation_stopped_failure(Animation *animation, bool finished, void *context) {
ProgressWindow *data = context;
prv_show_peek_layer(data);
}
static void prv_schedule_progress_success_animation(ProgressWindow *data) {
#if !PLATFORM_TINTIN
GRect beg = data->progress_layer.layer.bounds;
GRect mid = beg;
GRect end = beg;
// Morph from progress_layer to a large transition dot to the compositor dot
// by changing the bounds of the progress_layer using 2 animations
layer_set_clips((Layer *)&data->progress_layer, false); // Extending bounds to grow the dot
progress_layer_set_corner_radius(&data->progress_layer, DOT_TRANSITION_RADIUS);
mid.size.w = DOT_TRANSITION_RADIUS * 2;
mid.size.h = DOT_TRANSITION_RADIUS * 2;
mid.origin.x = DOT_OFFSET - DOT_TRANSITION_RADIUS + 2;
mid.origin.y = BAR_HEIGHT - DOT_TRANSITION_RADIUS + 1; // shift to accommodate growing radius
end.size.w = DOT_COMPOSITOR_RADIUS * 2;
end.size.h = DOT_COMPOSITOR_RADIUS * 2;
end.origin.x = DOT_OFFSET - DOT_COMPOSITOR_RADIUS - 1;
end.origin.y = BAR_HEIGHT - DOT_COMPOSITOR_RADIUS - 2; // shift to accommodate growing radius
PropertyAnimation *prop_anim =
property_animation_create_layer_bounds((Layer *)&data->progress_layer, &beg, &mid);
Animation *animation1 = property_animation_get_animation(prop_anim);
animation_set_duration(animation1, BAR_TO_TRANS_MS);
animation_set_curve(animation1, AnimationCurveEaseIn);
prop_anim = property_animation_create_layer_bounds((Layer *)&data->progress_layer, &mid, &end);
Animation *animation2 = property_animation_get_animation(prop_anim);
animation_set_duration(animation2, TRANS_TO_DOT_MS);
animation_set_curve(animation2, AnimationCurveLinear);
animation_set_handlers(animation2, (AnimationHandlers) {
.stopped = prv_animation_stopped_success,
}, data);
Animation *animation = animation_sequence_create(animation1, animation2, NULL);
data->result_animation = animation;
animation_schedule(animation);
#else
// Don't animate to a dot on old platforms, just finish immediately.
static const bool success = true;
prv_finished(data, success);
#endif
}
static void prv_schedule_progress_failure_animation(ProgressWindow *data, uint32_t timeline_res_id,
const char *message, uint32_t delay) {
// Initialize the peek layer
if (timeline_res_id || message) {
Layer *root_layer = window_get_root_layer(&data->window);
PeekLayer *peek_layer = &data->peek_layer;
peek_layer_init(peek_layer, &root_layer->frame);
peek_layer_set_title_font(peek_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
TimelineResourceInfo timeline_res = {
.res_id = timeline_res_id,
};
peek_layer_set_icon(peek_layer, &timeline_res);
peek_layer_set_title(peek_layer, message);
peek_layer_set_background_color(peek_layer, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite));
data->is_peek_layer_used = true;
}
#if !PLATFORM_TINTIN
// Animate the progress bar out, by shrinking it's width from it's current size down to 0.
// When this completes, prv_animation_stopped_failure will show the peek layer.
GRect *start = &data->progress_layer.layer.frame;
GRect stop = *start;
stop.size.w = 0;
PropertyAnimation *prop_anim =
property_animation_create_layer_frame((Layer *)&data->progress_layer, start, &stop);
Animation *animation = property_animation_get_animation(prop_anim);
// If we failed, pause on the screen for a little.
animation_set_delay(animation, delay);
animation_set_duration(animation, SCROLL_OUT_MS);
animation_set_curve(animation, AnimationCurveEaseOut);
animation_set_handlers(animation, (AnimationHandlers) {
.stopped = prv_animation_stopped_failure,
}, data);
data->result_animation = animation;
animation_schedule(animation);
#else
// Don't animate, just show the peek layer immediately.
prv_show_peek_layer(data);
#endif
}
////////////////////////////
// Internal Helper Functions
////////////////////////////
//! Used to clean up the application's data before exiting
static void prv_cancel_fake_progress_timer(ProgressWindow *data) {
if (data->fake_progress_timer != EVENTED_TIMER_INVALID_ID) {
evented_timer_cancel(data->fake_progress_timer);
data->fake_progress_timer = EVENTED_TIMER_INVALID_ID;
}
}
static void prv_set_progress(ProgressWindow *data, int16_t progress) {
data->progress_percent = CLIP(progress, data->progress_percent, MAX_PROGRESS_PERCENT);
progress_layer_set_progress(&data->progress_layer, data->progress_percent);
}
static void prv_fake_update_progress(void *context) {
ProgressWindow *data = context;
prv_set_progress(data, data->progress_percent + FAKE_PROGRESS_UPDATE_AMOUNT);
if (data->progress_percent >= data->max_fake_progress_percent) {
// Hit the max, we're done
data->fake_progress_timer = EVENTED_TIMER_INVALID_ID;
} else {
data->fake_progress_timer = evented_timer_register(FAKE_PROGRESS_UPDATE_INTERVAL, false,
prv_fake_update_progress, data);
}
}
////////////////////////////
// Public API
////////////////////////////
void progress_window_set_max_fake_progress(ProgressWindow *window,
int16_t max_fake_progress_percent) {
window->max_fake_progress_percent = CLIP(max_fake_progress_percent, 0, MAX_PROGRESS_PERCENT);
}
void progress_window_set_progress(ProgressWindow *window, int16_t progress) {
if (window->state == ProgressWindowState_FakeProgress) {
// We've seen our first bit of real progress, stop faking it.
prv_cancel_fake_progress_timer(window);
window->state = ProgressWindowState_RealProgress;
}
prv_set_progress(window, progress);
}
void progress_window_set_result_success(ProgressWindow *window) {
if (window->state == ProgressWindowState_Result) {
// Ignore requests to change the result once we already have one
return;
}
window->state = ProgressWindowState_Result;
prv_cancel_fake_progress_timer(window);
prv_set_progress(window, MAX_PROGRESS_PERCENT);
prv_schedule_progress_success_animation(window);
}
void progress_window_set_result_failure(ProgressWindow *window, uint32_t timeline_res,
const char *message, uint32_t delay) {
if (window->state == ProgressWindowState_Result) {
// Ignore requests to change the result once we already have one
return;
}
window->state = ProgressWindowState_Result;
prv_cancel_fake_progress_timer(window);
prv_schedule_progress_failure_animation(window, timeline_res, message, delay);
}
void progress_window_set_callbacks(ProgressWindow *window, ProgressWindowCallbacks callbacks,
void *context) {
window->context = context;
window->callbacks = callbacks;
}
void progress_window_set_back_disabled(ProgressWindow *window, bool disabled) {
window_set_overrides_back_button(&window->window, disabled);
}
void progress_window_push(ProgressWindow *window, WindowStack *window_stack) {
const bool animated = true;
window_stack_push(window_stack, (Window *)window, animated);
}
void app_progress_window_push(ProgressWindow *window) {
const bool animated = true;
app_window_stack_push((Window *)window, animated);
}
void progress_window_pop(ProgressWindow *window) {
const bool animated = true;
window_stack_remove((Window *)window, animated);
}
void progress_window_init(ProgressWindow *data) {
// Create and set up the window
Window *window = &data->window;
window_init(window, WINDOW_NAME("Progress Window"));
window_set_background_color(window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite));
const GRect *bounds = &window->layer.bounds;
GPoint center = grect_center_point(bounds);
const GRect progress_bounds = (GRect) {
.origin = { center.x - (BAR_WIDTH / 2), center.y - (BAR_HEIGHT / 2) },
.size = { BAR_WIDTH, BAR_HEIGHT },
};
ProgressLayer *progress_layer = &data->progress_layer;
progress_layer_init(progress_layer, &progress_bounds);
#if PBL_COLOR
progress_layer_set_foreground_color(progress_layer, GColorWhite);
progress_layer_set_background_color(progress_layer, GColorBlack);
#endif
progress_layer_set_corner_radius(progress_layer, PROGRESS_SUGGESTED_CORNER_RADIUS);
layer_add_child(&window->layer, (Layer *)progress_layer);
data->max_fake_progress_percent = PROGRESS_WINDOW_DEFAULT_FAKE_PERCENT;
data->state = ProgressWindowState_FakeProgress;
data->is_peek_layer_used = false;
data->fake_progress_timer = evented_timer_register(FAKE_PROGRESS_UPDATE_INTERVAL, false,
prv_fake_update_progress, data);
prv_set_progress(data, INITIAL_PERCENT);
}
void progress_window_deinit(ProgressWindow *data) {
if (!data) {
return;
}
animation_unschedule(data->result_animation);
peek_layer_deinit(&data->peek_layer);
data->is_peek_layer_used = false;
prv_cancel_fake_progress_timer(data);
if (data->peek_layer_timer != EVENTED_TIMER_INVALID_ID) {
evented_timer_cancel(data->peek_layer_timer);
data->peek_layer_timer = EVENTED_TIMER_INVALID_ID;
}
}
ProgressWindow *progress_window_create(void) {
ProgressWindow *window = applib_zalloc(sizeof(ProgressWindow));
progress_window_init(window);
return window;
}
void progress_window_destroy(ProgressWindow *window) {
if (window) {
progress_window_pop(window);
}
progress_window_deinit(window);
applib_free(window);
}