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,242 @@
/*
* 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 "action_bar_layer_legacy2.h"
#include "applib/graphics/graphics.h"
#include "applib/ui/window_private.h"
#include "kernel/pbl_malloc.h"
#include "system/passert.h"
#include <string.h>
inline static bool action_bar_legacy2_is_highlighted(ActionBarLayerLegacy2 *action_bar,
uint8_t index) {
PBL_ASSERTN(index < NUM_ACTION_BAR_LEGACY2_ITEMS);
return (bool) (action_bar->is_highlighted & (1 << index));
}
inline static void action_bar_legacy2_set_highlighted(ActionBarLayerLegacy2 *action_bar,
uint8_t index, bool highlighted) {
PBL_ASSERT(index < NUM_ACTION_BAR_LEGACY2_ITEMS, "Index: %"PRIu8, index);
const uint8_t bit = (1 << index);
if (highlighted) {
action_bar->is_highlighted |= bit;
} else {
action_bar->is_highlighted &= ~bit;
}
}
static void action_bar_legacy2_changed_proc(ActionBarLayerLegacy2 *action_bar, GContext* ctx) {
if (action_bar->layer.window && action_bar->layer.window->on_screen == false) {
// clear first, fixes issue of returning from other page while highlighted
for (int i = 0; i < NUM_ACTION_BAR_LEGACY2_ITEMS; i++) {
action_bar_legacy2_set_highlighted(action_bar, i, false);
}
}
}
static void action_bar_legacy2_update_proc(ActionBarLayerLegacy2 *action_bar, GContext* ctx) {
const GColor bg_color = get_native_color(action_bar->background_color);
graphics_context_set_fill_color(ctx, bg_color);
const uint8_t radius = 3;
const uint8_t margin = 1;
graphics_fill_round_rect(ctx, &action_bar->layer.bounds, radius,
GCornerTopLeft | GCornerBottomLeft);
GRect rect = action_bar->layer.bounds;
rect.origin.x += margin;
rect.origin.y += margin;
rect.size.w -= margin;
rect.size.h -= 2 * margin;
rect.size.h /= NUM_ACTION_BAR_LEGACY2_ITEMS;
const bool is_white = gcolor_equal(bg_color, GColorWhite);
const GColor highlighted_color = (is_white) ? GColorBlack : GColorWhite;
for (unsigned int index = 0; index < NUM_ACTION_BAR_LEGACY2_ITEMS; ++index) {
const GBitmap *icon = action_bar->icons[index];
if (icon) {
const bool is_highlighted = action_bar_legacy2_is_highlighted(action_bar, index);
if (is_highlighted) {
graphics_context_set_fill_color(ctx, highlighted_color);
GCornerMask corner;
switch (index) {
case 0: corner = GCornerTopLeft; break;
case NUM_ACTION_BAR_LEGACY2_ITEMS - 1: corner = GCornerBottomLeft; break;
default: corner = GCornerNone; break;
}
graphics_fill_round_rect(ctx, &rect, radius - margin, corner);
}
GRect icon_rect = icon->bounds;
const bool clip = true;
grect_align(&icon_rect, &rect, GAlignCenter, clip);
const GCompOp op = (is_white != is_highlighted) ? GCompOpAssign : GCompOpAssignInverted;
graphics_context_set_compositing_mode(ctx, op);
graphics_draw_bitmap_in_rect(ctx, (GBitmap*)icon, &icon_rect);
}
rect.origin.y += rect.size.h;
}
}
void action_bar_layer_legacy2_init(ActionBarLayerLegacy2 *action_bar) {
*action_bar = (ActionBarLayerLegacy2){};
layer_init(&action_bar->layer, &GRectZero);
action_bar->layer.update_proc = (LayerUpdateProc) action_bar_legacy2_update_proc;
action_bar->layer.property_changed_proc =
(PropertyChangedProc) action_bar_legacy2_changed_proc;
action_bar->background_color = GColor2Black;
}
ActionBarLayerLegacy2 *action_bar_layer_legacy2_create(void) {
ActionBarLayerLegacy2 * layer = task_malloc(sizeof(ActionBarLayerLegacy2));
if (layer) {
action_bar_layer_legacy2_init(layer);
}
return layer;
}
void action_bar_layer_legacy2_deinit(ActionBarLayerLegacy2 *action_bar_layer) {
layer_deinit(&action_bar_layer->layer);
}
void action_bar_layer_legacy2_destroy(ActionBarLayerLegacy2 *action_bar_layer) {
if (action_bar_layer == NULL) {
return;
}
action_bar_layer_legacy2_deinit(action_bar_layer);
task_free(action_bar_layer);
}
Layer* action_bar_layer_legacy2_get_layer(ActionBarLayerLegacy2 *action_bar_layer) {
return &action_bar_layer->layer;
}
inline static void* action_bar_legacy2_get_context(ActionBarLayerLegacy2 *action_bar) {
return action_bar->context ? action_bar->context : action_bar;
}
void action_bar_layer_legacy2_set_context(ActionBarLayerLegacy2 *action_bar, void *context) {
action_bar->context = context;
}
static void action_bar_legacy2_raw_up_down_handler(ClickRecognizerRef recognizer,
ActionBarLayerLegacy2 *action_bar,
bool is_highlighted) {
const ButtonId button_id = click_recognizer_get_button_id(recognizer);
const uint8_t index = button_id - 1;
const GBitmap *icon = action_bar->icons[index];
// is_highlighted will cause the icon in the action bar to render normal or inverted:
action_bar_legacy2_set_highlighted(action_bar, index, is_highlighted);
if (icon == NULL) {
return;
} else {
layer_mark_dirty(&action_bar->layer);
}
}
static void action_bar_legacy2_raw_up_handler(ClickRecognizerRef recognizer, void *context) {
ActionBarLayerLegacy2 *action_bar = (ActionBarLayerLegacy2 *)context;
action_bar_legacy2_raw_up_down_handler(recognizer, action_bar, false);
}
static void action_bar_legacy2_raw_down_handler(ClickRecognizerRef recognizer, void *context) {
ActionBarLayerLegacy2 *action_bar = (ActionBarLayerLegacy2 *)context;
action_bar_legacy2_raw_up_down_handler(recognizer, action_bar, true);
}
static void action_bar_legacy2_click_config_provider(ActionBarLayerLegacy2 *action_bar) {
void *context = action_bar_legacy2_get_context(action_bar);
// For UP, SELECT and DOWN, setup the raw handler and assign the user specified context:
for (ButtonId button_id = BUTTON_ID_UP; button_id < NUM_BUTTONS; ++button_id) {
window_raw_click_subscribe(button_id, action_bar_legacy2_raw_down_handler,
action_bar_legacy2_raw_up_handler, action_bar);
window_set_click_context(button_id, context);
}
// If back button is overridden, set context of BACK click recognizer as well:
if (action_bar->window && action_bar->window->overrides_back_button) {
window_set_click_context(BUTTON_ID_BACK, context);
}
if (action_bar->click_config_provider) {
action_bar->click_config_provider(context);
}
}
inline static void action_bar_legacy2_update_click_config_provider(
ActionBarLayerLegacy2 *action_bar) {
if (action_bar->window) {
window_set_click_config_provider_with_context(
action_bar->window,
(ClickConfigProvider) action_bar_legacy2_click_config_provider,
action_bar);
}
}
void action_bar_layer_legacy2_set_click_config_provider(ActionBarLayerLegacy2 *action_bar,
ClickConfigProvider click_config_provider) {
action_bar->click_config_provider = click_config_provider;
action_bar_legacy2_update_click_config_provider(action_bar);
}
void action_bar_layer_legacy2_set_icon(ActionBarLayerLegacy2 *action_bar, ButtonId button_id,
const GBitmap *icon) {
if (button_id < BUTTON_ID_UP || button_id >= NUM_BUTTONS) {
return;
}
if (action_bar->icons[button_id - 1] == icon) {
return;
}
action_bar->icons[button_id - 1] = icon;
layer_mark_dirty(&action_bar->layer);
}
void action_bar_layer_legacy2_clear_icon(ActionBarLayerLegacy2 *action_bar, ButtonId button_id) {
action_bar_layer_legacy2_set_icon(action_bar, button_id, NULL);
}
void action_bar_layer_legacy2_add_to_window(ActionBarLayerLegacy2 *action_bar,
struct Window *window) {
const uint8_t vertical_margin = 3;
const GRect *window_bounds = &window->layer.bounds;
GRect rect = GRect(0, 0, ACTION_BAR_LEGACY2_WIDTH,
window_bounds->size.h - (vertical_margin * 2));
layer_set_bounds(&action_bar->layer, &rect);
rect.origin.x = window_bounds->size.w - ACTION_BAR_LEGACY2_WIDTH;
rect.origin.y = vertical_margin;
layer_set_frame(&action_bar->layer, &rect);
layer_add_child(&window->layer, &action_bar->layer);
action_bar->window = window;
action_bar_legacy2_update_click_config_provider(action_bar);
}
void action_bar_layer_legacy2_remove_from_window(ActionBarLayerLegacy2 *action_bar) {
if (action_bar == NULL || action_bar->window == NULL) {
return;
}
layer_remove_from_parent(&action_bar->layer);
window_set_click_config_provider_with_context(action_bar->window, NULL, NULL);
action_bar->window = NULL;
}
void action_bar_layer_legacy2_set_background_color_2bit(ActionBarLayerLegacy2 *action_bar,
GColor2 background_color) {
GColor native_background_color = get_native_color(background_color);
if (gcolor_equal(native_background_color, get_native_color(action_bar->background_color))) {
return;
}
action_bar->background_color = get_closest_gcolor2(native_background_color);
layer_mark_dirty(&(action_bar->layer));
}

View file

@ -0,0 +1,246 @@
/*
* 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/ui/layer.h"
#include "applib/ui/click.h"
//! @file action_bar_layer.h
//! @addtogroup UI
//! @{
//! @addtogroup Layer Layers
//! @{
//! @addtogroup ActionBarLayerLegacy2
//! \brief Vertical, bar-shaped control widget on the right edge of the window
//!
//! ![](action_bar_layer.png)
//! ActionBarLayerLegacy2 is a Layer that displays a bar on the right edge of the
//! window. The bar can contain up to 3 icons, each corresponding with one of
//! the buttons on the right side of the watch. The purpose of each icon is
//! to provide a hint (feed-forward) to what action a click on the respective
//! button will cause.
//!
//! The action bar is useful when there are a few (up to 3) main actions that
//! are desirable to be able to take quickly, literally with one press of a
//! button.
//!
//! <h3>More actions</h3>
//! If there are more than 3 actions the user might want to take:
//! * Try assigning the top and bottom icons of the action bar to the two most
//! immediate actions and use the middle icon to push a Window with a MenuLayer
//! with less immediate actions.
//! * Secondary actions that are not vital, can be "hidden" under a long click.
//! Try to group similar actions to one button. For example, in a Music app,
//! a single click on the top button is tied to the action to jump to the
//! previous track. Holding that same button means seek backwards.
//!
//! <h3>Directionality mapping</h3>
//! When the top and bottom buttons are used to control navigating through
//! a (potentially virtual, non-visible) list of items, follow this guideline:
//! * Tie the top button to the action that goes to the _previous_ item in the
//! list, for example "jump to previous track" in a Music app.
//! * Tie the bottom button to the action that goes to the _next_ item in the
//! list, for example "jump to next track" in a Music app.
//!
//! <h3>Geometry</h3>
//! * The action bar is 20 pixels wide. Use the \ref ACTION_BAR_LEGACY2_WIDTH define.
//! * The top and bottom spacing is 3 pixels each (the space between the top and
//! bottom of the frame of the action bar and the edges of the window it is
//! contained in).
//! * Icons should not be wider than 18 pixels. It is recommended to use a size
//! of around 14 x 14 pixels for the "visual core" of the icon, and extending
//! or contracting where needed.
//! <h3>Example Code</h3>
//! The code example below shows how to do the initial setup of the action bar
//! in a window's `.load` handler.
//! Configuring the button actions is similar to the process when using
//! \ref window_set_click_config_provider(). See \ref Clicks for more
//! information.
//!
//! \code{.c}
//! ActionBarLayerLegacy2 *action_bar;
//!
//! // The implementation of my_next_click_handler and my_previous_click_handler
//! // is omitted for the sake of brevity. See the Clicks reference docs.
//!
//! void click_config_provider(void *context) {
//! window_single_click_subscribe(BUTTON_ID_DOWN, (ClickHandler) my_next_click_handler);
//! window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler) my_previous_click_handler);
//! }
//!
//! void window_load(Window *window) {
//! ...
//! // Initialize the action bar:
//! action_bar = action_bar_layer_legacy2_create();
//! // Associate the action bar with the window:
//! action_bar_layer_legacy2_add_to_window(action_bar, window);
//! // Set the click config provider:
//! action_bar_layer_legacy2_set_click_config_provider(action_bar,
//! click_config_provider);
//!
//! // Set the icons:
//! // The loading of the icons is omitted for brevity... See gbitmap_create_with_resource()
//! action_bar_layer_legacy2_set_icon(action_bar, BUTTON_ID_UP, &my_icon_previous);
//! action_bar_layer_legacy2_set_icon(action_bar, BUTTON_ID_DOWN, &my_icon_next);
//! }
//! \endcode
//! @{
//! The width of the action bar in pixels.
#define ACTION_BAR_LEGACY2_WIDTH 20
//! The maximum number of action bar items.
#define NUM_ACTION_BAR_LEGACY2_ITEMS 3
struct Window;
struct GBitmap;
//! Data structure of an action bar.
//! @note an `ActionBarLayerLegacy2 *` can safely be casted to a `Layer *` and can
//! thus be used with all other functions that take a `Layer *` as an argument.
//! <br/>For example, the following is legal:
//! \code{.c}
//! ActionBarLayerLegacy2 action_bar;
//! ...
//! layer_set_hidden((Layer *)&action_bar, true);
//! \endcode
typedef struct ActionBarLayerLegacy2 {
Layer layer;
const struct GBitmap *icons[NUM_ACTION_BAR_LEGACY2_ITEMS];
struct Window *window;
void *context;
ClickConfigProvider click_config_provider;
unsigned is_highlighted:NUM_ACTION_BAR_LEGACY2_ITEMS;
GColor2 background_color:2;
} ActionBarLayerLegacy2;
//! Initializes the action bar and reverts any state back to the default state:
//! * Background color: \ref GColorBlack
//! * No click configuration provider (`NULL`)
//! * No icons
//! * Not added to / associated with any window, thus not catching any button input yet.
//! @note Do not call this function on an action bar that is still or already added to a window.
//! @param action_bar The action bar to initialize
void action_bar_layer_legacy2_init(ActionBarLayerLegacy2 *action_bar);
//! Creates a new ActionBarLayerLegacy2 on the heap and initalizes it with the default values.
//! * Background color: \ref GColorBlack
//! * No click configuration provider (`NULL`)
//! * No icons
//! * Not added to / associated with any window, thus not catching any button input yet.
//! @return A pointer to the ActionBarLayerLegacy2. `NULL` if the ActionBarLayerLegacy2 could not
//! be created
ActionBarLayerLegacy2 *action_bar_layer_legacy2_create(void);
void action_bar_layer_legacy2_deinit(ActionBarLayerLegacy2 *action_bar_layer);
//! Destroys a ActionBarLayerLegacy2 previously created by action_bar_layer_legacy2_create
void action_bar_layer_legacy2_destroy(ActionBarLayerLegacy2 *action_bar_layer);
//! Gets the "root" Layer of the action bar layer, which is the parent for the sub-
//! layers used for its implementation.
//! @param action_bar_layer Pointer to the ActionBarLayerLegacy2 for which to get the "root" Layer
//! @return The "root" Layer of the action bar layer.
//! @internal
//! @note The result is always equal to `(Layer *) action_bar_layer`.
Layer*action_bar_layer_legacy2_get_layer(ActionBarLayerLegacy2 *action_bar_layer);
//! Sets the context parameter, which will be passed in to \ref ClickHandler
//! callbacks and the \ref ClickConfigProvider callback of the action bar.
//! @note By default, a pointer to the action bar itself is passed in, if the
//! context has not been set or if it has been set to `NULL`.
//! @param action_bar The action bar for which to assign the new context
//! @param context The new context
//! @see action_bar_layer_legacy2_set_click_config_provider()
//! @see \ref Clicks
void action_bar_layer_legacy2_set_context(ActionBarLayerLegacy2 *action_bar, void *context);
//! Sets the click configuration provider callback of the action bar.
//! In this callback your application can associate handlers to the different
//! types of click events for each of the buttons, see \ref Clicks.
//! @note If the action bar had already been added to a window and the window
//! is currently on-screen, the click configuration provider will be called
//! before this function returns. Otherwise, it will be called by the system
//! when the window becomes on-screen.
//! @note The `.raw` handlers cannot be used without breaking the automatic
//! highlighting of the segment of the action bar that for which a button is
//! @see action_bar_layer_legacy2_set_icon()
//! @param action_bar The action bar for which to assign a new click
//! configuration provider
//! @param click_config_provider The new click configuration provider
void action_bar_layer_legacy2_set_click_config_provider(ActionBarLayerLegacy2 *action_bar,
ClickConfigProvider click_config_provider);
//! Sets an action bar icon onto one of the 3 slots as identified by `button_id`.
//! Only \ref BUTTON_ID_UP, \ref BUTTON_ID_SELECT and \ref BUTTON_ID_DOWN can be
//! used. Whenever an icon is set, the click configuration provider will be
//! called, to give the application the opportunity to reconfigure the button
//! interaction.
//! @param action_bar The action bar for which to set the new icon
//! @param button_id The identifier of the button for which to set the icon
//! @param icon Pointer to the \ref GBitmap icon
//! @see action_bar_layer_legacy2_set_click_config_provider()
void action_bar_layer_legacy2_set_icon(ActionBarLayerLegacy2 *action_bar, ButtonId button_id,
const GBitmap *icon);
//! Convenience function to clear out an existing icon.
//! All it does is call `action_bar_layer_legacy2_set_icon(action_bar, button_id, NULL)`
//! @param action_bar The action bar for which to clear an icon
//! @param button_id The identifier of the button for which to clear the icon
//! @see action_bar_layer_legacy2_set_icon()
void action_bar_layer_legacy2_clear_icon(ActionBarLayerLegacy2 *action_bar, ButtonId button_id);
//! Adds the action bar's layer on top of the window's root layer. It also
//! adjusts the layout of the action bar to match the geometry of the window it
//! gets added to.
//! Lastly, it calls \ref window_set_click_config_provider_with_context() on
//! the window to set it up to work with the internal callback and raw click
//! handlers of the action bar, to enable the highlighting of the section of the
//! action bar when the user presses a button.
//! @note After this call, do not use
//! \ref window_set_click_config_provider_with_context() with the window that
//! the action bar has been added to (this would de-associate the action bar's
//! click config provider and context). Instead use
//! \ref action_bar_layer_legacy2_set_click_config_provider() and
//! \ref action_bar_layer_legacy2_set_context() to register the click configuration
//! provider to configure the buttons actions.
//! @note It is advised to call this is in the window's `.load` or `.appear`
//! handler. Make sure to call \ref action_bar_layer_legacy2_remove_from_window() in the
//! window's `.unload` or `.disappear` handler.
//! @note Adding additional layers to the window's root layer after this calll
//! can occlude the action bar.
//! @param action_bar The action bar to associate with the window
//! @param window The window with which the action bar is to be associated
void action_bar_layer_legacy2_add_to_window(ActionBarLayerLegacy2 *action_bar,
struct Window *window);
//! Removes the action bar from the window and unconfigures the window's
//! click configuration provider. `NULL` is set as the window's new click config
//! provider and also as its callback context. If it has not been added to a
//! window before, this function is a no-op.
//! @param action_bar The action bar to de-associate from its current window
void action_bar_layer_legacy2_remove_from_window(ActionBarLayerLegacy2 *action_bar);
//! Sets the background color of the action bar. Defaults to \ref GColorBlack.
//! The action bar's layer is automatically marked dirty.
//! @param action_bar The action bar of which to set the background color
//! @param background_color The new background color
void action_bar_layer_legacy2_set_background_color_2bit(ActionBarLayerLegacy2 *action_bar,
GColor2 background_color);
//! @} // end addtogroup ActionBarLayerLegacy2
//! @} // end addtogroup Layer
//! @} // end addtogroup UI

View file

@ -0,0 +1,408 @@
/*
* 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 "animation_private_legacy2.h"
#include "animation_legacy2.h"
#include "applib/ui/animation_timing.h"
#include "process_state/app_state/app_state.h"
#include "kernel/kernel_applib_state.h"
#include "kernel/pbl_malloc.h"
#include "system/passert.h"
#include "system/logging.h"
#include "util/math.h"
#include "util/order.h"
#include <string.h>
///////////////////
// Base AnimationLegacy2
//
static const uint32_t ANIMATION_TARGET_FRAME_INTERVAL_MS_LEGACY2 = 40; // 25 Hz
static void animation_legacy2_private_run(AnimationLegacy2Scheduler *animation_legacy2_scheduler);
// Unfortunately, the fields of the AnimationLegacy2 struct were made part of
// the Pebble SDK public interface, and some apps statically allocated
// AnimationLegacy2 or PropertyAnimation structs. Therefore, the size of an
// AnimationLegacy2 struct can never change without breaking apps. Thankfully,
// some bits of the AnimationLegacy2 struct were left unused due to padding and
// unnecessary use of bitfields. To be able to implement the custom
// animation curves feature, a function pointer needed to be added into
// the struct. There are only 30 unallocated bits in total in the
// struct: 28 bits of padding at the end, and two bits in the
// AnimationCurve enum (AnimationLegacy2.curve = 0b1xx encodes custom function,
// leaving lowest two bits free).
//
// Out of the 32 bits that make up a function pointer, only 31 bits need
// to be encoded. The least-significant bit will always be 1, indicating
// that the function is in Thumb-mode. Since we have only 30 bits free
// in the struct, this means that we need to drop at least one bit from
// the pointer, restricting us from being able to store a pointer to a
// function anywhere in one half of the total address space. Since
// current Pebble hardware (at the time of writing) can only have code
// in one of a few small ranges, a pointer can be packed into much fewer
// than 28 bits while still being able to address a function anywhere in
// memory that exists.
//
// For reference, those ranges are:
// 0x0000 0000 - 0x0001 FFFF - Internal Flash, remapped at 0x0
// 0x0800 0000 - 0x0801 FFFF - Internal Flash
// 0x2000 0000 - 0x2002 FFFF - Internal SRAM
//
// When such a time comes that more ranges are required, the spare two
// bits in AnimationLegacy2.curve can be utilized.
#ifndef UNITTEST
_Static_assert(sizeof(AnimationLegacy2) <= 40, "Breaking back-compatibility!");
_Static_assert(sizeof(AnimationLegacy2Scheduler) <= 16, "Breaking back-compatibility!");
#endif
// Pack a function pointer into 28 bits. We do this by dropping bits
// 1, 26, 30 and 31 and packing the remainder together.
static uintptr_t prv_custom_curve_ptr_pack(AnimationCurveFunction ptr) {
uintptr_t bits = (uintptr_t)ptr;
uint8_t top_byte = bits >> 24;
PBL_ASSERTN((top_byte & 0b11000100) == 0); // Function pointer outside of packable range!
top_byte = ((top_byte & 0b00111000) >> 1) | (top_byte & 0b11);
bits = (top_byte << 24) | (bits & 0xffffff);
bits >>= 1;
return bits;
}
// Unpack a function pointer previously packed by prv_custom_curve_ptr_pack
static AnimationCurveFunction prv_custom_curve_ptr_unpack(uintptr_t bits) {
bits = (bits << 1) | 1; // Set the Thumb bit on the function pointer
uint8_t top_byte = bits >> 24;
top_byte = ((top_byte & 0b11100) << 1) | (top_byte & 0b11);
bits = (top_byte << 24) | (bits & 0xffffff);
return (AnimationCurveFunction)bits;
}
struct AnimationLegacy2 *animation_legacy2_create(void) {
AnimationLegacy2 *animation = task_malloc(sizeof(AnimationLegacy2));
if (animation) {
animation_legacy2_init(animation);
}
return (animation);
}
void animation_legacy2_destroy(AnimationLegacy2 *animation) {
if (animation == NULL) {
return;
}
animation_legacy2_unschedule(animation);
task_free(animation);
}
void animation_legacy2_init(struct AnimationLegacy2 *animation) {
PBL_ASSERTN(animation != NULL);
*animation = (AnimationLegacy2){};
animation->duration_ms = 250;
animation->curve = AnimationCurveEaseInOut;
animation->handlers = (AnimationLegacy2Handlers) { NULL, NULL };
animation->context = NULL;
animation->is_completed = false;
}
static int animation_legacy2_scheduler_comparator(AnimationLegacy2 *animation_legacy2_a,
AnimationLegacy2 *animation_legacy2_b) {
return serial_distance32(animation_legacy2_a->abs_start_time_ms,
animation_legacy2_b->abs_start_time_ms);
}
static AnimationLegacy2Scheduler* animation_legacy2_scheduler_data_for_app_ctx_idx(
AppTaskCtxIdx idx) {
if (idx == AppTaskCtxIdxApp) {
return (AnimationLegacy2Scheduler *)app_state_get_animation_state();
}
return (AnimationLegacy2Scheduler *)kernel_applib_get_animation_state();
}
static AnimationLegacy2Scheduler* get_current_scheduler(void) {
if (pebble_task_get_current() == PebbleTask_App) {
return (AnimationLegacy2Scheduler *)app_state_get_animation_state();
}
return (AnimationLegacy2Scheduler *)kernel_applib_get_animation_state();
}
inline static uint32_t animation_legacy2_get_ms_since_system_start(void) {
return (sys_get_ticks() * 1000 / RTC_TICKS_HZ);
}
static void animation_legacy2_timer_callback(
AnimationLegacy2Scheduler *animation_legacy2_scheduler) {
animation_legacy2_scheduler->timer_handle = NULL;
animation_legacy2_private_run(animation_legacy2_scheduler);
}
static void animation_legacy2_reschedule_timer(
AnimationLegacy2Scheduler *animation_legacy2_scheduler,
uint32_t rate_control_delay_ms) {
AnimationLegacy2 *animation = (AnimationLegacy2 *) animation_legacy2_scheduler->head;
if (animation == NULL) {
return;
}
const uint32_t now = animation_legacy2_get_ms_since_system_start();
const int32_t delta_ms = serial_distance32(now, animation->abs_start_time_ms);
const uint32_t interval_ms = MAX(delta_ms, 0) + rate_control_delay_ms;
if (animation_legacy2_scheduler->timer_handle != NULL) {
app_timer_reschedule(animation_legacy2_scheduler->timer_handle, interval_ms);
// Ignore the return value of reschedule. If it fails it probably means the callback is already
// fired and we're waiting for the handler to be called. This will end up rescheduling us for
// the right time once that gets handled.
} else {
animation_legacy2_scheduler->timer_handle =
app_timer_register(interval_ms, (AppTimerCallback) animation_legacy2_timer_callback,
animation_legacy2_scheduler);
PBL_ASSERTN(animation_legacy2_scheduler->timer_handle != NULL);
}
}
static void animation_legacy2_private_schedule(AnimationLegacy2 *animation,
AnimationLegacy2Scheduler* animation_legacy2_scheduler) {
PBL_ASSERTN(animation != NULL);
PBL_ASSERTN(animation->implementation->update != NULL);
if (animation_legacy2_is_scheduled(animation)) {
animation_legacy2_unschedule(animation);
}
const uint32_t now = animation_legacy2_get_ms_since_system_start();
animation->abs_start_time_ms = now + animation->delay_ms;
if (animation->implementation->setup != NULL) {
animation->implementation->setup(animation);
}
const bool old_head_is_animating = animation_legacy2_scheduler->head
? (((AnimationLegacy2 *)animation_legacy2_scheduler->head)->abs_start_time_ms <= now)
: false;
const bool ascending = true;
animation_legacy2_scheduler->head = list_sorted_add(animation_legacy2_scheduler->head,
&animation->list_node, (Comparator) animation_legacy2_scheduler_comparator, ascending);
const bool has_new_head = (&animation->list_node == animation_legacy2_scheduler->head);
if (has_new_head) {
// Only reschedule the timer if the previous head animation wasn't running yet:
if (old_head_is_animating == false) {
animation_legacy2_reschedule_timer(animation_legacy2_scheduler, 0);
}
}
}
void animation_legacy2_private_unschedule(AnimationLegacy2 *animation,
AnimationLegacy2Scheduler *animation_legacy2_scheduler, const bool finished) {
if (animation == NULL || !animation_legacy2_is_scheduled(animation)) {
return;
}
PBL_ASSERTN(animation->implementation != NULL);
const bool was_old_head = (animation == (AnimationLegacy2 *) animation_legacy2_scheduler->head);
list_remove(&animation->list_node, &animation_legacy2_scheduler->head, NULL);
// Reschedule the timer if we're removing the head animation:
if (was_old_head && animation_legacy2_scheduler->head != NULL) {
animation_legacy2_reschedule_timer(animation_legacy2_scheduler, 0);
}
// Reset these fields, before calling .stopped(), so that this animation
// instance can be rescheduled again in the .stopped() handler, if needed.
animation->abs_start_time_ms = 0;
animation->is_completed = false;
if (animation->handlers.stopped) {
animation->handlers.stopped(animation, finished, animation->context);
}
if (animation->implementation->teardown != NULL) {
animation->implementation->teardown(animation);
}
}
void animation_legacy2_private_unschedule_all(AppTaskCtxIdx idx) {
AnimationLegacy2Scheduler *animation_legacy2_scheduler =
animation_legacy2_scheduler_data_for_app_ctx_idx(idx);
AnimationLegacy2 *animation = (AnimationLegacy2 *) animation_legacy2_scheduler->head;
while (animation) {
AnimationLegacy2 *next = (AnimationLegacy2 *) list_get_next(&animation->list_node);
animation_legacy2_private_unschedule(animation, animation_legacy2_scheduler, false);
animation = next;
}
}
void animation_legacy2_private_init_scheduler(
AnimationLegacy2Scheduler *animation_legacy2_scheduler) {
*animation_legacy2_scheduler = (AnimationLegacy2Scheduler) {
.timer_handle = NULL,
.last_delay_ms = ANIMATION_TARGET_FRAME_INTERVAL_MS_LEGACY2,
.last_frame_time = animation_legacy2_get_ms_since_system_start()
};
}
void animation_legacy2_schedule(AnimationLegacy2 *animation) {
animation_legacy2_private_schedule(animation, get_current_scheduler());
}
void animation_legacy2_unschedule(AnimationLegacy2 *animation) {
animation_legacy2_private_unschedule(animation, get_current_scheduler(), false);
}
void animation_legacy2_unschedule_all(void) {
animation_legacy2_private_unschedule_all(AppTaskCtxIdxApp);
}
bool animation_legacy2_is_scheduled(AnimationLegacy2 *animation) {
AnimationLegacy2Scheduler *animation_legacy2_scheduler = get_current_scheduler();
return list_contains(animation_legacy2_scheduler->head, &animation->list_node);
}
static void animation_legacy2_private_run(AnimationLegacy2Scheduler *animation_legacy2_scheduler) {
AnimationLegacy2 *animation = (AnimationLegacy2 *) animation_legacy2_scheduler->head;
const uint32_t now = animation_legacy2_get_ms_since_system_start();
while (animation) {
const int32_t rel_ms_running = serial_distance32(animation->abs_start_time_ms, now);
if (rel_ms_running < 0) {
// AnimationLegacy2s are ordered by abs_start_time_ms.
// We've reached an animation that should not start yet, so
// everything after and including this animation shouldn't run yet.
break;
}
// Get a pointer to next now, because after unscheduling this animation won't have a next.
AnimationLegacy2 *next = (AnimationLegacy2*) list_get_next(&animation->list_node);
if (animation->is_completed) {
// Unschedule + call animation.stopped callback:
const bool finished = true;
animation_legacy2_private_unschedule(animation, animation_legacy2_scheduler, finished);
} else {
// If this is the animation's first frame, call the 'started' handler:
if (animation->handlers.started &&
animation->abs_start_time_ms > animation_legacy2_scheduler->last_frame_time) {
animation->handlers.started(animation, animation->context);
}
const uint32_t time_normalized_raw = (animation->duration_ms != 0)
? ((ANIMATION_NORMALIZED_MAX * rel_ms_running) / animation->duration_ms)
: ANIMATION_NORMALIZED_MAX;
const uint32_t time_normalized = MIN(time_normalized_raw, ANIMATION_NORMALIZED_MAX);
uint32_t distance_normalized;
if (animation->curve >= AnimationCurveCustomFunction) {
distance_normalized = prv_custom_curve_ptr_unpack(
animation->custom_curve_function)(time_normalized);
} else {
distance_normalized = animation_timing_curve(time_normalized, animation->curve);
}
animation->implementation->update(animation, distance_normalized);
const bool completed = (time_normalized == ANIMATION_NORMALIZED_MAX);
if (completed) {
if (animation->duration_ms != ANIMATION_DURATION_INFINITE) {
// Leave the animation on the list for now, we'll unschedule it the next time,
// so it's guaranteed the animation.stopped callback gets fired after the
// (render) events caused by this last update have been processed:
animation->is_completed = true;
}
}
}
animation = next;
};
// Frame rate control:
const int32_t frame_interval_ms = serial_distance32(
animation_legacy2_scheduler->last_frame_time, now);
const int32_t error_ms = frame_interval_ms - ANIMATION_TARGET_FRAME_INTERVAL_MS_LEGACY2;
const int32_t theoretic_delay_ms = animation_legacy2_scheduler->last_delay_ms - error_ms;
const uint32_t delay_ms = CLIP(theoretic_delay_ms, (int32_t) 0,
(int32_t) ANIMATION_TARGET_FRAME_INTERVAL_MS_LEGACY2);
animation_legacy2_reschedule_timer(animation_legacy2_scheduler, delay_ms);
animation_legacy2_scheduler->last_delay_ms = delay_ms;
animation_legacy2_scheduler->last_frame_time = now;
}
void animation_legacy2_set_handlers(AnimationLegacy2 *animation, AnimationLegacy2Handlers handlers,
void *context) {
PBL_ASSERTN(animation->abs_start_time_ms == 0); // can't set after animation has been added
animation->context = context;
animation->handlers = handlers;
}
void animation_legacy2_set_implementation(AnimationLegacy2 *animation,
const AnimationLegacy2Implementation *implementation) {
PBL_ASSERTN(animation->abs_start_time_ms == 0); // can't set after animation has been added
animation->implementation = implementation;
}
void *animation_legacy2_get_context(AnimationLegacy2 *animation) {
return animation->context;
}
void animation_legacy2_set_delay(AnimationLegacy2 *animation, uint32_t delay_ms) {
PBL_ASSERTN(animation->abs_start_time_ms == 0); // can't set after animation has been added
animation->delay_ms = delay_ms;
}
void animation_legacy2_set_duration(AnimationLegacy2 *animation, uint32_t duration_ms) {
PBL_ASSERTN(animation->abs_start_time_ms == 0); // can't set after animation has been added
animation->duration_ms = duration_ms;
}
void animation_legacy2_set_curve(AnimationLegacy2 *animation, AnimationCurve curve) {
PBL_ASSERTN(animation->abs_start_time_ms == 0); // can't set after animation has been added
PBL_ASSERTN(curve < AnimationCurveCustomFunction);
animation->curve = curve;
}
void animation_legacy2_set_custom_curve(AnimationLegacy2 *animation,
AnimationCurveFunction curve_function) {
animation->curve = AnimationCurveCustomFunction;
animation->custom_curve_function = prv_custom_curve_ptr_pack(curve_function);
}
AnimationCurveFunction animation_legacy2_get_custom_curve(AnimationLegacy2 *animation) {
return prv_custom_curve_ptr_unpack(animation->custom_curve_function);
}
static void dump_scheduler(char* buffer, int buffer_size,
AnimationLegacy2Scheduler* animation_legacy2_scheduler) {
AnimationLegacy2 *animation = (AnimationLegacy2 *) animation_legacy2_scheduler->head;
while (animation) {
dbgserial_putstr_fmt(buffer, buffer_size,
"<%p> { abs_start_time_ms = %"PRIu32", delay = %"PRIu32", duration = %"PRIu32", "
"curve = %i, run = %p }",
animation, animation->abs_start_time_ms, animation->delay_ms, animation->duration_ms,
animation->curve, animation->implementation->update);
animation = (AnimationLegacy2 *)list_get_next(&animation->list_node);
}
}
void command_legacy2_animations_info(void) {
char buffer[128];
dbgserial_putstr_fmt(buffer, sizeof(buffer), "Now: %"PRIu32,
animation_legacy2_get_ms_since_system_start());
dbgserial_putstr_fmt(buffer, sizeof(buffer), "Kernel AnimationLegacy2s:");
dump_scheduler(buffer, sizeof(buffer),
(AnimationLegacy2Scheduler *)kernel_applib_get_animation_state());
dbgserial_putstr_fmt(buffer, sizeof(buffer), "App AnimationLegacy2s:");
dump_scheduler(buffer, sizeof(buffer),
(AnimationLegacy2Scheduler *)app_state_get_animation_state());
}

View file

@ -0,0 +1,286 @@
/*
* 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 <stdint.h>
#include <stdbool.h>
#include "util/list.h"
#include "drivers/rtc.h"
#include "applib/ui/animation.h"
//! @file animation.h
//! @addtogroup UI
//! @{
//! @addtogroup AnimationLegacy2
//! \brief Abstract framework to create arbitrary animations
//!
//! The AnimationLegacy2 framework provides your Pebble app with an base layer to create arbitrary
//! animations. The simplest way to work with animations is to use the layer frame
//! \ref PropertyAnimationLegacy2, which enables you to move a Layer around on the screen.
//! Using animation_legacy2_set_implementation(), you can implement a custom animation.
//!
//! @{
///////////////////
// Base AnimationLegacy2
//
struct AnimationLegacy2;
struct AnimationLegacy2Implementation;
struct AnimationLegacy2Handlers;
//! Creates a new AnimationLegacy2 on the heap and initalizes it with the default values.
//!
//! * Duration: 250ms,
//! * Curve: \ref AnimationCurveEaseInOut (ease-in-out),
//! * Delay: 0ms,
//! * Handlers: `{NULL, NULL}` (none),
//! * Context: `NULL` (none),
//! * Implementation: `NULL` (no implementation),
//! * Scheduled: no
//! @return A pointer to the animation. `NULL` if the animation could not
//! be created
struct AnimationLegacy2 *animation_legacy2_create(void);
//! Destroys an AnimationLegacy2 previously created by animation_legacy2_create.
void animation_legacy2_destroy(struct AnimationLegacy2 *animation);
//! @internal
//! resets animation to default values (see animation_create)
void animation_legacy2_init(struct AnimationLegacy2 *animation);
//! Sets the time in milliseconds that an animation takes from start to finish.
//! @param animation The animation for which to set the duration.
//! @param duration_ms The duration in milliseconds of the animation. This excludes
//! any optional delay as set using \ref animation_legacy2_set_delay().
void animation_legacy2_set_duration(struct AnimationLegacy2 *animation, uint32_t duration_ms);
//! Sets an optional delay for the animation.
//! @param animation The animation for which to set the delay.
//! @param delay_ms The delay in milliseconds that the animation system should
//! wait from the moment the animation is scheduled to starting the animation.
void animation_legacy2_set_delay(struct AnimationLegacy2 *animation, uint32_t delay_ms);
//! Sets the animation curve for the animation.
//! @param animation The animation for which to set the curve.
//! @param curve The type of curve.
//! @see AnimationCurve
void animation_legacy2_set_curve(struct AnimationLegacy2 *animation, AnimationCurve curve);
//! Sets a custom animation curve function.
//! @param animation The animation for which to set the curve.
//! @param curve_function The custom animation curve function.
//! @see AnimationCurveFunction
void animation_legacy2_set_custom_curve(struct AnimationLegacy2 *animation,
AnimationCurveFunction curve_function);
//! Gets the custom animation curve function.
//! @param animation The animation for which to get the curve.
//! @return The custom animation curve function for the given animation. NULL if not set.
//! @see AnimationCurveFunction
AnimationCurveFunction animation_legacy2_get_custom_curve(struct AnimationLegacy2 *animation);
//! The function pointer type of the handler that will be called when an animation is started,
//! just before updating the first frame of the animation.
//! @param animation The animation that was started.
//! @param context The pointer to custom, application specific data, as set using
//! \ref animation_legacy2_set_handlers()
//! @note This is called after any optional delay as set by \ref animation_legacy2_set_delay()
//! has expired.
//! @see animation_legacy2_set_handlers
typedef void (*AnimationLegacy2StartedHandler)(struct AnimationLegacy2 *animation, void *context);
//! The function pointer type of the handler that will be called when the animation is stopped.
//! @param animation The animation that was stopped.
//! @param finished True if the animation was stopped because it was finished normally,
//! or False if the animation was stopped prematurely, because it was unscheduled before finishing.
//! @param context The pointer to custom, application specific data, as set using
//! \ref animation_legacy2_set_handlers()
//! @see animation_legacy2_set_handlers
typedef void (*AnimationLegacy2StoppedHandler)(struct AnimationLegacy2 *animation, bool finished,
void *context);
//! The handlers that will get called when an animation starts and stops.
//! See documentation with the function pointer types for more information.
//! @see animation_legacy2_set_handlers
typedef struct AnimationLegacy2Handlers {
//! The handler that will be called when an animation is started.
AnimationLegacy2StartedHandler started;
//! The handler that will be called when an animation is stopped.
AnimationLegacy2StoppedHandler stopped;
} AnimationLegacy2Handlers;
//! Sets the callbacks for the animation.
//! Often an application needs to run code at the start or at the end of an animation.
//! Using this function is possible to register callback functions with an animation,
//! that will get called at the start and end of the animation.
//! @param animation The animation for which to set up the callbacks.
//! @param callbacks The callbacks.
//! @param context A pointer to application specific data, that will be passed as an argument by
//! the animation subsystem when a callback is called.
void animation_legacy2_set_handlers(struct AnimationLegacy2 *animation,
AnimationLegacy2Handlers callbacks, void *context);
//! Gets the application-specific callback context of the animation.
//! This `void` pointer is passed as an argument when the animation system calls AnimationHandlers
//! callbacks. The context pointer can be set to point to any application specific data using
//! \ref animation_legacy2_set_handlers().
//! @param animation The animation.
//! @see animation_legacy2_set_handlers
void *animation_legacy2_get_context(struct AnimationLegacy2 *animation);
//! Schedules the animation. Call this once after configuring an animation to get it to
//! start running.
//!
//! If the animation's implementation has a `.setup` callback it will get called before
//! this function returns.
//!
//! @note If the animation was already scheduled,
//! it will first unschedule it and then re-schedule it again.
//! Note that in that case, the animation's `.stopped` handler, the implementation's
//! `.teardown` and `.setup` will get called, due to the unscheduling and scheduling.
//! @param animation The animation to schedule.
//! @see \ref animation_legacy2_unschedule()
void animation_legacy2_schedule(struct AnimationLegacy2 *animation);
//! Unschedules the animation, which in effect stops the animation.
//! @param animation The animation to unschedule.
//! @note If the animation was not yet finished, unscheduling it will
//! cause its `.stopped` handler to get called, with the "finished" argument set to false.
//! @see \ref animation_legacy2_schedule()
void animation_legacy2_unschedule(struct AnimationLegacy2 *animation);
//! Unschedules all animations of the application.
//! @see animation_legacy2_unschedule
void animation_legacy2_unschedule_all(void);
//! @return True if the animation was scheduled, or false if it was not.
//! @note An animation will be scheduled when it is running and not finished yet.
//! An animation that has finished is automatically unscheduled.
//! @param animation The animation for which to get its scheduled state.
//! @see animation_legacy2_schedule
//! @see animation_legacy2_unschedule
bool animation_legacy2_is_scheduled(struct AnimationLegacy2 *animation);
//! The data structure of an animation.
typedef struct AnimationLegacy2 {
ListNode list_node;
const struct AnimationLegacy2Implementation *implementation;
AnimationLegacy2Handlers handlers;
void *context;
//! Absolute time when the animation got scheduled, in ms since system start.
uint32_t abs_start_time_ms;
uint32_t delay_ms;
uint32_t duration_ms;
AnimationCurve curve:3;
bool is_completed:1;
//! Pointer to a custom curve. Unfortunately, due to backward-compatibility
//! constraints, it must fit into 28 bits.
//! It is only valid when curve == AnimationCurveCustomFunction.
//! The mapping from 28-bit field to pointer is unpublished. Call
//! animation_legacy2_set_custom_curve() to ensure your app continues to run
//! after future Pebble updates.
uintptr_t custom_curve_function:28;
} AnimationLegacy2;
///////////////////////////////////////
// Implementing custom animation types
//! Pointer to function that (optionally) prepares the animation for running.
//! This callback is called when the animation is added to the scheduler.
//! @param animation The animation that needs to be set up.
//! @see animation_legacy2_schedule
//! @see AnimationTeardownImplementation
typedef void (*AnimationLegacy2SetupImplementation)(struct AnimationLegacy2 *animation);
//! Pointer to function that updates the animation according to the given normalized distance.
//! This callback will be called repeatedly by the animation scheduler whenever the animation needs
//! to be updated.
//! @param animation The animation that needs to update; gets passed in by the animation framework.
//! @param distance_normalized The current normalized distance; gets passed in by the animation
//! framework for each animation frame.
//! This is a value between \ref ANIMATION_NORMALIZED_MIN and \ref ANIMATION_NORMALIZED_MAX.
//! At the start of the animation, the value will be \ref ANIMATION_NORMALIZED_MIN.
//! At the end of the animation, the value will be \ref ANIMATION_NORMALIZED_MAX.
//! For each frame during the animation, the value will be the distance along the
//! animation path, mapped between \ref ANIMATION_NORMALIZED_MIN and
//! \ref ANIMATION_NORMALIZED_MAX based on the animation duration and the
//! \ref AnimationCurve set.
//! For example, say an animation was scheduled at t = 1.0s, has a delay of 1.0s,
//! a duration of 2.0s and a curve of AnimationCurveLinear.
//! Then the .update callback will get called on t = 2.0s with
//! distance_normalized = \ref ANIMATION_NORMALIZED_MIN. For each frame
//! thereafter until t = 4.0s, the update callback will get called where
//! distance_normalized is
//! (\ref ANIMATION_NORMALIZED_MIN + (((\ref ANIMATION_NORMALIZED_MAX -
//! \ref ANIMATION_NORMALIZED_MIN) * t) / duration)).
//! Other animation curves will result in a non-linear relation between
//! distance_normalized and time.
//! @internal
//! @see animation_legacy2_timing.h
typedef void (*AnimationLegacy2UpdateImplementation)(struct AnimationLegacy2 *animation,
const uint32_t distance_normalized);
//! Pointer to function that (optionally) cleans up the animation.
//! This callback is called when the animation is removed from the scheduler.
//! In case the `.setup` implementation
//! allocated any memory, this is a good place to release that memory again.
//! @param animation The animation that needs to be teared down.
//! @see animation_legacy2_unschedule
//! @see AnimationSetupImplementation
typedef void (*AnimationLegacy2TeardownImplementation)(struct AnimationLegacy2 *animation);
//! The 3 callbacks that implement a custom animation.
//! Only the `.update` callback is mandatory, `.setup` and `.teardown` are optional.
//! See the documentation with the function pointer typedefs for more information.
//!
//! @note The `.setup` callback is called immediately after scheduling the animation,
//! regardless if there is a delay set for that animation using \ref animation_legacy2_set_delay().
//!
//! The diagram below illustrates the order in which callbacks can be expected to get called
//! over the life cycle of an animation. It also illustrates where the implementation of
//! different animation callbacks are intended to be “living”.
//! ![](animations.png)
//!
//! @see AnimationLegacy2SetupImplementation
//! @see AnimationLegacy2UpdateImplementation
//! @see AnimationLegacy2TeardownImplementation
typedef struct AnimationLegacy2Implementation {
//! Called by the animation system when an animation is scheduled, to prepare it for running.
//! This callback is optional and can be left `NULL` when not needed.
AnimationLegacy2SetupImplementation setup;
//! Called by the animation system when the animation needs to calculate the next animation frame.
//! This callback is mandatory and should not be left `NULL`.
AnimationLegacy2UpdateImplementation update;
//! Called by the animation system when an animation is unscheduled, to clean up after it has run.
//! This callback is optional and can be left `NULL` when not needed.
AnimationLegacy2TeardownImplementation teardown;
} AnimationLegacy2Implementation;
//! Sets the implementation of the custom animation.
//! When implementing custom animations, use this function to specify what functions need to be called to
//! for the setup, frame update and teardown of the animation.
//! @param animation The animation for which to set the implementation.
//! @param implementation The structure with function pointers to the implementation of the setup, update and teardown functions.
//! @see AnimationImplementation
void animation_legacy2_set_implementation(struct AnimationLegacy2 *animation,
const AnimationLegacy2Implementation *implementation);
//! @} // group AnimationLegacy2
//! @} // group UI

View file

@ -0,0 +1,36 @@
/*
* 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/app_manager.h"
#include "syscall/syscall.h"
typedef struct {
ListNode *head; //! Pointer to the Animation struct that is the animation that is scheduled
//! first.
AppTimer* timer_handle;
//! The delay the animation scheduler uses between finishing a frame and starting a new one.
//! Derived from actual rendering/calculation times, using a PID-like control algorithm.
uint32_t last_delay_ms;
uint32_t last_frame_time; //! Absolute RTC time of the moment the last animation frame started.
} AnimationLegacy2Scheduler;
void animation_legacy2_private_init_scheduler(AnimationLegacy2Scheduler* scheduler);
void animation_legacy2_private_unschedule_all(AppTaskCtxIdx app_task_ctx_idx);

View file

@ -0,0 +1,81 @@
/*
* 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 "menu_layer_legacy2.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "system/passert.h"
extern void menu_layer_init_scroll_layer_callbacks(MenuLayer *menu_layer);
void menu_layer_legacy2_init(MenuLayer *menu_layer, const GRect *frame) {
*menu_layer = (MenuLayer){};
ScrollLayer *scroll_layer = &menu_layer->scroll_layer;
scroll_layer_init(scroll_layer, frame);
menu_layer_init_scroll_layer_callbacks(menu_layer);
scroll_layer_set_context(scroll_layer, menu_layer);
menu_layer_set_normal_colors(menu_layer, GColorWhite, GColorBlack);
menu_layer_set_highlight_colors(menu_layer, GColorBlack, GColorWhite);
InverterLayer *inverter = &menu_layer->inverter;
inverter_layer_init(inverter, &GRectZero);
scroll_layer_add_child(scroll_layer, &inverter->layer);
}
MenuLayer* menu_layer_legacy2_create(GRect frame) {
MenuLayer *layer = task_malloc(sizeof(MenuLayer));
if (layer) {
menu_layer_legacy2_init(layer, &frame);
}
return layer;
}
void menu_layer_legacy2_set_callbacks(MenuLayer *menu_layer,
void *callback_context,
MenuLayerCallbacksLegacy2 callbacks) {
menu_layer_set_callbacks(menu_layer, callback_context, &(MenuLayerCallbacks) {
.get_num_sections = callbacks.get_num_sections,
.get_num_rows = callbacks.get_num_rows,
.get_cell_height = callbacks.get_cell_height,
.get_header_height = callbacks.get_header_height,
.draw_row = callbacks.draw_row,
.draw_header = callbacks.draw_header,
.select_click = callbacks.select_click,
.select_long_click = callbacks.select_long_click,
.selection_changed = callbacks.selection_changed,
.get_separator_height = callbacks.get_separator_height,
.draw_separator = callbacks.draw_separator,
});
}
void menu_layer_legacy2_set_callbacks__deprecated(MenuLayer *menu_layer,
void *callback_context,
MenuLayerCallbacksLegacy2__deprecated callbacks) {
menu_layer_set_callbacks(menu_layer, callback_context, &(MenuLayerCallbacks) {
.get_num_sections = callbacks.get_num_sections,
.get_num_rows = callbacks.get_num_rows,
.get_cell_height = callbacks.get_cell_height,
.get_header_height = callbacks.get_header_height,
.draw_row = callbacks.draw_row,
.draw_header = callbacks.draw_header,
.select_click = callbacks.select_click,
.select_long_click = callbacks.select_long_click,
.selection_changed = callbacks.selection_changed,
});
}

View file

@ -0,0 +1,159 @@
/*
* 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/ui/menu_layer.h"
#include "applib/graphics/gtypes.h"
#define MENU_CELL_LEGACY2_BASIC_SEPARATOR_HEIGHT ((const int16_t) 1)
//! Data structure containing all the callbacks of a MenuLayer.
typedef struct MenuLayerCallbacksLegacy2 {
//! Callback that gets called to get the number of sections in the menu.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the number of sections defaults to 1.
MenuLayerGetNumberOfSectionsCallback get_num_sections;
//! Callback that gets called to get the number of rows in a section. This
//! can get called at various moments throughout the life of a menu.
//! @note Must be set to a valid callback; `NULL` causes undefined behavior.
MenuLayerGetNumberOfRowsInSectionsCallback get_num_rows;
//! Callback that gets called to get the height of a cell.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the default height of 44 pixels is used.
MenuLayerGetCellHeightCallback get_cell_height;
//! Callback that gets called to get the height of a section header.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the defaults height of 0 pixels is used. This disables
//! section headers.
MenuLayerGetHeaderHeightCallback get_header_height;
//! Callback that gets called to render a menu item.
//! This gets called for each menu item, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback; `NULL` causes undefined behavior.
MenuLayerDrawRowCallback draw_row;
//! Callback that gets called to render a section header.
//! This gets called for each section header, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback, unless `.get_header_height` is
//! `NULL`. Causes undefined behavior otherwise.
MenuLayerDrawHeaderCallback draw_header;
//! Callback that gets called when the user triggers a click with the SELECT
//! button.
//! @note When `NULL`, click events for the SELECT button are ignored.
MenuLayerSelectCallback select_click;
//! Callback that gets called when the user triggers a long click with the
//! SELECT button.
//! @note When `NULL`, long click events for the SELECT button are ignored.
MenuLayerSelectCallback select_long_click;
//! Callback that gets called whenever the selection changes.
//! @note When `NULL`, selection change events are ignored.
MenuLayerSelectionChangedCallback selection_changed;
//! Callback that gets called to get the height of a separator
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the default height of 1 is used.
MenuLayerGetSeparatorHeightCallback get_separator_height;
//! Callback that gets called to render a separator.
//! This gets called for each separator, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback, unless `.get_separator_height` is
//! `NULL`. Causes undefined behavior otherwise.
MenuLayerDrawSeparatorCallback draw_separator;
} MenuLayerCallbacksLegacy2;
typedef struct MenuLayerCallbacksLegacy2__deprecated {
//! Callback that gets called to get the number of sections in the menu.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the number of sections defaults to 1.
MenuLayerGetNumberOfSectionsCallback get_num_sections;
//! Callback that gets called to get the number of rows in a section. This
//! can get called at various moments throughout the life of a menu.
//! @note Must be set to a valid callback; `NULL` causes undefined behavior.
MenuLayerGetNumberOfRowsInSectionsCallback get_num_rows;
//! Callback that gets called to get the height of a cell.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the default height of 44 pixels is used.
MenuLayerGetCellHeightCallback get_cell_height;
//! Callback that gets called to get the height of a section header.
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the defaults height of 0 pixels is used. This disables
//! section headers.
MenuLayerGetHeaderHeightCallback get_header_height;
//! Callback that gets called to render a menu item.
//! This gets called for each menu item, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback; `NULL` causes undefined behavior.
MenuLayerDrawRowCallback draw_row;
//! Callback that gets called to render a section header.
//! This gets called for each section header, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback, unless `.get_header_height` is
//! `NULL`. Causes undefined behavior otherwise.
MenuLayerDrawHeaderCallback draw_header;
//! Callback that gets called when the user triggers a click with the SELECT
//! button.
//! @note When `NULL`, click events for the SELECT button are ignored.
MenuLayerSelectCallback select_click;
//! Callback that gets called when the user triggers a long click with the
//! SELECT button.
//! @note When `NULL`, long click events for the SELECT button are ignored.
MenuLayerSelectCallback select_long_click;
//! Callback that gets called whenever the selection changes.
//! @note When `NULL`, selection change events are ignored.
MenuLayerSelectionChangedCallback selection_changed;
//! Callback that gets called to get the height of a separator
//! This can get called at various moments throughout the life of a menu.
//! @note When `NULL`, the default height of 1 is used.
MenuLayerGetSeparatorHeightCallback get_separator_height;
//! Callback that gets called to render a separator.
//! This gets called for each separator, every time it needs to be
//! re-rendered.
//! @note Must be set to a valid callback, unless `.get_separator_height` is
//! `NULL`. Causes undefined behavior otherwise.
MenuLayerDrawSeparatorCallback draw_separator;
} MenuLayerCallbacksLegacy2__deprecated;
void menu_layer_legacy2_init(MenuLayer *menu_layer, const GRect *frame);
MenuLayer* menu_layer_legacy2_create(GRect frame);
void menu_layer_legacy2_set_callbacks(MenuLayer *menu_layer,
void *callback_context,
MenuLayerCallbacksLegacy2 callbacks);
void menu_layer_legacy2_set_callbacks__deprecated(MenuLayer *menu_layer,
void *callback_context,
MenuLayerCallbacksLegacy2__deprecated callbacks);

View file

@ -0,0 +1,169 @@
/*
* 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 "property_animation_legacy2.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/animation_timing.h"
#include "applib/ui/layer.h"
#include "kernel/pbl_malloc.h"
#include "system/passert.h"
#include "system/logging.h"
/////////////////////
// Property Animation
//
static inline int16_t distance_interpolate(const int32_t normalized,
int16_t from, int16_t to) {
return from + ((normalized * (to - from)) / ANIMATION_NORMALIZED_MAX);
}
void property_animation_legacy2_update_int16(PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized) {
int16_t result = distance_interpolate(distance_normalized,
property_animation->values.from.int16,
property_animation->values.to.int16);
((PropertyAnimationLegacy2Implementation*)property_animation->animation.implementation)
->accessors.setter.int16(property_animation->subject, result);
}
void property_animation_legacy2_update_gpoint(PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized) {
GPoint result;
result.x = distance_interpolate(distance_normalized,
property_animation->values.from.gpoint.x,
property_animation->values.to.gpoint.x);
result.y = distance_interpolate(distance_normalized,
property_animation->values.from.gpoint.y,
property_animation->values.to.gpoint.y);
((PropertyAnimationLegacy2Implementation*)
property_animation->animation.implementation)
->accessors.setter.gpoint(property_animation->subject, result);
}
void property_animation_legacy2_update_grect(PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized) {
GRect result;
result.origin.x = distance_interpolate(
distance_normalized,
property_animation->values.from.grect.origin.x,
property_animation->values.to.grect.origin.x);
result.origin.y = distance_interpolate(
distance_normalized,
property_animation->values.from.grect.origin.y,
property_animation->values.to.grect.origin.y);
result.size.w = distance_interpolate(
distance_normalized,
property_animation->values.from.grect.size.w,
property_animation->values.to.grect.size.w);
result.size.h = distance_interpolate(
distance_normalized,
property_animation->values.from.grect.size.h,
property_animation->values.to.grect.size.h);
((PropertyAnimationLegacy2Implementation*)
property_animation->animation.implementation)
->accessors.setter.grect(property_animation->subject, result);
}
void property_animation_legacy2_init(
PropertyAnimationLegacy2 *property_animation,
const PropertyAnimationLegacy2Implementation *implementation,
void *subject, void *from_value, void *to_value) {
animation_legacy2_init(&property_animation->animation);
memset(&property_animation->values, 0xff, sizeof(property_animation->values));
property_animation->animation.implementation =
(AnimationLegacy2Implementation*) implementation;
property_animation->subject = subject;
// NOTE: we are also comparing against the 3.0 animation update handlers so that modules
// like scroll_layer and menu_layer can use the legacy 2.0 animation when interfacing with a
// 2.x app.
if (property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_legacy2_update_int16
|| property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_update_int16) {
property_animation->values.to.int16 = to_value ? *((int16_t*)to_value)
: implementation->accessors.getter.int16(subject);
property_animation->values.from.int16 = from_value ? *((int16_t*)from_value)
: implementation->accessors.getter.int16(subject);
} else if (property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_legacy2_update_gpoint
|| property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_update_gpoint) {
property_animation->values.to.gpoint = to_value ? *((GPoint*)to_value)
: implementation->accessors.getter.gpoint(subject);
property_animation->values.from.gpoint =
from_value ? *((GPoint*)from_value) : implementation->accessors.getter.gpoint(subject);
} else if (property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_legacy2_update_grect
|| property_animation->animation.implementation->update
== (AnimationLegacy2UpdateImplementation) property_animation_update_grect) {
property_animation->values.to.grect = to_value ? *((GRect*)to_value)
: implementation->accessors.getter.grect(subject);
property_animation->values.from.grect = from_value ? *((GRect*)from_value)
: implementation->accessors.getter.grect(subject);
}
}
struct PropertyAnimationLegacy2* property_animation_legacy2_create(
const struct PropertyAnimationLegacy2Implementation *implementation,
void *subject, void *from_value, void *to_value) {
struct PropertyAnimationLegacy2* property_animation =
task_malloc(sizeof(struct PropertyAnimationLegacy2));
if (property_animation) {
property_animation_legacy2_init(property_animation, implementation, subject,
from_value, to_value);
}
return property_animation;
}
void property_animation_legacy2_destroy(struct PropertyAnimationLegacy2* property_animation) {
if (property_animation == NULL) {
return;
}
animation_legacy2_unschedule(&property_animation->animation);
task_free(property_animation);
}
void property_animation_legacy2_init_layer_frame(
PropertyAnimationLegacy2 *property_animation, struct Layer *layer,
GRect *from_frame, GRect *to_frame) {
static const PropertyAnimationLegacy2Implementation implementation = {
.base = {
.update = (AnimationLegacy2UpdateImplementation) property_animation_legacy2_update_grect,
},
.accessors = {
.setter = { .grect = (const GRectSetter) layer_set_frame_by_value, },
.getter = { .grect = (const GRectGetter) layer_get_frame_by_value, },
},
};
property_animation_legacy2_init(property_animation, &implementation, layer,
from_frame, to_frame);
}
struct PropertyAnimationLegacy2* property_animation_legacy2_create_layer_frame(
struct Layer *layer, GRect *from_frame, GRect *to_frame) {
struct PropertyAnimationLegacy2* property_animation =
task_malloc(sizeof(struct PropertyAnimationLegacy2));
if (property_animation) {
property_animation_legacy2_init_layer_frame(property_animation, layer, from_frame,
to_frame);
}
return property_animation;
}

View file

@ -0,0 +1,242 @@
/*
* 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 "animation_legacy2.h"
#include "applib/ui/property_animation.h"
#include "applib/graphics/gtypes.h"
//////////////////////
// Legacy2 Property Animations
//
//! @file property_animation_legacy2_legacy2.h
//! @addtogroup UI
//! @{
//! @addtogroup AnimationLegacy2
//! @{
//! @addtogroup PropertyAnimationLegacy2
//! \brief Concrete animations to move a layer around over time
//!
//! Actually, property animations do more than just moving a Layer around over time.
//! PropertyAnimationLegacy2 is a concrete class of animations and is built around the Animation
//! subsystem, which covers anything timing related, but does not move anything around.
//! A ProperyAnimation animates a "property" of a "subject".
//!
//! <h3>Animating a Layer's frame property</h3>
//! Currently there is only one specific type of property animation offered off-the-shelf, namely
//! one to change the frame (property) of a layer (subject), see
//! \ref property_animation_legacy2_create_layer_frame().
//!
//! <h3>Implementing a custom PropertyAnimationLegacy2</h3>
//! It is fairly simple to create your own variant of a PropertyAnimationLegacy2.
//!
//! Please refer to \htmlinclude UiFramework.html (chapter "Property Animations") for a conceptual
//! overview
//! of the animation framework and make sure you understand the underlying \ref Animation, in case
//! you are not familiar with it, before trying to implement a variation on
//! PropertyAnimationLegacy2.
//!
//! To implement a custom property animation, use \ref property_animation_legacy2_create() and
//! provide a
//! function pointers to the accessors (getter and setter) and setup, update and teardown callbacks
//! in the implementation argument.
//! Note that the type of property to animate with \ref PropertyAnimationLegacy2 is limited to
//! int16_t, GPoint or GRect.
//!
//! For each of these types, there are implementations provided
//! for the necessary `.update` handler of the animation: see
//! \ref property_animation_legacy2_update_int16(), \ref property_animation_legacy2_update_gpoint()
//! and \ref property_animation_legacy2_update_grect().
//! These update functions expect the `.accessors` to conform to the following interface:
//! Any getter needs to have the following function signature: `__type__ getter(void *subject);`
//! Any setter needs to have to following function signature: `void setter(void *subject,
//! __type__ value);`
//! See \ref Int16Getter, \ref Int16Setter, \ref GPointGetter, \ref GPointSetter, \ref GRectGetter,
//! \ref GRectSetter
//! for the typedefs that accompany the update fuctions.
//!
//! \code{.c}
//! static const PropertyAnimationLegacy2Implementation my_implementation = {
//! .base = {
//! // using the "stock" update callback:
//! .update = (AnimationUpdateImplementation) property_animation_legacy2_update_gpoint,
//! },
//! .accessors = {
//! // my accessors that get/set a GPoint from/onto my subject:
//! .setter = { .gpoint = my_layer_set_corner_point, },
//! .getter = { .gpoint = (const GPointGetter) my_layer_get_corner_point, },
//! },
//! };
//! static PropertyAnimationLegacy2* s_my_animation_ptr = NULL;
//! static GPoint s_to_point = GPointZero;
//! ...
//! // Use NULL as 'from' value, this will make the animation framework call the getter
//! // to get the current value of the property and use that as the 'from' value:
//! s_my_animation_ptr = property_animation_legacy2_create(&my_implementation, my_layer, NULL,
//! &s_to_point);
//! animation_schedule(s_my_animation_ptr->animation);
//! \endcode
//! @{
struct PropertyAnimationLegacy2;
struct Layer;
struct PropertyAnimationLegacy2Implementation;
//! @internal
void property_animation_legacy2_init_layer_frame(
struct PropertyAnimationLegacy2 *property_animation, struct Layer *layer, GRect *from_frame,
GRect *to_frame);
//! Convenience function to create and initialize a property animation that animates the frame of a
//! Layer. It sets up the PropertyAnimationLegacy2 to use \ref layer_set_frame() and
//! \ref layer_get_frame() as accessors and uses the `layer` parameter as the subject for the
//! animation. The same defaults are used as with \ref animation_create().
//! @param layer the layer that will be animated
//! @param from_frame the frame that the layer should animate from
//! @param to_frame the frame that the layer should animate to
//! @note Pass in `NULL` as one of the frame arguments to have it set automatically to the layer's
//! current frame. This will result in a call to \ref layer_get_frame() to get the current frame of
//! the layer.
//! @return A pointer to the property animation. `NULL` if animation could not
//! be created
struct PropertyAnimationLegacy2* property_animation_legacy2_create_layer_frame(struct Layer *layer,
GRect *from_frame, GRect *to_frame);
//////////////////////////////////////////
// Implementing custom Property Animations
//
//! @internal
//! See \ref property_animation_legacy2_create() for a description of the parameter list
void property_animation_legacy2_init(struct PropertyAnimationLegacy2 *property_animation,
const struct PropertyAnimationLegacy2Implementation *implementation, void *subject,
void *from_value, void *to_value);
//! Creates a new PropertyAnimationLegacy2 on the heap and and initializes it with the specified
//! values. The same defaults are used as with \ref animation_create().
//! If the `from_value` or the `to_value` is `NULL`, the getter accessor will be called to get the
//! current value of the property and be used instead.
//! @param implementation Pointer to the implementation of the animation. In most cases, it makes
//! sense to pass in a `static const` struct pointer.
//! @param subject Pointer to the "subject" being animated. This will be passed in when the
//! getter/setter accessors are called,
//! see \ref PropertyAnimationLegacy2Accessors, \ref GPointSetter, and friends. The value of this
//! pointer will be copied into the `.subject` field of the PropertyAnimationLegacy2 struct.
//! @param from_value Pointer to the value that the subject should animate from
//! @param to_value Pointer to the value that the subject should animate to
//! @note Pass in `NULL` as one of the value arguments to have it set automatically to the
//! subject's current property value, as returned by the getter function. Also note that passing in
//! `NULL` for both `from_value` and `to_value`, will result in the animation having the same from-
//! and to- values, effectively not doing anything.
//! @return A pointer to the property animation. `NULL` if animation could not
//! be created
struct PropertyAnimationLegacy2* property_animation_legacy2_create(
const struct PropertyAnimationLegacy2Implementation *implementation, void *subject,
void *from_value, void *to_value);
//! Free a dynamically allocated property animation
//! @param property_animation The property animation to be freed.
void property_animation_legacy2_destroy(struct PropertyAnimationLegacy2* property_animation);
//! Default update callback for a property animations to update a property of type int16_t.
//! Assign this function to the `.base.update` callback field of your
//! PropertyAnimationLegacy2Implementation,
//! in combination with a `.getter` and `.setter` accessors of types \ref Int16Getter and
//! \ref Int16Setter.
//! The implementation of this function will calculate the next value of the animation and call the
//! setter to set the new value upon the subject.
//! @param property_animation The property animation for which the update is requested.
//! @param distance_normalized The current normalized distance. See
//! \ref AnimationUpdateImplementation
//! @note This function is not supposed to be called "manually", but will be called automatically
//! when the animation is being run.
void property_animation_legacy2_update_int16(struct PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized);
//! Default update callback for a property animations to update a property of type GPoint.
//! Assign this function to the `.base.update` callback field of your
//! PropertyAnimationLegacy2Implementation, in combination with a `.getter` and `.setter` accessors
//! of types \ref GPointGetter and \ref GPointSetter.
//! The implementation of this function will calculate the next point of the animation and call the
//! setter to set the new point upon the subject.
//! @param property_animation The property animation for which the update is requested.
//! @param distance_normalized The current normalized distance. See
//! \ref AnimationUpdateImplementation
//! @note This function is not supposed to be called "manually", but will be called automatically
//! when the animation is being run.
void property_animation_legacy2_update_gpoint(struct PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized);
//! Default update callback for a property animations to update a property of type GRect.
//! Assign this function to the `.base.update` callback field of your
//! PropertyAnimationLegacy2Implementation,
//! in combination with a `.getter` and `.setter` accessors of types \ref GRectGetter and
//! \ref GRectSetter.
//! The implementation of this function will calculate the next rectangle of the animation and call
//! the setter to set the new rectangle upon the subject.
//! @param property_animation The property animation for which the update is requested.
//! @param distance_normalized The current normalized distance. See
//! \ref AnimationUpdateImplementation
//! @note This function is not supposed to be called "manually", but will be called automatically
//! when the animation is being run.
void property_animation_legacy2_update_grect(struct PropertyAnimationLegacy2 *property_animation,
const uint32_t distance_normalized);
//! Data structure containing a collection of function pointers that form the implementation of the
//! property animation.
//! See the code example at the top (\ref PropertyAnimationLegacy2).
typedef struct PropertyAnimationLegacy2Implementation {
//! The "inherited" fields from the Animation "base class".
AnimationLegacy2Implementation base;
//! The accessors to set/get the property to be animated.
PropertyAnimationAccessors accessors;
} PropertyAnimationLegacy2Implementation;
//! The data structure of a property animation that contains all its state.
typedef struct PropertyAnimationLegacy2 {
//! The "inherited" state from the "base class", \ref Animation.
AnimationLegacy2 animation;
//! The values of the property that the animation should animated from and to.
struct {
//! The value of the property that the animation should animate to.
//! When the animation completes, this value will be the final value that is set.
union {
//! Valid when the property being animated is of type GRect
GRect grect;
//! Valid when the property being animated is of type GPoint
GPoint gpoint;
//! Valid when the property being animated is of type int16_t
int16_t int16;
} to;
//! The value of the property that the animation should animate to.
//! When the animation starts, this value will be the initial value that is set.
union {
//! Valid when the property being animated is of type GRect
GRect grect;
//! Valid when the property being animated is of type GPoint
GPoint gpoint;
//! Valid when the property being animated is of type int16_t
int16_t int16;
} from;
} values; //!< See detail table
void *subject; //!< The subject of the animation of which the property should be animated.
} PropertyAnimationLegacy2;
//! @} // group PropertyAnimationLegacy2
//! @} // group Animation
//! @} // group UI

View file

@ -0,0 +1,28 @@
/*
* 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"
//! @file status_bar_legacy2.h
//!
//! This file implements a 2.x status bar for backwards compatibility with apps compiled with old
//! firmwares.
#define STATUS_BAR_HEIGHT 16
#define STATUS_BAR_FRAME GRect(0, 0, DISP_COLS, STATUS_BAR_HEIGHT)

View file

@ -0,0 +1,217 @@
/*
* 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 "text_layer_legacy2.h"
#include "applib/graphics/gtypes.h"
#include "process_state/app_state/app_state.h"
#include "applib/graphics/graphics.h"
#include "applib/fonts/fonts.h"
#include "kernel/pbl_malloc.h"
#include "system/logging.h"
#include "system/passert.h"
#include <string.h>
#include <stddef.h>
static GTextLayoutCacheRef prv_text_layer_legacy2_get_cache_handle(TextLayerLegacy2 *text_layer) {
if (text_layer == NULL) {
return NULL;
}
return text_layer->should_cache_layout ? text_layer->layout_cache : NULL;
}
void text_layer_legacy2_update_proc(TextLayerLegacy2 *text_layer, GContext* ctx) {
if (text_layer == NULL) {
return;
}
const GColor bg_color = get_native_color(text_layer->background_color);
if (!(gcolor_equal(bg_color, GColorClear))) {
graphics_context_set_fill_color(ctx, bg_color);
graphics_fill_rect(ctx, &text_layer->layer.bounds);
}
if (text_layer->text && strlen(text_layer->text) > 0) {
graphics_context_set_text_color(ctx, get_native_color(text_layer->text_color));
graphics_draw_text(ctx, text_layer->text, text_layer->font, text_layer->layer.bounds,
text_layer->overflow_mode, text_layer->text_alignment,
prv_text_layer_legacy2_get_cache_handle(text_layer));
}
}
void text_layer_legacy2_init(TextLayerLegacy2 *text_layer, const GRect *frame) {
PBL_ASSERTN(text_layer);
*text_layer = (TextLayerLegacy2){};
text_layer->layer.frame = *frame;
text_layer->layer.bounds = (GRect){{0, 0}, frame->size};
text_layer->layer.update_proc = (LayerUpdateProc)text_layer_legacy2_update_proc;
text_layer->text_color = GColor2Black;
text_layer->background_color = GColor2White;
text_layer->overflow_mode = GTextOverflowModeTrailingEllipsis;
layer_set_clips(&text_layer->layer, true);
text_layer->text_alignment = GTextAlignmentLeft;
text_layer->font = fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD);
layer_mark_dirty(&(text_layer->layer));
}
TextLayerLegacy2* text_layer_legacy2_create(GRect frame) {
TextLayerLegacy2* layer = task_malloc(sizeof(TextLayerLegacy2));
if (layer) {
text_layer_legacy2_init(layer, &frame);
}
return layer;
}
void text_layer_legacy2_destroy(TextLayerLegacy2* text_layer) {
if (!text_layer) {
return;
}
text_layer_legacy2_deinit(text_layer);
task_free(text_layer);
}
void text_layer_legacy2_deinit(TextLayerLegacy2 *text_layer) {
if (text_layer == NULL) {
return;
}
layer_deinit(&text_layer->layer);
graphics_text_layout_cache_deinit(&text_layer->layout_cache);
text_layer->layout_cache = NULL;
}
Layer* text_layer_legacy2_get_layer(TextLayerLegacy2 *text_layer) {
if (text_layer == NULL) {
return NULL;
}
return &text_layer->layer;
}
void text_layer_legacy2_set_size(TextLayerLegacy2 *text_layer, const GSize max_size) {
if (text_layer == NULL) {
return;
}
layer_set_frame(&text_layer->layer, &(GRect) { text_layer->layer.frame.origin, max_size });
layer_mark_dirty(&text_layer->layer);
}
GSize text_layer_legacy2_get_size(TextLayerLegacy2* text_layer) {
if (text_layer == NULL) {
return GSizeZero;
}
return text_layer->layer.frame.size;
}
void text_layer_legacy2_set_text(TextLayerLegacy2 *text_layer, const char *text) {
if (text_layer == NULL) {
return;
}
text_layer->text = text;
layer_mark_dirty(&text_layer->layer);
}
const char* text_layer_legacy2_get_text(TextLayerLegacy2 *text_layer) {
if (text_layer == NULL) {
return NULL;
}
return text_layer->text;
}
void text_layer_legacy2_set_background_color_2bit(TextLayerLegacy2 *text_layer, GColor2 color) {
if (text_layer == NULL) {
return;
}
GColor native_color = get_native_color(color);
const GColor bg_color = get_native_color(text_layer->background_color);
if (gcolor_equal(native_color, bg_color)) {
return;
}
text_layer->background_color = get_closest_gcolor2(native_color);
layer_mark_dirty(&(text_layer->layer));
}
void text_layer_legacy2_set_text_color_2bit(TextLayerLegacy2 *text_layer, GColor2 color) {
if (text_layer == NULL) {
return;
}
GColor8 native_color = get_native_color(color);
const GColor text_color = get_native_color(text_layer->text_color);
if (gcolor_equal(native_color, text_color)) {
return;
}
text_layer->text_color = get_closest_gcolor2(native_color);
layer_mark_dirty(&(text_layer->layer));
}
void text_layer_legacy2_set_text_alignment(TextLayerLegacy2 *text_layer,
GTextAlignment text_alignment) {
if (text_layer == NULL || text_alignment == text_layer->text_alignment) {
return;
}
text_layer->text_alignment = text_alignment;
layer_mark_dirty(&(text_layer->layer));
}
void text_layer_legacy2_set_overflow_mode(TextLayerLegacy2 *text_layer,
GTextOverflowMode overflow_mode) {
if (text_layer == NULL || overflow_mode == text_layer->overflow_mode) {
return;
}
text_layer->overflow_mode = overflow_mode;
layer_mark_dirty(&(text_layer->layer));
}
void text_layer_legacy2_set_font(TextLayerLegacy2 *text_layer, GFont font) {
if (text_layer == NULL || font == text_layer->font) {
return;
}
text_layer->font = font;
layer_mark_dirty(&(text_layer->layer));
}
void text_layer_legacy2_set_should_cache_layout(TextLayerLegacy2 *text_layer,
bool should_cache_layout) {
if (text_layer == NULL || should_cache_layout == text_layer->should_cache_layout) {
return;
}
text_layer->should_cache_layout = should_cache_layout;
if (text_layer->should_cache_layout) {
PBL_LOG(LOG_LEVEL_DEBUG, "Init layout");
graphics_text_layout_cache_init(&text_layer->layout_cache);
} else {
graphics_text_layout_cache_deinit(&text_layer->layout_cache);
text_layer->layout_cache = NULL;
}
}
GSize text_layer_legacy2_get_content_size(GContext* ctx, TextLayerLegacy2 *text_layer) {
if (text_layer == NULL) {
return GSizeZero;
} else if (!text_layer->should_cache_layout) {
text_layer_legacy2_set_should_cache_layout(text_layer, true);
}
GTextLayoutCacheRef layout = prv_text_layer_legacy2_get_cache_handle(text_layer);
PBL_ASSERTN(layout);
return graphics_text_layout_get_max_used_size(ctx, text_layer->text, text_layer->font,
text_layer->layer.bounds, text_layer->overflow_mode, text_layer->text_alignment, layout);
}
GSize app_text_layer_legacy2_get_content_size(TextLayerLegacy2 *text_layer) {
GContext* ctx = app_state_get_graphics_context();
return text_layer_legacy2_get_content_size(ctx, text_layer);
}

View file

@ -0,0 +1,195 @@
/*
* 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/ui/layer.h"
#include "applib/graphics/text.h"
//! @file text_layer_legacy2.h
//! @addtogroup UI
//! @{
//! @addtogroup Layer Layers
//! @{
//! @addtogroup TextLayerLegacy2
//! \brief Layer that displays and formats a text string.
//!
//! ![](text_layer.png)
//! The geometric information (bounds, frame) of the Layer
//! is used as the "box" in which the text is drawn. The \ref TextLayerLegacy2 also has a number of
//! other properties that influence how the text is drawn. Most important of these properties are:
//! a pointer to the string to draw itself, the font, the text color, the background color of the
//! layer, the overflow mode and alignment of the text inside the layer.
//! @see Layer
//! @see TextDrawing
//! @see Fonts
//! @{
//! The data structure of a TextLayerLegacy2.
//! @note a `TextLayerLegacy2 *` can safely be casted to a `Layer *` and can thus be used
//! with all other functions that take a `Layer *` as an argument.
//! <br/>For example, the following is legal:
//! \code{.c}
//! TextLayerLegacy2 text_layer;
//! ...
//! layer_set_hidden((Layer *)&text_layer, true);
//! \endcode
typedef struct TextLayerLegacy2 {
Layer layer;
const char* text;
GFont font;
GTextLayoutCacheRef layout_cache;
GColor2 text_color:2;
GColor2 background_color:2;
GTextOverflowMode overflow_mode:2;
GTextAlignment text_alignment:2;
bool should_cache_layout:1;
} TextLayerLegacy2;
//! Initializes the TextLayerLegacy2 with given frame
//! All previous contents are erased and the following default values are set:
//! * Font: Raster Gothic 14-point Boldface (system font)
//! * Text Alignment: \ref GTextAlignmentLeft
//! * Text color: \ref GColorBlack
//! * Background color: \ref GColorWhite
//! * Clips: `true`
//! * Hidden: `false`
//! * Caching: `false`
//!
//! The text layer is automatically marked dirty after this operation.
//! @param text_layer The TextLayerLegacy2 to initialize
//! @param frame The frame with which to initialze the TextLayerLegacy2
void text_layer_legacy2_init(TextLayerLegacy2 *text_layer, const GRect *frame);
//! Creates a new TextLayerLegacy2 on the heap and initializes it with the default values.
//!
//! * Font: Raster Gothic 14-point Boldface (system font)
//! * Text Alignment: \ref GTextAlignmentLeft
//! * Text color: \ref GColorBlack
//! * Background color: \ref GColorWhite
//! * Clips: `true`
//! * Hidden: `false`
//! * Caching: `false`
//!
//! The text layer is automatically marked dirty after this operation.
//! @param frame The frame with which to initialze the TextLayerLegacy2
//! @return A pointer to the TextLayerLegacy2. `NULL` if the TextLayerLegacy2 could not
//! be created
TextLayerLegacy2* text_layer_legacy2_create(GRect frame);
//! Destroys a TextLayerLegacy2 previously created by text_layer_legacy2_create.
void text_layer_legacy2_destroy(TextLayerLegacy2* text_layer);
//! Deinitializes the TextLayerLegacy2 and frees any caches.
//! @param text_layer The TextLayerLegacy2 to deinitialize
//! @internal
//! @see text_layer_legacy2_set_should_cache_layout
//! @note This MUST be called after discarding the text layer when using layout caching.
void text_layer_legacy2_deinit(TextLayerLegacy2 *text_layer);
//! Gets the "root" Layer of the text layer, which is the parent for the sub-
//! layers used for its implementation.
//! @param text_layer Pointer to the TextLayerLegacy2 for which to get the "root" Layer
//! @return The "root" Layer of the text layer.
//! @internal
//! @note The result is always equal to `(Layer *) text_layer`.
Layer* text_layer_legacy2_get_layer(TextLayerLegacy2 *text_layer);
//! Sets the pointer to the string where the TextLayerLegacy2 is supposed to find the text
//! at a later point in time, when it needs to draw itself.
//! @param text_layer The TextLayerLegacy2 of which to set the text
//! @param text The new text to set onto the TextLayerLegacy2. This must be a null-terminated and
//! valid UTF-8 string!
//! @note The string is not copied, so its buffer most likely cannot be stack allocated,
//! but is recommended to be a buffer that is long-lived, at least as long as the TextLayerLegacy2
//! is part of a visible Layer hierarchy.
//! @see text_layer_legacy2_get_text
void text_layer_legacy2_set_text(TextLayerLegacy2 *text_layer, const char *text);
//! Gets the pointer to the string that the TextLayerLegacy2 is using.
//! @param text_layer The TextLayerLegacy2 for which to get the text
//! @see text_layer_legacy2_set_text
const char* text_layer_legacy2_get_text(TextLayerLegacy2 *text_layer);
//! Sets the background color of bounding box that will be drawn behind the text
//! @param text_layer The TextLayerLegacy2 of which to set the background color
//! @param color The new \ref GColor to set the background to
//! @see text_layer_legacy2_set_text_color
void text_layer_legacy2_set_background_color_2bit(TextLayerLegacy2 *text_layer, GColor2 color);
//! Sets the color of text that will be drawn
//! @param text_layer The TextLayerLegacy2 of which to set the text color
//! @param color The new \ref GColor to set the text color to
//! @see text_layer_legacy2_set_background_color
void text_layer_legacy2_set_text_color_2bit(TextLayerLegacy2 *text_layer, GColor2 color);
//! Sets the line break mode of the TextLayerLegacy2
//! @param text_layer The TextLayerLegacy2 of which to set the overflow mode
//! @param line_mode The new \ref GTextOverflowMode to set
void text_layer_legacy2_set_overflow_mode(TextLayerLegacy2 *text_layer,
GTextOverflowMode line_mode);
//! Sets the font of the TextLayerLegacy2
//! @param text_layer The TextLayerLegacy2 of which to set the font
//! @param font The new \ref GFont for the TextLayerLegacy2
//! @see fonts_get_system_font
//! @see fonts_load_custom_font
void text_layer_legacy2_set_font(TextLayerLegacy2 *text_layer, GFont font);
//! Sets the alignment of the TextLayerLegacy2
//! @param text_layer The TextLayerLegacy2 of which to set the alignment
//! @param text_alignment The new text alignment for the TextLayerLegacy2
//! @see GTextAlignment
void text_layer_legacy2_set_text_alignment(TextLayerLegacy2 *text_layer,
GTextAlignment text_alignment);
//! @internal
//! Sets whether or not the text layer should cache text layout information.
//! By default, layout caching is off (false). Layout caches store the max used
//! height and width of a text layer.
//! NOTE: when using cached layouts, text_layer_legacy2_deinit() MUST be called at some
//! point in time to prevent memory leaks from occuring.
void text_layer_legacy2_set_should_cache_layout(TextLayerLegacy2 *text_layer,
bool should_cache_layout);
//! @internal
//! Calculates the size occupied by the current text of the TextLayerLegacy2
//! @param ctx the current graphics context
//! @param text_layer the TextLayerLegacy2 for which to calculate the text's size
//! @return The size occupied by the current text of the TextLayerLegacy2. If the text
//! string is not valid UTF-8 or NULL, the size returned will be (0,0).
//! @note Because of an implementation detail, it is necessary to pass in the current graphics
//! context, even though this function does not draw anything.
//! @internal
//! @see \ref app_get_current_graphics_context()
GSize text_layer_legacy2_get_content_size(GContext* ctx, TextLayerLegacy2 *text_layer);
//! Calculates the size occupied by the current text of the TextLayerLegacy2
//! @param text_layer the TextLayerLegacy2 for which to calculate the text's size
//! @return The size occupied by the current text of the TextLayerLegacy2
GSize app_text_layer_legacy2_get_content_size(TextLayerLegacy2 *text_layer);
//! Update the size of the text layer
//! This is a convenience function to update the frame of the TextLayerLegacy2.
//! @param text_layer The TextLayerLegacy2 of which to set the size
//! @param max_size The new size for the TextLayerLegacy2
void text_layer_legacy2_set_size(TextLayerLegacy2 *text_layer, const GSize max_size);
GSize text_layer_legacy2_get_size(TextLayerLegacy2* text_layer);
//! @} // end addtogroup TextLayerLegacy2
//! @} // end addtogroup Layer
//! @} // end addtogroup UI