pebble/src/fw/applib/ui/crumbs_layer.c
2025-01-27 11:38:16 -08:00

200 lines
6.2 KiB
C

/*
* 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 "crumbs_layer.h"
#include "applib/applib_malloc.auto.h"
#include "shell/system_theme.h"
#include "applib/ui/property_animation.h"
#include "process_management/process_manager.h"
#include "system/logging.h"
#include "util/trig.h"
typedef struct CrumbsLayerSizeConfig {
int layer_width;
int crumb_radius;
int crumb_spacing;
int crumb_space_from_top;
} CrumbsLayerSizeConfig;
static const CrumbsLayerSizeConfig s_crumb_configs[NumPreferredContentSizes] = {
//! @note this is the same as Medium until Small is designed
[PreferredContentSizeSmall] = {
.layer_width = 14,
.crumb_radius = 2,
.crumb_spacing = 8,
.crumb_space_from_top = 8,
},
[PreferredContentSizeMedium] = {
.layer_width = 14,
.crumb_radius = 2,
.crumb_spacing = 8,
.crumb_space_from_top = 8,
},
[PreferredContentSizeLarge] = {
.layer_width = 16,
.crumb_radius = 2,
.crumb_spacing = 10,
.crumb_space_from_top = 10,
},
//! @note this is the same as Large until ExtraLarge is designed
[PreferredContentSizeExtraLarge] = {
.layer_width = 16,
.crumb_radius = 2,
.crumb_spacing = 10,
.crumb_space_from_top = 10,
},
};
static const CrumbsLayerSizeConfig *prv_crumb_config(void) {
const PreferredContentSize runtime_platform_default_size =
system_theme_get_default_content_size_for_runtime_platform();
return &s_crumb_configs[runtime_platform_default_size];
}
int crumbs_layer_width(void) {
return prv_crumb_config()->layer_width;
}
static int prv_crumb_x_position(void) {
return prv_crumb_config()->layer_width / 2;
}
static int prv_crumb_radius(void) {
return prv_crumb_config()->crumb_radius;
}
static int prv_crumb_spacing(void) {
return prv_crumb_config()->crumb_spacing;
}
static int prv_crumb_space_from_top(void) {
return prv_crumb_config()->crumb_space_from_top;
}
static int prv_crumb_maximum_count(void) {
// NOTE: Was originally:
// static const int MAX_CRUMBS = 16; // 168 / (4px diameter + 4px in between each)
// However that math literally doesn't add up, it would've been 20 like that.
// I'm going to just return 16 all the time like we used to, but leave a "correct" version
// commented out.
// return (PBL_DISPLAY_HEIGHT - prv_crumb_space_from_top()) / prv_crumb_spacing();
return 16;
}
static void prv_crumbs_layer_update_proc_rect(Layer *layer, GContext *ctx) {
const int crumb_radius = prv_crumb_radius();
const int crumb_spacing = prv_crumb_spacing();
const int xpos = prv_crumb_x_position();
GPoint p = GPoint(xpos, crumb_radius + prv_crumb_space_from_top());
CrumbsLayer *cl = (CrumbsLayer *)layer;
graphics_context_set_fill_color(ctx, cl->bg_color);
graphics_fill_rect(ctx, &layer->bounds);
for (int i = cl->level; i > 0; --i) {
p.x = xpos + (cl->crumbs_x_increment / i);
graphics_context_set_fill_color(ctx, cl->fg_color);
graphics_fill_circle(ctx, p, crumb_radius);
p.y += crumb_spacing;
}
}
static void prv_crumbs_layer_update_proc_round(Layer *layer, GContext *ctx) {
CrumbsLayer *cl = (CrumbsLayer *)layer;
graphics_context_set_fill_color(ctx, cl->bg_color);
// TODO: remove stroke color again, once it's been fixed in fill_radial
graphics_context_set_stroke_color(ctx, cl->bg_color);
// compensate for problems with rounding errors and physical display shape
const uint16_t overdraw = 2;
graphics_fill_radial(ctx, grect_inset(layer->bounds, GEdgeInsets(-overdraw)),
GOvalScaleModeFillCircle, crumbs_layer_width(), 0, TRIG_MAX_ANGLE);
}
void crumbs_layer_set_level(CrumbsLayer *crumbs_layer, int level) {
const int max_crumbs = prv_crumb_maximum_count();
if (level > max_crumbs) {
PBL_LOG(LOG_LEVEL_WARNING, "exceeded max number of crumbs");
level = max_crumbs;
}
crumbs_layer->level = level;
layer_mark_dirty((Layer *)crumbs_layer);
}
void crumbs_layer_init(CrumbsLayer *crumbs_layer, const GRect *frame, GColor bg_color,
GColor fg_color) {
layer_init(&crumbs_layer->layer, frame);
crumbs_layer->level = 0;
crumbs_layer->fg_color = fg_color;
crumbs_layer->bg_color = bg_color;
const LayerUpdateProc update_proc = PBL_IF_RECT_ELSE(prv_crumbs_layer_update_proc_rect,
prv_crumbs_layer_update_proc_round);
layer_set_update_proc(&crumbs_layer->layer, update_proc);
}
CrumbsLayer *crumbs_layer_create(GRect frame, GColor bg_color, GColor fg_color) {
// Note: Not yet exported for 3rd party applications so no padding needed
CrumbsLayer *cl = applib_malloc(sizeof(CrumbsLayer));
if (cl) {
crumbs_layer_init(cl, &frame, fg_color, bg_color);
}
return cl;
}
void crumbs_layer_deinit(CrumbsLayer *crumbs_layer) {
if (crumbs_layer == NULL) {
return;
}
layer_deinit((Layer *)crumbs_layer);
}
void crumbs_layer_destroy(CrumbsLayer *crumbs_layer) {
crumbs_layer_deinit(crumbs_layer);
applib_free(crumbs_layer);
}
int16_t prv_x_getter(void *subject) {
CrumbsLayer *crumbs_layer = subject;
return crumbs_layer->crumbs_x_increment;
}
void prv_x_setter(void *subject, int16_t int16) {
CrumbsLayer *crumbs_layer = subject;
crumbs_layer->crumbs_x_increment = int16;
}
static const PropertyAnimationImplementation s_prop_impl = {
.base = {
.update = (AnimationUpdateImplementation)property_animation_update_int16,
},
.accessors = {
.getter.int16 = prv_x_getter,
.setter.int16 = prv_x_setter,
},
};
Animation *crumbs_layer_get_animation(CrumbsLayer *crumbs_layer) {
uint16_t from = crumbs_layer->level * 2 * prv_crumb_radius();
uint16_t to = 0;
PropertyAnimation *prop_anim = property_animation_create(&s_prop_impl, crumbs_layer, &from, &to);
return property_animation_get_animation(prop_anim);
}