mirror of
https://github.com/google/pebble.git
synced 2025-05-24 20:24:53 +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
381
src/fw/applib/ui/content_indicator.c
Normal file
381
src/fw/applib/ui/content_indicator.c
Normal file
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
* 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 "content_indicator.h"
|
||||
#include "content_indicator_private.h"
|
||||
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/graphics/gpath.h"
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/buffer.h"
|
||||
#include "util/size.h"
|
||||
|
||||
//! Signature for callbacks provided to prv_content_indicator_iterate()
|
||||
//! @param content_indicator The current ContentIndicator in the iteration.
|
||||
//! @param buffer_offset_bytes The offset of the ContentIndicator in the buffer's storage.
|
||||
//! @param input_context An input context.
|
||||
//! @param output_context An output context.
|
||||
//! @return `true` if iteration should continue, `false` otherwise.
|
||||
typedef bool (*ContentIndicatorIteratorCb)(ContentIndicator *content_indicator,
|
||||
size_t buffer_offset_bytes,
|
||||
void *input_context,
|
||||
void *output_context);
|
||||
|
||||
bool prv_content_indicator_init(ContentIndicator *content_indicator) {
|
||||
if (!content_indicator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*content_indicator = (ContentIndicator){};
|
||||
|
||||
// Add the content indicator to the appropriate buffer
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
size_t bytes_written = buffer_add(buffer,
|
||||
(uint8_t *)&content_indicator,
|
||||
sizeof(ContentIndicator *));
|
||||
// Return whether or not the content indicator was successfully written to the buffer
|
||||
return (bytes_written == sizeof(ContentIndicator *));
|
||||
}
|
||||
|
||||
void content_indicator_init(ContentIndicator *content_indicator) {
|
||||
bool success = prv_content_indicator_init(content_indicator);
|
||||
PBL_ASSERTN(success);
|
||||
}
|
||||
|
||||
//! Returns `true` if `iterator_cb` signaled iteration to end, `false` otherwise.
|
||||
static bool prv_content_indicator_iterate(ContentIndicatorIteratorCb iterator_cb,
|
||||
void *input_context,
|
||||
void *output_context) {
|
||||
if (!iterator_cb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
for (size_t offset = 0; offset < buffer->bytes_written; offset += sizeof(ContentIndicator *)) {
|
||||
// We have to break up the access into two parts, otherwise we get a strict-aliasing error
|
||||
ContentIndicator **content_indicator_address = (ContentIndicator **)(buffer->data + offset);
|
||||
ContentIndicator *content_indicator = *content_indicator_address;
|
||||
if (!iterator_cb(content_indicator, offset, input_context, output_context)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator_create(void) {
|
||||
ContentIndicator *content_indicator = applib_type_zalloc(ContentIndicator);
|
||||
if (!content_indicator) {
|
||||
return NULL;
|
||||
}
|
||||
if (!prv_content_indicator_init(content_indicator)) {
|
||||
applib_free(content_indicator);
|
||||
return NULL;
|
||||
}
|
||||
return content_indicator;
|
||||
}
|
||||
|
||||
static bool prv_content_indicator_find_for_scroll_layer_cb(ContentIndicator *content_indicator,
|
||||
size_t buffer_offset_bytes,
|
||||
void *input_context,
|
||||
void *output_context) {
|
||||
ScrollLayer *target_scroll_layer = input_context;
|
||||
if (content_indicator->scroll_layer == target_scroll_layer) {
|
||||
*((ContentIndicator **)output_context) = content_indicator;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator_get_for_scroll_layer(ScrollLayer *scroll_layer) {
|
||||
if (!scroll_layer) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator = NULL;
|
||||
prv_content_indicator_iterate(prv_content_indicator_find_for_scroll_layer_cb,
|
||||
scroll_layer,
|
||||
&content_indicator);
|
||||
return content_indicator;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator_get_or_create_for_scroll_layer(ScrollLayer *scroll_layer) {
|
||||
if (!scroll_layer) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator = content_indicator_get_for_scroll_layer(scroll_layer);
|
||||
if (!content_indicator) {
|
||||
content_indicator = content_indicator_create();
|
||||
if (content_indicator) {
|
||||
content_indicator->scroll_layer = scroll_layer;
|
||||
}
|
||||
}
|
||||
return content_indicator;
|
||||
}
|
||||
|
||||
static bool prv_content_indicator_find_buffer_offset_bytes_cb(ContentIndicator *content_indicator,
|
||||
size_t buffer_offset_bytes,
|
||||
void *input_context,
|
||||
void *output_context) {
|
||||
ContentIndicator *target_content_indicator = input_context;
|
||||
if (content_indicator == target_content_indicator) {
|
||||
*((size_t *)output_context) = buffer_offset_bytes;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_content_indicator_reset_direction(ContentIndicatorDirectionData *direction_data) {
|
||||
// Cancel the timeout timer, if necessary
|
||||
if (direction_data->timeout_timer) {
|
||||
app_timer_cancel(direction_data->timeout_timer);
|
||||
direction_data->timeout_timer = NULL;
|
||||
}
|
||||
|
||||
ContentIndicatorConfig *config = &direction_data->config;
|
||||
if (config->layer) {
|
||||
// Set the layer's update proc to be the layer's original update proc
|
||||
config->layer->update_proc = direction_data->original_update_proc;
|
||||
layer_mark_dirty(config->layer);
|
||||
}
|
||||
}
|
||||
|
||||
void content_indicator_deinit(ContentIndicator *content_indicator) {
|
||||
if (!content_indicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deinit the data for each of the directions
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(content_indicator->direction_data); i++) {
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[i];
|
||||
prv_content_indicator_reset_direction(direction_data);
|
||||
}
|
||||
|
||||
// Find the offset of the content indicator in the buffer
|
||||
size_t buffer_offset_bytes;
|
||||
if (!prv_content_indicator_iterate(prv_content_indicator_find_buffer_offset_bytes_cb,
|
||||
content_indicator,
|
||||
&buffer_offset_bytes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the content indicator from the appropriate buffer
|
||||
ContentIndicatorsBuffer *content_indicators_buffer = content_indicator_get_current_buffer();
|
||||
Buffer *buffer = &content_indicators_buffer->buffer;
|
||||
buffer_remove(buffer, buffer_offset_bytes, sizeof(ContentIndicator *));
|
||||
}
|
||||
|
||||
void content_indicator_destroy(ContentIndicator *content_indicator) {
|
||||
if (!content_indicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
content_indicator_deinit(content_indicator);
|
||||
applib_free(content_indicator);
|
||||
}
|
||||
|
||||
void content_indicator_destroy_for_scroll_layer(ScrollLayer *scroll_layer) {
|
||||
if (!scroll_layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentIndicator *content_indicator;
|
||||
if (prv_content_indicator_iterate(prv_content_indicator_find_for_scroll_layer_cb,
|
||||
scroll_layer,
|
||||
&content_indicator)) {
|
||||
content_indicator_destroy(content_indicator);
|
||||
}
|
||||
}
|
||||
|
||||
bool content_indicator_configure_direction(ContentIndicator *content_indicator,
|
||||
ContentIndicatorDirection direction,
|
||||
const ContentIndicatorConfig *config) {
|
||||
if (!content_indicator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If NULL is passed for config, reset the data for this direction.
|
||||
if (!config) {
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[direction];
|
||||
prv_content_indicator_reset_direction(direction_data);
|
||||
*direction_data = (ContentIndicatorDirectionData){};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!config->layer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if any of the other directions have already been configured with this config's layer
|
||||
for (ContentIndicatorDirection dir = 0; dir < NumContentIndicatorDirections; dir++) {
|
||||
if (dir == direction) {
|
||||
continue;
|
||||
}
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[dir];
|
||||
if (direction_data->config.layer == config->layer) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[direction];
|
||||
prv_content_indicator_reset_direction(direction_data);
|
||||
*direction_data = (ContentIndicatorDirectionData){
|
||||
.direction = direction,
|
||||
.config = *config,
|
||||
.original_update_proc = config->layer->update_proc,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_content_indicator_find_direction_data_cb(ContentIndicator *content_indicator,
|
||||
size_t buffer_offset_bytes,
|
||||
void *input_context,
|
||||
void *output_context) {
|
||||
Layer *target_layer = input_context;
|
||||
for (ContentIndicatorDirection dir = 0; dir < NumContentIndicatorDirections; dir++) {
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[dir];
|
||||
if (direction_data->config.layer == target_layer) {
|
||||
*((ContentIndicatorDirectionData **)output_context) = direction_data;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void content_indicator_draw_arrow(GContext *ctx,
|
||||
const GRect *frame,
|
||||
ContentIndicatorDirection direction,
|
||||
GColor fg_color,
|
||||
GColor bg_color,
|
||||
GAlign alignment) {
|
||||
// Fill the background color
|
||||
graphics_context_set_fill_color(ctx, bg_color);
|
||||
graphics_fill_rect(ctx, frame);
|
||||
|
||||
// Pick the arrow to draw
|
||||
const int16_t arrow_height = 6;
|
||||
const GPathInfo arrow_up_path_info = {
|
||||
.num_points = 3,
|
||||
.points = (GPoint[]) {{0, arrow_height}, {(arrow_height + 1), 0},
|
||||
{((arrow_height * 2) + 1), arrow_height}}
|
||||
};
|
||||
const GPathInfo arrow_down_path_info = {
|
||||
.num_points = 3,
|
||||
.points = (GPoint[]) {{0, 0}, {(arrow_height + 1), arrow_height},
|
||||
{((arrow_height * 2) + 1), 0}}
|
||||
};
|
||||
const GPathInfo *arrow_path_info;
|
||||
switch (direction) {
|
||||
case ContentIndicatorDirectionUp:
|
||||
arrow_path_info = &arrow_up_path_info;
|
||||
break;
|
||||
case ContentIndicatorDirectionDown:
|
||||
arrow_path_info = &arrow_down_path_info;
|
||||
break;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
|
||||
// Draw the arrow
|
||||
GPath arrow_path;
|
||||
gpath_init(&arrow_path, arrow_path_info);
|
||||
// Align the arrow within the provided bounds
|
||||
GRect arrow_box = gpath_outer_rect(&arrow_path);
|
||||
grect_align(&arrow_box, frame, alignment, true /* clip */);
|
||||
gpath_move_to(&arrow_path, arrow_box.origin);
|
||||
|
||||
const bool prev_antialiased = graphics_context_get_antialiased(ctx);
|
||||
graphics_context_set_antialiased(ctx, false);
|
||||
graphics_context_set_fill_color(ctx, fg_color);
|
||||
gpath_draw_filled(ctx, &arrow_path);
|
||||
graphics_context_set_antialiased(ctx, prev_antialiased);
|
||||
}
|
||||
|
||||
T_STATIC void prv_content_indicator_update_proc(Layer *layer, GContext *ctx) {
|
||||
// Find the direction data corresponding to the layer that should be updated
|
||||
ContentIndicatorDirectionData *direction_data;
|
||||
if (!prv_content_indicator_iterate(prv_content_indicator_find_direction_data_cb,
|
||||
layer,
|
||||
&direction_data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentIndicatorConfig *config = &direction_data->config;
|
||||
content_indicator_draw_arrow(ctx,
|
||||
&layer->bounds,
|
||||
direction_data->direction,
|
||||
config->colors.foreground,
|
||||
config->colors.background,
|
||||
config->alignment);
|
||||
}
|
||||
|
||||
bool content_indicator_get_content_available(ContentIndicator *content_indicator,
|
||||
ContentIndicatorDirection direction) {
|
||||
if (!content_indicator) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[direction];
|
||||
return direction_data->content_available;
|
||||
}
|
||||
|
||||
|
||||
void content_indicator_set_content_available(ContentIndicator *content_indicator,
|
||||
ContentIndicatorDirection direction,
|
||||
bool available) {
|
||||
if (!content_indicator) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentIndicatorDirectionData *direction_data = &content_indicator->direction_data[direction];
|
||||
direction_data->content_available = available;
|
||||
|
||||
ContentIndicatorConfig *config = &direction_data->config;
|
||||
|
||||
if (!config->layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleans potentially scheduled timer, resets update_proc, marks dirty
|
||||
prv_content_indicator_reset_direction(direction_data);
|
||||
|
||||
if (available) {
|
||||
// Set the layer's update proc to be the arrow-drawing update proc and mark it as dirty
|
||||
config->layer->update_proc = prv_content_indicator_update_proc;
|
||||
layer_mark_dirty(config->layer);
|
||||
// If the arrow should time out and a timer isn't already scheduled, register a timeout timer
|
||||
if (config->times_out && !direction_data->timeout_timer) {
|
||||
direction_data->timeout_timer = app_timer_register(
|
||||
CONTENT_INDICATOR_TIMEOUT_MS,
|
||||
(AppTimerCallback)prv_content_indicator_reset_direction,
|
||||
direction_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void content_indicator_init_buffer(ContentIndicatorsBuffer *content_indicators_buffer) {
|
||||
if (!content_indicators_buffer) {
|
||||
return;
|
||||
}
|
||||
buffer_init(&content_indicators_buffer->buffer, CONTENT_INDICATOR_BUFFER_SIZE_BYTES);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue