mirror of
https://github.com/google/pebble.git
synced 2025-05-01 15:51:40 -04:00
525 lines
19 KiB
C
525 lines
19 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "window_stack.h"
|
|
|
|
#include "animation_private.h"
|
|
#include "app_window_click_glue.h"
|
|
#include "window_manager.h"
|
|
#include "window_private.h"
|
|
#include "window_stack_animation.h"
|
|
#include "window_stack_private.h"
|
|
|
|
#include "applib/applib_malloc.auto.h"
|
|
#include "applib/legacy2/ui/status_bar_legacy2.h"
|
|
#include "kernel/events.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "kernel/ui/kernel_ui.h"
|
|
#include "process_management/app_manager.h"
|
|
#include "process_state/app_state/app_state.h"
|
|
#include "services/common/compositor/compositor.h"
|
|
#include "syscall/syscall.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/struct.h"
|
|
|
|
// Private API
|
|
////////////////////////////////////
|
|
|
|
static bool prv_filter_window_item_for_window(ListNode *node, void *data) {
|
|
WindowStackItem *item = (WindowStackItem *)node;
|
|
return item->window == data;
|
|
}
|
|
|
|
static WindowStackItem *prv_next_item(WindowStackItem *item) {
|
|
return (WindowStackItem *)list_get_next(&item->list_node);
|
|
}
|
|
|
|
static WindowStackItem *prv_find_window_stack_item_for_window(WindowStack *window_stack,
|
|
Window *window) {
|
|
if (!window_stack) {
|
|
// Window can't be on a NULL window stack.
|
|
return NULL;
|
|
}
|
|
|
|
WindowStackItem *item = (WindowStackItem *)list_find(window_stack->list_head,
|
|
prv_filter_window_item_for_window, window);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void prv_set_new_window_on_screen(Window *appeared_window) {
|
|
GRect bounds = window_calc_frame(window_get_fullscreen(appeared_window));
|
|
|
|
Layer *root_layer = window_get_root_layer(appeared_window);
|
|
layer_set_bounds(root_layer, &bounds);
|
|
|
|
window_set_on_screen(appeared_window, true /* new window */, true /* call handlers */);
|
|
|
|
click_manager_clear(window_manager_get_window_click_manager(appeared_window));
|
|
}
|
|
|
|
static void prv_unload_removed_windows(WindowStack *window_stack) {
|
|
// Copy the removed windows list into a local array and then call the unload callback on
|
|
// each removed window.
|
|
WindowStackItem *items_to_unload[WINDOW_STACK_ITEMS_MAX];
|
|
unsigned int num_items = 0;
|
|
while (window_stack->removed_list_head) {
|
|
if (num_items >= WINDOW_STACK_ITEMS_MAX) {
|
|
break;
|
|
}
|
|
WindowStackItem *removed_item = (WindowStackItem *)window_stack->removed_list_head;
|
|
items_to_unload[num_items++] = removed_item;
|
|
window_stack->removed_list_head = list_pop_head(window_stack->removed_list_head);
|
|
}
|
|
|
|
WindowTransitioningContext *context = &window_stack->transition_context;
|
|
|
|
for (unsigned int i = 0; i < num_items; ++i) {
|
|
// The update routine for the transition_to animation relies on the
|
|
// window_to being present. If we are unloading the window we should really
|
|
// unschedule the animation so we don't touch free'd memory!
|
|
//
|
|
// For now, rely on our animation transition routines having checks for
|
|
// NULL windows since our animation subsystem can't cope with these
|
|
// unschedules in some cases (See PBL-25460 for more details)
|
|
if (context->window_to == items_to_unload[i]->window) {
|
|
context->window_to = NULL;
|
|
}
|
|
|
|
if (context->window_from == items_to_unload[i]->window) {
|
|
context->window_from = NULL;
|
|
}
|
|
|
|
window_unload(items_to_unload[i]->window);
|
|
applib_free(items_to_unload[i]);
|
|
}
|
|
}
|
|
|
|
static void prv_transition_to(Window *window_from, Window *window_to,
|
|
const WindowTransitionImplementation *transition) {
|
|
PBL_ASSERTN(window_to && transition);
|
|
WindowStack *window_stack = window_to->parent_window_stack;
|
|
WindowTransitioningContext *context = &window_stack->transition_context;
|
|
|
|
if (context->animation) {
|
|
// If we currently have an animation, run it to completion immediately before starting
|
|
// another transition.
|
|
// For 2.x apps, just unschedule the animation because there is no equivalent call for
|
|
// animation_set_elapsed (which is not available to 2.x apps)
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
animation_unschedule(context->animation);
|
|
applib_free(context->animation);
|
|
} else {
|
|
animation_set_elapsed(context->animation,
|
|
animation_get_duration(context->animation, true, true));
|
|
}
|
|
}
|
|
|
|
*context = (WindowTransitioningContext) {
|
|
.window_to = window_to,
|
|
.window_to_last_x = INT16_MAX,
|
|
.window_from = window_from,
|
|
.window_from_last_x = INT16_MAX,
|
|
.implementation = transition,
|
|
};
|
|
|
|
// TODO: PBL-17806 in future, store frames and config values as well
|
|
if (transition->create_animation) {
|
|
context->animation = transition->create_animation(context);
|
|
}
|
|
// If we haven't set an animation, either because create_animation() was NULL or it returned
|
|
// NULL, use g_window_transition_none_implementation
|
|
if (!context->animation) {
|
|
context->animation = g_window_transition_none_implementation.create_animation(context);
|
|
}
|
|
PBL_ASSERTN(context->animation);
|
|
|
|
// TODO: PBL-17645 setup container view
|
|
animation_schedule(context->animation);
|
|
// TODO: PBL-17645 cleanup container view in animation.stopped
|
|
}
|
|
|
|
static void prv_next_inserter(WindowStackItem *stack_item) {
|
|
WindowStack *window_stack = stack_item->window->parent_window_stack;
|
|
WindowStackItem *prev_item = (WindowStackItem *)window_stack->list_head;
|
|
// Insert after the found previous item:
|
|
list_insert_after(&prev_item->list_node, &stack_item->list_node);
|
|
}
|
|
|
|
static void prv_push_inserter(WindowStackItem *stack_item) {
|
|
WindowStack *window_stack = stack_item->window->parent_window_stack;
|
|
WindowStackItem *prev_item = (WindowStackItem *)(window_stack->list_head);
|
|
|
|
// Insert before the found previous item, provided that it exists.
|
|
ListNode *list_node_closest_to_head = &stack_item->list_node;
|
|
list_init(list_node_closest_to_head);
|
|
|
|
if (prev_item) {
|
|
ListNode *prev_list_node = &prev_item->list_node;
|
|
list_node_closest_to_head = list_insert_before(prev_list_node, list_node_closest_to_head);
|
|
}
|
|
|
|
// Update the reference to list head in case the window that was inserted is
|
|
// the new list head.
|
|
window_stack->list_head = list_get_head(list_node_closest_to_head);
|
|
}
|
|
|
|
static void prv_insert_with_function(WindowStack *window_stack_to, Window *window,
|
|
void(*inserter)(WindowStackItem *),
|
|
const WindowTransitionImplementation *transition_insert,
|
|
const WindowTransitionImplementation *transition_pop) {
|
|
PBL_ASSERTN(window_stack_to);
|
|
WindowStack *window_stack_from = window->parent_window_stack;
|
|
Window *window_from = window_manager_get_top_window();
|
|
|
|
// Assign the new stack for the window
|
|
window->parent_window_stack = window_stack_to;
|
|
|
|
if (!window_from) {
|
|
// We do not animate the first window, but instead let the compositor animate.
|
|
transition_insert = &g_window_transition_none_implementation;
|
|
transition_pop = &g_window_transition_none_implementation;
|
|
}
|
|
|
|
// This is a backwards compatibility hack for legacy2 applications. By default, watchface
|
|
// windows are not fullscreen until they're pushed onto the window stack, whereas legacy2
|
|
// watchfaces assume they are already full screen before being pushed.
|
|
bool is_app_window = window_manager_is_app_window(window);
|
|
if (is_app_window && sys_app_is_watchface()) {
|
|
window_set_fullscreen(window, true);
|
|
}
|
|
|
|
// If on the list of removed items for a window stack, remove from the removed items list
|
|
// as we want to add it back to the window stack.
|
|
if (window_stack_from) {
|
|
ListNode *node = list_find(window_stack_from->removed_list_head,
|
|
prv_filter_window_item_for_window, window);
|
|
if (node != NULL) {
|
|
list_remove(node, &window_stack_from->removed_list_head, NULL);
|
|
}
|
|
}
|
|
|
|
WindowStackItem *item = NULL;
|
|
if (window_stack_contains_window(window_stack_from, window)) {
|
|
// If the item is on the list of window items, then we remove it as we're going to
|
|
// re-insert it.
|
|
item = prv_find_window_stack_item_for_window(window_stack_from, window);
|
|
list_remove(&item->list_node, &window_stack_from->list_head, NULL);
|
|
} else {
|
|
// If the item is not yet on the window stack's list, then we allocate space for it
|
|
// on the heap.
|
|
item = applib_type_malloc(WindowStackItem);
|
|
|
|
*item = (WindowStackItem) {
|
|
.window = window,
|
|
.pop_transition_implementation = transition_pop,
|
|
};
|
|
}
|
|
|
|
inserter(item);
|
|
|
|
if (window_from == NULL || !window_manager_is_window_visible(window_from)) {
|
|
prv_transition_to(window_from, window, transition_insert);
|
|
}
|
|
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "(+) %s=%p <%s>", is_app_window ? "window" : "modal window",
|
|
window, window_get_debug_name(window));
|
|
}
|
|
|
|
static Window *prv_remove_item(WindowStackItem *pop_item,
|
|
const WindowTransitionImplementation *transition) {
|
|
PBL_ASSERTN(pop_item->window);
|
|
WindowStack *window_stack = pop_item->window->parent_window_stack;
|
|
// Do a transition from element that needs to be removed only if it was on the
|
|
// top of the visible window stack.
|
|
Window *window_from = NULL;
|
|
|
|
// If this window is currently transitioning and it is a modal window
|
|
if ((window_stack->transition_context.window_to == pop_item->window) &&
|
|
!window_manager_is_app_window(pop_item->window)) {
|
|
compositor_transition_cancel();
|
|
}
|
|
|
|
if (window_manager_is_window_visible(pop_item->window)) {
|
|
window_from = pop_item->window;
|
|
// If no transition is explicitly provided, use the one specified when pushed.
|
|
transition = transition ?: pop_item->pop_transition_implementation;
|
|
PBL_ASSERTN(transition);
|
|
} else {
|
|
// We don't intentionally clean up the .pop_transition of a previous element if
|
|
// a client actively messes with the window stack; they need to take care of this
|
|
// in any potential custom transition. The default transitions cannot handle this.
|
|
window_from = NULL;
|
|
}
|
|
|
|
// Remove the item from the window stack
|
|
list_remove(&pop_item->list_node, &window_stack->list_head, NULL);
|
|
|
|
WindowStackItem *stack_item = (WindowStackItem *)window_stack->list_head;
|
|
Window *window_to = stack_item ? stack_item->window : NULL;
|
|
|
|
// Add the removed item to the 'removed' list
|
|
window_stack->removed_list_head = list_insert_before(window_stack->removed_list_head,
|
|
&pop_item->list_node);
|
|
|
|
// Store the window here, as we're potentially free'ing the item later on.
|
|
Window *pop_item_window = pop_item->window;
|
|
bool is_app_window = window_manager_is_app_window(pop_item_window);
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "(-) %s=%p <%s>", is_app_window ? "window" : "modal window",
|
|
pop_item_window, window_get_debug_name(pop_item_window));
|
|
|
|
// Only animate if the window was previously at the top of the stack and there's a
|
|
// window we can transition to.
|
|
if (window_from && window_to) {
|
|
prv_transition_to(window_from, window_to, transition);
|
|
} else {
|
|
// We don't fire a transition in this case, but to ensure that all window-callbacks
|
|
// will still be called and the click handler is managed correctly, we call the
|
|
// appropriate helper functions manually with a fake transitioning context.
|
|
WindowTransitioningContext ctx = {
|
|
.window_from = pop_item_window,
|
|
};
|
|
|
|
window_transition_context_disappear(&ctx);
|
|
}
|
|
|
|
return pop_item_window;
|
|
}
|
|
|
|
// Public API
|
|
////////////////////////////////////
|
|
|
|
Window *window_stack_get_top_window(WindowStack *window_stack) {
|
|
WindowStackItem *item = (WindowStackItem *)NULL_SAFE_FIELD_ACCESS(window_stack, list_head, NULL);
|
|
return NULL_SAFE_FIELD_ACCESS(item, window, NULL);
|
|
}
|
|
|
|
void window_stack_push(WindowStack *window_stack, Window *window, bool animated) {
|
|
const WindowTransitionImplementation *transition_insert =
|
|
animated ? window_transition_get_default_push_implementation() :
|
|
&g_window_transition_none_implementation;
|
|
const WindowTransitionImplementation *transition_pop =
|
|
animated ? window_transition_get_default_pop_implementation() :
|
|
&g_window_transition_none_implementation;
|
|
|
|
window_stack_push_with_transition(window_stack, window, transition_insert, transition_pop);
|
|
}
|
|
|
|
void window_stack_push_with_transition(WindowStack *window_stack, Window *window,
|
|
const WindowTransitionImplementation *push_transition,
|
|
const WindowTransitionImplementation *pop_transition) {
|
|
if (window_stack->lock_push) {
|
|
return;
|
|
}
|
|
|
|
PBL_ASSERTN(push_transition && pop_transition);
|
|
prv_insert_with_function(window_stack, window, prv_push_inserter, push_transition,
|
|
pop_transition);
|
|
}
|
|
|
|
void window_stack_insert_next(WindowStack *window_stack, Window *window) {
|
|
if (window_stack->lock_push) {
|
|
return;
|
|
}
|
|
|
|
const WindowTransitionImplementation *transition_to = &g_window_transition_none_implementation;
|
|
const WindowTransitionImplementation *transition_pop =
|
|
window_transition_get_default_pop_implementation();
|
|
prv_insert_with_function(window_stack, window, prv_next_inserter, transition_to, transition_pop);
|
|
}
|
|
|
|
Window *window_stack_pop(WindowStack *window_stack, bool animated) {
|
|
// Transition NULL will default to the registered pop_transition to the stack item
|
|
const WindowTransitionImplementation *transition =
|
|
animated ? NULL : &g_window_transition_none_implementation;
|
|
return window_stack_pop_with_transition(window_stack, transition);
|
|
}
|
|
|
|
Window *window_stack_pop_with_transition(WindowStack *window_stack,
|
|
const WindowTransitionImplementation *transition) {
|
|
if (window_stack->list_head == NULL) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Nothing to pop.");
|
|
return NULL;
|
|
}
|
|
|
|
WindowStackItem *pop_item = (WindowStackItem *)window_stack->list_head;
|
|
Window *window = NULL;
|
|
if (pop_item) {
|
|
window = prv_remove_item(pop_item, transition);
|
|
}
|
|
return window;
|
|
}
|
|
|
|
void window_stack_pop_all(WindowStack *window_stack, const bool animated) {
|
|
if (window_stack->list_head == NULL) {
|
|
return;
|
|
}
|
|
|
|
WindowStackItem *top_item = (WindowStackItem *)window_stack->list_head;
|
|
WindowStackItem *next_item;
|
|
do {
|
|
// We manually remove each item to ensure that we do not call prv_remove_unloaded_windows
|
|
// until we have cleaned up the list. This prevents us from running into the issue where
|
|
// an unload handler pushes a window onto the stack, but that window is subsequently
|
|
// popped from the stack on another iteration of the while loop.
|
|
next_item = prv_next_item(top_item);
|
|
if (next_item == NULL) {
|
|
break;
|
|
}
|
|
list_remove(&next_item->list_node, &window_stack->list_head, NULL);
|
|
|
|
window_stack->removed_list_head = list_insert_before(window_stack->removed_list_head,
|
|
&next_item->list_node);
|
|
|
|
window_set_on_screen(next_item->window, false /* not new */, true /* call handlers */);
|
|
} while (true);
|
|
|
|
window_stack_pop(window_stack, animated);
|
|
}
|
|
|
|
bool window_stack_remove(Window *window, bool animated) {
|
|
if (window == NULL) {
|
|
return false;
|
|
}
|
|
|
|
WindowStackItem *item = prv_find_window_stack_item_for_window(window->parent_window_stack,
|
|
window);
|
|
if (item == NULL) {
|
|
return false;
|
|
}
|
|
|
|
const WindowTransitionImplementation *transition =
|
|
animated ? window_transition_get_default_pop_implementation() :
|
|
&g_window_transition_none_implementation;
|
|
|
|
window = prv_remove_item(item, transition);
|
|
return window != NULL;
|
|
}
|
|
|
|
bool window_stack_remove_with_transition(Window *window,
|
|
const WindowTransitionImplementation *transition) {
|
|
if (window == NULL) {
|
|
return false;
|
|
}
|
|
|
|
WindowStack *stack = window->parent_window_stack;
|
|
WindowStackItem *item = prv_find_window_stack_item_for_window(stack, window);
|
|
if (item == NULL) {
|
|
return false;
|
|
}
|
|
window = prv_remove_item(item, transition);
|
|
return window != NULL;
|
|
}
|
|
|
|
bool window_stack_contains_window(WindowStack *window_stack, Window *window) {
|
|
return (prv_find_window_stack_item_for_window(window_stack, window) != NULL);
|
|
}
|
|
|
|
uint32_t window_stack_count(WindowStack *window_stack) {
|
|
return list_count(window_stack->list_head);
|
|
}
|
|
|
|
// Stack status
|
|
////////////////////////////////////
|
|
|
|
void window_stack_lock_push(WindowStack *window_stack) {
|
|
window_stack->lock_push = true;
|
|
}
|
|
|
|
void window_stack_unlock_push(WindowStack *window_stack) {
|
|
window_stack->lock_push = false;
|
|
}
|
|
|
|
bool window_stack_is_animating(WindowStack *window_stack) {
|
|
return window_stack && animation_is_scheduled(window_stack->transition_context.animation);
|
|
}
|
|
|
|
bool window_stack_is_animating_with_fixed_status_bar(WindowStack *window_stack) {
|
|
WindowTransitioningContext *context = &window_stack->transition_context;
|
|
return window_stack_is_animating(window_stack) &&
|
|
window_has_status_bar(context->window_from) &&
|
|
window_has_status_bar(context->window_to);
|
|
}
|
|
|
|
// Transitioning Context Functions
|
|
////////////////////////////////////
|
|
|
|
// @note: This function prototypes exist in window_stack_private.h
|
|
|
|
bool window_transition_context_has_legacy_window_to(WindowStack *stack, Window *window) {
|
|
return (stack->transition_context.window_to == window) &&
|
|
process_manager_compiled_with_legacy2_sdk();
|
|
}
|
|
|
|
void window_transition_context_disappear(WindowTransitioningContext *context) {
|
|
Window *window_from = context->window_from;
|
|
if (!window_from || window_manager_is_window_visible(window_from)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "No windows to unload from stack.");
|
|
context->window_from = NULL;
|
|
return;
|
|
}
|
|
|
|
// Remove window reference from context to prevent future calls to it (e.g. "is dirty?")
|
|
context->window_from = NULL;
|
|
|
|
if (!window_manager_is_window_visible(window_from)) {
|
|
window_set_on_screen(window_from, false /* not new */, true /* call handlers */);
|
|
}
|
|
|
|
prv_unload_removed_windows(window_from->parent_window_stack);
|
|
}
|
|
|
|
void window_transition_context_appear(WindowTransitioningContext *context) {
|
|
Window *window_to = context->window_to;
|
|
if (!window_manager_is_window_visible(window_to)) {
|
|
return;
|
|
}
|
|
|
|
prv_set_new_window_on_screen(window_to);
|
|
|
|
ClickManager *click_manager = window_manager_get_window_click_manager(window_to);
|
|
if (window_manager_is_window_focused(window_to)) {
|
|
// TODO: PBL-37477 Window Stack directly calls click config
|
|
// Either this or app_click_config_setup_with_window should be calling
|
|
// window_setup_click_config_provider instead
|
|
app_click_config_setup_with_window(click_manager, window_to);
|
|
}
|
|
}
|
|
|
|
// Debug and Test Functions
|
|
/////////////////////////////
|
|
|
|
size_t window_stack_dump(WindowStack *stack, WindowStackDump **dump) {
|
|
*dump = NULL;
|
|
size_t count = window_stack_count(stack);
|
|
size_t idx = 0;
|
|
if (count > 0) {
|
|
*dump = kernel_calloc(count, sizeof(WindowStackDump));
|
|
if (*dump) {
|
|
WindowStackItem *item = (WindowStackItem *)stack->list_head;
|
|
while (item) {
|
|
(*dump)[idx++] = (WindowStackDump) {
|
|
.addr = item->window,
|
|
.name = window_get_debug_name(item->window),
|
|
};
|
|
item = (WindowStackItem *)list_get_next(&item->list_node);
|
|
}
|
|
PBL_ASSERTN(idx == count);
|
|
}
|
|
}
|
|
return count;
|
|
}
|