Files
pebble/src/fw/applib/ui/kino/kino_reel/transform.c
T

424 lines
14 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 "transform.h"
#include "applib/graphics/gdraw_command_private.h"
#include "applib/graphics/gdraw_command_transforms.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_interpolate.h"
#include "applib/ui/animation_timing.h"
#include "applib/ui/kino/kino_reel.h"
#include "applib/ui/kino/kino_reel_pdci.h"
#include "applib/ui/kino/kino_reel_custom.h"
#include "applib/applib_malloc.auto.h"
#include "syscall/syscall.h"
#include "util/math.h"
#include "util/net.h"
#include "util/struct.h"
typedef struct {
GRect layer_frame;
GRect from;
GRect to;
const TransformImpl *impl;
void *context;
int32_t normalized;
uint32_t elapsed;
uint32_t duration;
KinoReel *from_reel;
KinoReel *to_reel;
GDrawCommandList *list_copy;
GSize list_copy_size;
bool owns_from_reel;
bool owns_to_reel;
bool global;
} KinoReelTransformData;
static bool prv_is_currently_from(KinoReelTransformData *data) {
return (!data->to_reel || (data->normalized < ANIMATION_NORMALIZED_MAX / 2));
}
static KinoReel *prv_get_current_reel(KinoReelTransformData *data) {
return prv_is_currently_from(data) ? data->from_reel : data->to_reel;
}
static GSize prv_get_current_size(KinoReelTransformData *data) {
return prv_is_currently_from(data) ? data->from.size : data->to.size;
}
static GPoint prv_get_interpolated_origin(KinoReelTransformData *data) {
const GSize size = prv_get_current_size(data);
const GPoint offset = interpolate_gpoint(data->normalized, grect_center_point(&data->from),
grect_center_point(&data->to));
return gpoint_sub(offset, GPoint(size.w / 2, size.h / 2));
}
static bool prv_image_size_eq_rect_size(KinoReelTransformData *data, GRect rect) {
GSize size = kino_reel_get_size(prv_get_current_reel(data));
return gsize_equal(&size, &rect.size);
}
static void prv_free_list_copy(KinoReelTransformData *data) {
applib_free(data->list_copy);
data->list_copy = NULL;
}
static GDrawCommandList *prv_get_or_create_list_copy(KinoReelTransformData *data,
GDrawCommandList *list) {
if (data->list_copy && (gdraw_command_list_get_data_size(data->list_copy) >=
gdraw_command_list_get_data_size(list))) {
return data->list_copy;
}
prv_free_list_copy(data);
data->list_copy = gdraw_command_list_clone(list);
return data->list_copy;
}
static void prv_destructor(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data->impl->destructor) {
data->impl->destructor(data->context);
}
if (data->owns_from_reel) {
kino_reel_destroy(data->from_reel);
}
if (data->owns_to_reel) {
kino_reel_destroy(data->to_reel);
}
gdraw_command_list_destroy(data->list_copy);
applib_free(data);
}
static uint32_t prv_elapsed_getter(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
return scale_int32(data->normalized, ANIMATION_NORMALIZED_MAX, data->duration);
}
static uint32_t prv_get_duration(KinoReelTransformData *data) {
uint32_t duration = data->duration;
if (data->from_reel) {
const uint32_t from_duration = kino_reel_get_duration(data->from_reel);
// If we don't have a 'to_reel' then we are looping back to the 'from_reel' so it's okay for
// the 'from_reel' to have an infinite duration
//
// If we have a 'to_reel' then ignore infinite duration requests because we will never get to
// it and burn a lot of power along the way!
if ((data->to_reel == NULL) || (from_duration != PLAY_DURATION_INFINITE)) {
duration = MAX(duration, from_duration);
}
}
if (data->to_reel) {
// We want to make sure the transform duration is at least as long as the to_reel duration
// so that the resource transition runs to completion if it's animated
const uint32_t to_duration = kino_reel_get_duration(data->to_reel);
duration = MAX(duration, to_duration);
}
return duration;
}
static bool prv_elapsed_setter(KinoReel *reel, uint32_t elapsed) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data->elapsed == elapsed) {
return false;
}
bool changed = false;
data->elapsed = elapsed;
if (data->from_reel && kino_reel_set_elapsed(data->from_reel, elapsed)) {
changed = true;
}
if (data->to_reel && kino_reel_set_elapsed(data->to_reel, elapsed)) {
changed = true;
}
const int32_t normalized = animation_timing_clip(
scale_int32(elapsed, data->duration, ANIMATION_NORMALIZED_MAX));
if (data->normalized == normalized) {
return changed;
}
data->normalized = normalized;
// No position setter is shorthand for always triggering a transform on any position setting
bool transform_changed = true;
if (data->impl->position_setter &&
!data->impl->position_setter(normalized, data->context)) {
transform_changed = false;
}
return (changed || transform_changed);
}
static uint32_t prv_duration_getter(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
return prv_get_duration(data);
}
static GSize prv_size_getter(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
return interpolate_gsize(data->normalized, data->from.size, data->to.size);
}
static void prv_transform_list(KinoReelTransformData *data) {
KinoReel *reel = prv_get_current_reel(data);
GDrawCommandList *source_list = kino_reel_get_gdraw_command_list(reel);
GDrawCommandList *list = prv_get_or_create_list_copy(data, source_list);
if (!list) {
return;
}
if (!gdraw_command_list_copy(list, gdraw_command_list_get_data_size(source_list),
source_list)) {
return;
}
if (data->impl->apply) {
const GSize size = kino_reel_get_size(reel);
data->impl->apply(list, size, &data->from, &data->to, data->normalized, data->context);
}
}
static void prv_draw_command_list_processed(GContext *ctx, GDrawCommandList *list, GPoint offset,
KinoReelProcessor *processor) {
GPoint draw_box_origin = ctx->draw_state.drawing_box.origin;
graphics_context_move_draw_box(ctx, offset);
gdraw_command_list_draw_processed(ctx, list, NULL_SAFE_FIELD_ACCESS(processor,
draw_command_processor,
NULL));
ctx->draw_state.drawing_box.origin = draw_box_origin;
}
static void prv_draw_reel_or_command_list_processed(
GContext *ctx, KinoReel *reel, GDrawCommandList *list, GPoint offset,
KinoReelProcessor *processor) {
if (list) {
prv_draw_command_list_processed(ctx, list, offset, processor);
} else {
kino_reel_draw_processed(reel, ctx, offset, processor);
}
}
static void prv_draw_processed_in_local(KinoReelTransformData *data, GContext *ctx, GPoint offset,
KinoReelProcessor *processor) {
KinoReel *reel = prv_get_current_reel(data);
GDrawCommandList *source_list = kino_reel_get_gdraw_command_list(reel);
GDrawCommandList *list = prv_get_or_create_list_copy(data, source_list);
if (data->global) {
offset = gpoint_sub(GPointZero, data->layer_frame.origin);
}
prv_draw_reel_or_command_list_processed(ctx, reel, list, offset, processor);
}
static void prv_draw_processed_in_global(KinoReelTransformData *data, GContext *ctx, GPoint offset,
KinoReelProcessor *processor) {
KinoReel *reel = prv_get_current_reel(data);
GDrawCommandList *source_list = kino_reel_get_gdraw_command_list(reel);
GDrawCommandList *list = prv_get_or_create_list_copy(data, source_list);
offset = gpoint_to_local_coordinates(GPointZero, ctx);
if (!list) {
// There is no list with global coordinates embedded. Instead, interpolate the offset.
gpoint_add_eq(&offset, prv_get_interpolated_origin(data));
}
prv_draw_reel_or_command_list_processed(ctx, reel, list, offset, processor);
}
static void prv_draw_processed_at_rect(KinoReelTransformData *data, GContext *ctx, GPoint offset,
GRect rect, KinoReelProcessor *processor) {
if (!prv_image_size_eq_rect_size(data, rect)) {
prv_transform_list(data);
prv_draw_processed_in_local(data, ctx, offset, processor);
return;
}
prv_free_list_copy(data);
if (data->global) {
offset = gpoint_sub(GPointZero, data->layer_frame.origin);
}
offset = gpoint_add(offset, rect.origin);
KinoReel *reel = prv_get_current_reel(data);
GDrawCommandList *source_list = kino_reel_get_gdraw_command_list(reel);
prv_draw_reel_or_command_list_processed(ctx, reel, source_list, offset, processor);
}
static void prv_draw_processed_func(KinoReel *reel, GContext *ctx, GPoint offset,
KinoReelProcessor *processor) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data->normalized == 0) {
prv_draw_processed_at_rect(data, ctx, offset, data->from, processor);
return;
}
if (data->normalized == ANIMATION_NORMALIZED_MAX) {
prv_draw_processed_at_rect(data, ctx, offset, data->to, processor);
return;
}
prv_transform_list(data);
if (data->global) {
prv_draw_processed_in_global(data, ctx, offset, processor);
} else {
prv_draw_processed_in_local(data, ctx, offset, processor);
}
}
static GDrawCommandList *prv_get_gdraw_command_list(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
KinoReel *reel = prv_get_current_reel(data);
return kino_reel_get_gdraw_command_list(reel);
}
return NULL;
}
static const KinoReelImpl s_kino_reel_impl_transform = {
.destructor = prv_destructor,
.get_elapsed = prv_elapsed_getter,
.set_elapsed = prv_elapsed_setter,
.get_duration = prv_duration_getter,
.get_size = prv_size_getter,
.draw_processed = prv_draw_processed_func,
.get_gdraw_command_list = prv_get_gdraw_command_list,
};
KinoReel *kino_reel_transform_create(const TransformImpl *impl, void *context) {
KinoReelTransformData *data = applib_malloc(sizeof(KinoReelTransformData));
if (!data) {
return NULL;
}
*data = (KinoReelTransformData) {
.impl = impl,
.context = context,
.duration = ANIMATION_DEFAULT_DURATION_MS,
};
KinoReel *reel = kino_reel_custom_create(&s_kino_reel_impl_transform, data);
if (!reel) {
applib_free(data);
}
return reel;
}
void *kino_reel_transform_get_context(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
return data->context;
}
return NULL;
}
void kino_reel_transform_set_from_reel(KinoReel *reel, KinoReel *from_reel,
bool take_ownership) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (!data) {
return;
}
if (data->owns_from_reel) {
kino_reel_destroy(data->from_reel);
}
data->from_reel = from_reel;
data->owns_from_reel = take_ownership;
prv_free_list_copy(data);
}
KinoReel *kino_reel_transform_get_from_reel(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
return data->from_reel;
}
return NULL;
}
void kino_reel_transform_set_to_reel(KinoReel *reel, KinoReel *to_reel,
bool take_ownership) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (!data) {
return;
}
if (data->owns_to_reel) {
kino_reel_destroy(data->to_reel);
}
data->to_reel = to_reel;
data->owns_to_reel = take_ownership;
prv_free_list_copy(data);
}
KinoReel *kino_reel_transform_get_to_reel(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
return data->to_reel;
}
return NULL;
}
void kino_reel_transform_set_layer_frame(KinoReel *reel, GRect layer_frame) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
data->layer_frame = layer_frame;
}
}
void kino_reel_transform_set_from_frame(KinoReel *reel, GRect from) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
data->from = from;
}
}
void kino_reel_transform_set_to_frame(KinoReel *reel, GRect to) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
data->to = to;
}
}
GRect kino_reel_transform_get_from_frame(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
return data->from;
}
return GRectZero;
}
GRect kino_reel_transform_get_to_frame(KinoReel *reel) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
return data->to;
}
return GRectZero;
}
void kino_reel_transform_set_global(KinoReel *reel, bool global) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
data->global = global;
}
}
void kino_reel_transform_set_transform_duration(KinoReel *reel, uint32_t duration) {
KinoReelTransformData *data = kino_reel_custom_get_data(reel);
if (data) {
data->duration = duration;
}
}