pebble/src/fw/applib/unobstructed_area_service.h
2025-01-27 11:38:16 -08:00

149 lines
8.8 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "applib/graphics/gtypes.h"
#include "applib/ui/animation.h"
//! @addtogroup UI
//! @{
//! @addtogroup UnobstructedArea
//!
//! \brief Events about when the app's unobstructed area changes for visually adapting
//!
//! Unobstructed Area enables a watchface to adapt to overlays partially obstructing it. Timeline
//! Peek is the only overlay, and it partially obstructs the bottom of watchfaces, displaying the
//! immediately upcoming or newly began event. Unobstructed Area is for use only with watchfaces.
//! There will be no Unobstructed Area events emitted for apps that are not watchfaces. Timeline
//! Peek is also limited to rectangular platforms, thus using Unobstructed Area on Chalk will also
//! result in no events.
//!
//! Watchfaces are encouraged to respect Unobstructed Area in order to dynamically resize within
//! the remaining screen area that isn't obstructed by an overlay. Unobstructed Area provides
//! handlers for when the screen area will begin and end changing as well as progress for every
//! frame of the animation, allowing one to animate the watchface to resize while the overlay
//! animates into or out of the screen. Additionally, at any point, a watchface can query the
//! Unobstructed Area using \ref layer_get_unobstructed_bounds even if there was not event or if it
//! didn't subscribe to them.
//!
//! The simplest usage of Unobstructed Area is to change rendering of your watchface to render
//! within the unobstructed bounds obtained by \ref layer_get_unobstructed_bounds. This is
//! accomplished by modifying your layer update procedure that you setup a layer with using
//! \ref layer_set_update_proc to use \ref layer_get_unobstructed_bounds instead of
//! \ref layer_get_bounds or \ref layer_get_frame and adjusting all the rendering logic within it
//! to handle resizing bounds. The app will redraw automatically whenever the Unobstructed
//! Area changes, even if you did not subscribe to the events.
//!
//! Subscribing to `will_change`, `change`, and `did_change` events with
//! \ref app_unobstructed_area_service_subscribe will allow you to obtain the final unobstructed
//! area as soon as the animation begins and the current animation progress for each frame as they
//! occur should you need that information to resize your watchface.
//!
//! When designing your watchface resizing logic or animation, take into consideration the way
//! Timeline Peek animates. Timeline Peek appears from the bottom with an animation that moves
//! slightly at first and snaps across with a bounce back. Because there is a bounce back in the
//! animation, there will be momentary frames where the unobstructed area is slightly smaller than
//! when Timeline Peek is visible but not animating.
//!
//! If you intend to hide or show layers on the screen depending on the unobstructed area, there
//! are two ways to do this. For anything inside a layer update procedure the simple solution would
//! be sufficient. That is, in your update procedure, you can to determine whether something is to
//! be rendered or not based on the remaining height available to you determined from
//! \ref layer_get_unobstructed_bounds.
//!
//! The other method for deciding whether to show or hide layers is to subscribe to events using
//! \ref app_unobstructed_area_service_subscribe. Note that hiding or showing elements in your
//! `will_change` can be too early due to the nature of the Timeline Peek animation where it moves
//! slowly in the first few frames. After judging the animation visually, you may instead show or
//! hide your layers based on both the final unobstructed area obtained from `will_change` in
//! combination with the progress obtained from `change`, or check the current height available to
//! you by calling \ref layer_get_unobstructed_bounds in your `change` handler to update your
//! state.
//!
//! For more advance usages such as functionally defined animations whose animation frames are a
//! function of animation progress, you will want to subscribe to `will_change`, `change`, and
//! optionally `did_change` events if you need to clean up resources or layers created in your
//! `will_change` handler. For example, in your `will_change` handler, you may use
//! \ref layer_get_unobstructed_bounds to get the current unobstructed area, and combine it with
//! the final unobstructed area passed into `will_change` to set up a \ref PropertyAnimation. A
//! separate example would be to save the animation progress passed to your `change` handler for
//! use in your own custom easing curve in your layer update procedure.
//! Handler that will be called just before the unobstructed area will begin changing
//! @param final_unobstructed_screen_area The final unobstructed screen area after
//! the unobstructed area has finished changing.
//! @param context A user-provided context.
typedef void (*UnobstructedAreaWillChangeHandler)(GRect final_unobstructed_screen_area,
void *context);
//! Handler that will be called every time the unobstructed area changes
//! @param progress The progress of the animation changing the unobstructed area.
//! @param context A user-provided context.
typedef void (*UnobstructedAreaChangeHandler)(AnimationProgress progress, void *context);
//! Handler that will be called after the unobstructed area has finished changing
//! @param context A user-provided context.
typedef void (*UnobstructedAreaDidChangeHandler)(void *context);
typedef struct UnobstructedAreaHandlers {
//! Handler that will be called just before the unobstructed area will begin changing.
UnobstructedAreaWillChangeHandler will_change;
//! Handler that will be called every time the unobstructed area changes.
UnobstructedAreaChangeHandler change;
//! Handler that will be called after the unobstructed area has finished changing.
UnobstructedAreaDidChangeHandler did_change;
} UnobstructedAreaHandlers;
//! @internal
//! Puts a will change event notifying of the start of an obstruction animation.
//! @param current_y The beginning obstruction y which is saved in the UnobstructedAreaState
//! @param final_y The final obstruction y which is converted to the unobstructed area and passed
//! to the `will_change` handler
void unobstructed_area_service_will_change(int16_t current_y, int16_t final_y);
//! @internal
//! Puts a change event notifying a frame change of an obstruction animation.
//! If the app was newly started, this will also put a will change event.
//! @param current_y The current obstruction y to be saved in UnobstructedAreaState for this frame
//! @param final_y The final obstruction y which is converted to the unobstructed area and may be
//! passed to the `will_change` handler if the app did not yet receive a will change event due to
//! being started mid-animation.
void unobstructed_area_service_change(int16_t current_y, int16_t final_y,
AnimationProgress progress);
//! @internal
//! Puts a did change event notifying that the obstruction animation is complete.
//! @param final_y The final obstruction y which is converted to the unobstructed area and may be
//! passed to the `will_change` handler if the app did not yet receive a will change event due to
//! being started mid-animation.
void unobstructed_area_service_did_change(int16_t final_y);
//! Subscribe to be notified when the app's unobstructed area changes. When an unobstructed area
//! begins changing, the `will_change` handler will be called, and every `will_change` call is
//! always paired with a `did_change` call that occurs when it is done changing given that
//! the `will_change` and `did_change` handlers are set. When subscribing while the unobstructed
//! area is changing, the `will_change` handler will be called after subscription in the next event
//! loop.
//! @param handlers The handlers that should be called when the unobstructed area changes.
//! @param context A user-provided context that will be passed to the callback handlers.
//! @see layer_get_unobstructed_bounds
void app_unobstructed_area_service_subscribe(UnobstructedAreaHandlers handlers, void *context);
//! Unsubscribe from notifications about changes to the app's unobstructed area.
void app_unobstructed_area_service_unsubscribe(void);
//! @} // end addtogroup UnobstructedArea
//! @} // end addtogroup UI