mirror of
https://github.com/google/pebble.git
synced 2025-07-07 15:20:32 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
582
src/fw/kernel/ui/modals/modal_manager.c
Normal file
582
src/fw/kernel/ui/modals/modal_manager.c
Normal file
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
* 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 "modal_manager.h"
|
||||
|
||||
#include "applib/ui/app_window_click_glue.h"
|
||||
#include "applib/ui/click_internal.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "applib/ui/window_stack.h"
|
||||
#include "applib/ui/window_stack_animation.h"
|
||||
#include "applib/ui/window_stack_private.h"
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "applib/graphics/graphics_private.h"
|
||||
#include "console/prompt.h"
|
||||
#include "kernel/panic.h"
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/common/compositor/compositor_transitions.h"
|
||||
#include "shell/normal/app_idle_timeout.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/profiler.h"
|
||||
#include "util/list.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "semphr.h"
|
||||
|
||||
typedef struct ModalContext {
|
||||
WindowStack window_stack;
|
||||
} ModalContext;
|
||||
|
||||
typedef struct UpdateContext {
|
||||
ModalPriority highest_idx;
|
||||
ModalProperty properties;
|
||||
} UpdateContext;
|
||||
|
||||
static void prv_update_modal_stacks(UpdateContext *context);
|
||||
|
||||
// Static State
|
||||
///////////////////
|
||||
static ModalContext s_modal_window_stacks[NumModalPriorities];
|
||||
|
||||
static ClickManager s_modal_window_click_manager;
|
||||
|
||||
static ModalPriority s_modal_min_priority = ModalPriorityMin;
|
||||
|
||||
// Used to help us keep track various modal properties in aggregate, such as existence.
|
||||
// Initialize the default to being equivalent to having no modals.
|
||||
static ModalProperty s_current_modal_properties = ModalPropertyDefault;
|
||||
|
||||
// Used to decide the compositor transition after a modal is already removed from the stack
|
||||
static ModalPriority s_last_highest_modal_priority = ModalPriorityInvalid;
|
||||
|
||||
// Private API
|
||||
////////////////////
|
||||
static bool prv_has_visible_window(ModalContext *context, void *unused) {
|
||||
const bool empty = (context->window_stack.list_head == NULL);
|
||||
const bool filtered_out = (context < &s_modal_window_stacks[s_modal_min_priority]);
|
||||
return (!empty && !filtered_out);
|
||||
}
|
||||
|
||||
static bool prv_has_transition_window(ModalContext *context) {
|
||||
Window *window = window_stack_get_top_window(&context->window_stack);
|
||||
return (window && (context > &s_modal_window_stacks[ModalPriorityDiscreet]));
|
||||
}
|
||||
|
||||
static bool prv_has_opaque_window(ModalContext *context) {
|
||||
Window *window = window_stack_get_top_window(&context->window_stack);
|
||||
return (window && !window->is_transparent);
|
||||
}
|
||||
|
||||
static bool prv_has_focusable_window(ModalContext *context) {
|
||||
Window *window = window_stack_get_top_window(&context->window_stack);
|
||||
return (window && !window->is_unfocusable);
|
||||
}
|
||||
|
||||
static bool prv_has_visible_focusable_window(ModalContext *context, void *unused) {
|
||||
return (prv_has_visible_window(context, NULL) && prv_has_focusable_window(context));
|
||||
}
|
||||
|
||||
static void prv_send_will_focus_event(bool in_focus) {
|
||||
static bool s_focus_lost = true;
|
||||
if (s_focus_lost == in_focus) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_focus_lost = in_focus;
|
||||
|
||||
PebbleEvent event = {
|
||||
.type = PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT,
|
||||
.app_focus = {
|
||||
.in_focus = in_focus,
|
||||
}
|
||||
};
|
||||
event_put(&event);
|
||||
}
|
||||
|
||||
// Public API
|
||||
////////////////////
|
||||
void modal_manager_init(void) {
|
||||
// Don't touch s_modal_window_stacks or s_modal_min_priority, it's valid for someone to have
|
||||
// disabled modals using modal_manager_set_enabled before we've initialized and we should
|
||||
// honour that setting.
|
||||
|
||||
click_manager_init(&s_modal_window_click_manager);
|
||||
}
|
||||
|
||||
void modal_manager_set_min_priority(ModalPriority priority) {
|
||||
s_modal_min_priority = priority;
|
||||
for (int i = 0; i < priority; i++) {
|
||||
window_stack_lock_push(&s_modal_window_stacks[i].window_stack);
|
||||
}
|
||||
for (int i = priority; i < NumModalPriorities; ++i) {
|
||||
window_stack_unlock_push(&s_modal_window_stacks[i].window_stack);
|
||||
}
|
||||
}
|
||||
|
||||
bool modal_manager_get_enabled(void) {
|
||||
return s_modal_min_priority < ModalPriorityMax;
|
||||
}
|
||||
|
||||
ClickManager *modal_manager_get_click_manager(void) {
|
||||
return &s_modal_window_click_manager;
|
||||
}
|
||||
|
||||
static WindowStack *prv_find_window_stack(ModalContextFilterCallback callback, void *data) {
|
||||
for (ModalPriority idx = NumModalPriorities - 1; idx >= ModalPriorityMin; idx--) {
|
||||
if (callback(&s_modal_window_stacks[idx], data)) {
|
||||
return &s_modal_window_stacks[idx].window_stack;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WindowStack *modal_manager_find_window_stack(ModalContextFilterCallback filter_cb, void *ctx) {
|
||||
return prv_find_window_stack(filter_cb, ctx);
|
||||
}
|
||||
|
||||
WindowStack *modal_manager_get_window_stack(ModalPriority priority) {
|
||||
PBL_ASSERTN((priority > ModalPriorityInvalid) && (priority < NumModalPriorities));
|
||||
ModalContext *context = &s_modal_window_stacks[priority];
|
||||
return &context->window_stack;
|
||||
}
|
||||
|
||||
Window *modal_manager_get_top_window(void) {
|
||||
WindowStack *stack = prv_find_window_stack(prv_has_visible_window, NULL);
|
||||
return window_stack_get_top_window(stack);
|
||||
}
|
||||
|
||||
static void prv_pop_stacks_in_range(ModalPriority low, ModalPriority high) {
|
||||
// Discreet modals are transparent and unfocusable, they are not meant to be popped when
|
||||
// requesting opaque focusable modals to pop.
|
||||
for (ModalPriority priority = MAX(low, ModalPriorityDiscreet + 1); priority <= high;
|
||||
priority++) {
|
||||
ModalContext *m_context = &s_modal_window_stacks[priority];
|
||||
window_stack_pop_all(&m_context->window_stack, true /* animated */);
|
||||
}
|
||||
}
|
||||
|
||||
void modal_manager_pop_all(void) {
|
||||
prv_pop_stacks_in_range(ModalPriorityMin, NumModalPriorities - 1);
|
||||
}
|
||||
|
||||
void modal_manager_pop_all_below_priority(ModalPriority priority) {
|
||||
prv_pop_stacks_in_range(ModalPriorityMin, priority - 1);
|
||||
}
|
||||
|
||||
static const CompositorTransition *prv_get_compositor_transition(bool modal_is_destination) {
|
||||
bool is_top_discreet;
|
||||
if (modal_is_destination) {
|
||||
Window *window =
|
||||
window_stack_get_top_window(&s_modal_window_stacks[ModalPriorityDiscreet].window_stack);
|
||||
is_top_discreet = (window && (window == modal_manager_get_top_window()));
|
||||
} else {
|
||||
is_top_discreet = (s_last_highest_modal_priority == ModalPriorityDiscreet);
|
||||
}
|
||||
return is_top_discreet ? NULL : compositor_modal_transition_to_modal_get(modal_is_destination);
|
||||
}
|
||||
|
||||
static void prv_handle_app_to_modal_transition_visible(void) {
|
||||
// The last event resulted in a modal window being pushed where we didn't have any before.
|
||||
// Start the animation!
|
||||
compositor_transition(prv_get_compositor_transition(true /* modal_is_destination */));
|
||||
}
|
||||
|
||||
static void prv_handle_modal_to_app_transition_visible(void) {
|
||||
compositor_transition(prv_get_compositor_transition(false /* modal_is_destination */));
|
||||
}
|
||||
|
||||
static void prv_handle_app_to_modal_transition_hidden_and_unfocused(void) {
|
||||
#if !RECOVERY_FW && !SHELL_SDK
|
||||
app_idle_timeout_pause();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_handle_modal_to_app_transition_hidden_and_unfocused(void) {
|
||||
#if !RECOVERY_FW && !SHELL_SDK
|
||||
app_idle_timeout_resume();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_handle_app_to_modal_transition_focus(void) {
|
||||
#if !RECOVERY_FW && (!SHELL_SDK || CAPABILITY_HAS_SDK_SHELL4)
|
||||
watchface_reset_click_manager();
|
||||
#endif
|
||||
|
||||
// Let the underlying window know it has lost focus if this is the first modal
|
||||
// window to show up.
|
||||
prv_send_will_focus_event(false /* in_focus */);
|
||||
}
|
||||
|
||||
static void prv_handle_modal_to_app_transition_focus(void) {
|
||||
// There are no more modal windows, so we need to cleanup the modal window state.
|
||||
click_manager_clear(modal_manager_get_click_manager());
|
||||
|
||||
prv_send_will_focus_event(true /* in_focus */);
|
||||
}
|
||||
|
||||
void modal_manager_event_loop_upkeep(void) {
|
||||
if (!modal_manager_get_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateContext update;
|
||||
prv_update_modal_stacks(&update);
|
||||
const ModalProperty last_properties = s_current_modal_properties;
|
||||
s_current_modal_properties = update.properties;
|
||||
|
||||
const bool is_modal_transitionable = (update.properties & ModalProperty_CompositorTransitions);
|
||||
const bool was_modal_transitionable = (last_properties & ModalProperty_CompositorTransitions);
|
||||
if (!was_modal_transitionable && is_modal_transitionable) {
|
||||
// We now have a window visible when we didn't have one before, start the transition.
|
||||
prv_handle_app_to_modal_transition_visible();
|
||||
} else if (was_modal_transitionable && !is_modal_transitionable) {
|
||||
// This event resulted in our last visible modal window being popped, let's transition away.
|
||||
prv_handle_modal_to_app_transition_visible();
|
||||
}
|
||||
|
||||
const bool is_modal_unfocused = (update.properties & ModalProperty_Unfocused);
|
||||
const bool was_modal_unfocused = (last_properties & ModalProperty_Unfocused);
|
||||
if (was_modal_unfocused && !is_modal_unfocused) {
|
||||
// We now have a modal window focused when we didn't have one before, start the transition.
|
||||
prv_handle_app_to_modal_transition_focus();
|
||||
} else if (!was_modal_unfocused && is_modal_unfocused) {
|
||||
// This event resulted in our last focusable modal window being popped, let's transition away.
|
||||
prv_handle_modal_to_app_transition_focus();
|
||||
}
|
||||
|
||||
const bool is_app_hidden_and_unfocused =
|
||||
(!(update.properties & ModalProperty_Transparent) && !is_modal_unfocused);
|
||||
const bool was_app_hidden_and_unfocused =
|
||||
(!(last_properties & ModalProperty_Transparent) && !was_modal_unfocused);
|
||||
if (!was_app_hidden_and_unfocused && is_app_hidden_and_unfocused) {
|
||||
// The app is now obstructed by an opaque modal and lost focus to a modal, idle.
|
||||
prv_handle_app_to_modal_transition_hidden_and_unfocused();
|
||||
} else if (was_app_hidden_and_unfocused && !is_app_hidden_and_unfocused) {
|
||||
// The app now either is obstructed only by transparent modals or gained focus, resume.
|
||||
prv_handle_modal_to_app_transition_hidden_and_unfocused();
|
||||
}
|
||||
|
||||
// We have modal windows and we should render them, either because they asked to or because
|
||||
// they recently became the top window in their respective modal stacks and haven't noticed yet.
|
||||
// See the handling for off screen windows in prv_render_modal_stack.
|
||||
if (update.properties & ModalProperty_RenderRequested) {
|
||||
compositor_modal_render_ready();
|
||||
}
|
||||
|
||||
s_last_highest_modal_priority = update.highest_idx;
|
||||
}
|
||||
|
||||
typedef struct IterContext {
|
||||
Window *current_top_window;
|
||||
ModalPriority current_idx;
|
||||
ModalPriority first_visible_idx;
|
||||
ModalPriority first_transition_idx;
|
||||
ModalPriority first_focus_idx;
|
||||
ModalPriority first_opaque_idx;
|
||||
} IterContext;
|
||||
|
||||
typedef bool (*ModalContextIterCallback)(ModalContext *modal, IterContext *iter, void *data);
|
||||
|
||||
static void prv_each_modal_stack(ModalContextIterCallback callback, void *data) {
|
||||
IterContext iter = {
|
||||
.first_visible_idx = ModalPriorityInvalid,
|
||||
.first_transition_idx = ModalPriorityInvalid,
|
||||
.first_focus_idx = ModalPriorityInvalid,
|
||||
.first_opaque_idx = ModalPriorityInvalid,
|
||||
};
|
||||
for (ModalPriority idx = NumModalPriorities - 1; idx >= ModalPriorityMin; idx--) {
|
||||
ModalContext *context = &s_modal_window_stacks[idx];
|
||||
if (!prv_has_visible_window(context, NULL)) {
|
||||
continue;
|
||||
} else if (iter.first_visible_idx == ModalPriorityInvalid) {
|
||||
iter.first_visible_idx = idx;
|
||||
}
|
||||
if (prv_has_transition_window(context) && (iter.first_transition_idx == ModalPriorityInvalid)) {
|
||||
iter.first_transition_idx = idx;
|
||||
}
|
||||
if (prv_has_focusable_window(context) && (iter.first_focus_idx == ModalPriorityInvalid)) {
|
||||
iter.first_focus_idx = idx;
|
||||
}
|
||||
if (prv_has_opaque_window(context) && (iter.first_opaque_idx == ModalPriorityInvalid)) {
|
||||
iter.first_opaque_idx = idx;
|
||||
}
|
||||
}
|
||||
if (iter.first_visible_idx != ModalPriorityInvalid) {
|
||||
for (ModalPriority idx = ModalPriorityMin; idx < NumModalPriorities; idx++) {
|
||||
ModalContext *modal = &s_modal_window_stacks[idx];
|
||||
WindowStack *stack = &modal->window_stack;
|
||||
iter.current_idx = idx;
|
||||
iter.current_top_window = window_stack_get_top_window(stack);
|
||||
if (!iter.current_top_window) {
|
||||
continue;
|
||||
}
|
||||
const bool should_continue = callback(modal, &iter, data);
|
||||
if (!should_continue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_update_modal_stack_callback(ModalContext *modal, IterContext *iter, void *data) {
|
||||
UpdateContext *ctx = data;
|
||||
Window *window = iter->current_top_window;
|
||||
|
||||
// Handle window state changes
|
||||
const bool is_visible = (iter->current_idx >= iter->first_opaque_idx);
|
||||
if (!window->on_screen && is_visible) {
|
||||
// We've been exposed by a higher priority modal window stack emptying out, become on screen
|
||||
// now.
|
||||
window_set_on_screen(window, true /* new on screen */, true /* call handlers */);
|
||||
}
|
||||
|
||||
// Setting on-screen can configure the click, but if this is a window below a transparent
|
||||
// window that just disappeared, it was already on screen and may need its click configured.
|
||||
const bool is_focused = (iter->current_idx == iter->first_focus_idx);
|
||||
if (!window->is_click_configured && is_focused) {
|
||||
// Input is now exposed by a higher priority modal window stack emptying out, gain input
|
||||
window_setup_click_config_provider(window);
|
||||
} else if (window->is_click_configured && !is_focused) {
|
||||
// A different modal window now has focus
|
||||
window->is_click_configured = false;
|
||||
}
|
||||
|
||||
// Set the last highest visible modal priority
|
||||
if (is_visible) {
|
||||
ctx->highest_idx = iter->current_idx;
|
||||
}
|
||||
|
||||
// Update properties based on state changes
|
||||
// If this callback was called, there exists a modal
|
||||
ctx->properties |= ModalProperty_Exists;
|
||||
|
||||
if (iter->current_idx > ModalPriorityDiscreet) {
|
||||
// There is a modal window that has a compositor transition
|
||||
ctx->properties |= ModalProperty_CompositorTransitions;
|
||||
}
|
||||
|
||||
if (is_visible) {
|
||||
if (!window->is_transparent) {
|
||||
// There is a visible opaque window, remove the transparent property
|
||||
ctx->properties &= ~ModalProperty_Transparent;
|
||||
}
|
||||
if (window->is_render_scheduled) {
|
||||
// There is a visible window that will render, add the render requested property
|
||||
ctx->properties |= ModalProperty_RenderRequested;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_focused) {
|
||||
// There is a modal with focus, remove the unfocused property
|
||||
ctx->properties &= ~ModalProperty_Unfocused;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_render_modal_stack_callback(ModalContext *modal, IterContext *iter, void *data) {
|
||||
if (iter->current_idx < iter->first_opaque_idx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GContext *ctx = data;
|
||||
WindowStack *stack = &modal->window_stack;
|
||||
Window *window = iter->current_top_window;
|
||||
|
||||
if (window_stack_is_animating(stack) &&
|
||||
stack->transition_context.implementation &&
|
||||
stack->transition_context.implementation->render) {
|
||||
// a lot of safety guards to make sure the transition can do render by its own
|
||||
WindowTransitioningContext *const transition_context = &stack->transition_context;
|
||||
transition_context->implementation->render(transition_context, ctx);
|
||||
} else {
|
||||
PROFILER_NODE_START(render_modal);
|
||||
window_render(window, ctx);
|
||||
PROFILER_NODE_STOP(render_modal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_update_modal_stacks(UpdateContext *context) {
|
||||
context->highest_idx = ModalPriorityInvalid;
|
||||
context->properties = ModalPropertyDefault;
|
||||
prv_each_modal_stack(prv_update_modal_stack_callback, context);
|
||||
}
|
||||
|
||||
|
||||
ModalProperty modal_manager_get_properties(void) {
|
||||
return modal_manager_get_enabled() ? s_current_modal_properties : ModalPropertyDefault;
|
||||
}
|
||||
|
||||
void modal_manager_render(GContext *ctx) {
|
||||
PBL_ASSERTN(ctx);
|
||||
prv_each_modal_stack(prv_render_modal_stack_callback, ctx);
|
||||
}
|
||||
|
||||
typedef struct VisibleContext {
|
||||
Window *window;
|
||||
bool visible;
|
||||
} VisibleContext;
|
||||
|
||||
static bool prv_is_window_visible_callback(ModalContext *modal, IterContext *iter, void *data) {
|
||||
if (iter->current_idx < iter->first_opaque_idx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
VisibleContext *ctx = data;
|
||||
ctx->visible = (ctx->window == iter->current_top_window);
|
||||
return !ctx->visible;
|
||||
}
|
||||
|
||||
bool modal_manager_is_window_visible(Window *window) {
|
||||
VisibleContext context = { .window = window };
|
||||
prv_each_modal_stack(prv_is_window_visible_callback, &context);
|
||||
return context.visible;
|
||||
}
|
||||
|
||||
typedef struct FocusedContext {
|
||||
Window *window;
|
||||
bool focused;
|
||||
} FocusedContext;
|
||||
|
||||
static bool prv_is_window_focused_callback(ModalContext *modal, IterContext *iter, void *data) {
|
||||
FocusedContext *ctx = data;
|
||||
ctx->focused = ((iter->current_top_window == ctx->window) &&
|
||||
(iter->current_idx == iter->first_focus_idx));
|
||||
return !ctx->focused;
|
||||
}
|
||||
|
||||
bool modal_manager_is_window_focused(Window *window) {
|
||||
FocusedContext context = { .window = window };
|
||||
prv_each_modal_stack(prv_is_window_focused_callback, &context);
|
||||
return context.focused;
|
||||
}
|
||||
|
||||
static Window *prv_get_visible_focused_window(void) {
|
||||
WindowStack *stack = prv_find_window_stack(prv_has_visible_focusable_window, NULL);
|
||||
return window_stack_get_top_window(stack);
|
||||
}
|
||||
|
||||
void modal_manager_handle_button_event(PebbleEvent *event) {
|
||||
ClickManager *click_manager = modal_manager_get_click_manager();
|
||||
switch (event->type) {
|
||||
case PEBBLE_BUTTON_DOWN_EVENT: {
|
||||
// If we get a button event, it must also be for the top modal window.
|
||||
Window *window = prv_get_visible_focused_window();
|
||||
// Ensure that this function isn't being called when a modal window
|
||||
// is not present.
|
||||
PBL_ASSERTN(window);
|
||||
ButtonId id = event->button.button_id;
|
||||
if (id == BUTTON_ID_BACK && !window->overrides_back_button) {
|
||||
window_stack_remove(window, true /* animated */);
|
||||
} else {
|
||||
click_recognizer_handle_button_down(&click_manager->recognizers[id]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PEBBLE_BUTTON_UP_EVENT: {
|
||||
ButtonId id = event->button.button_id;
|
||||
click_recognizer_handle_button_up(&click_manager->recognizers[id]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
PBL_CROAK("Invalid event type: %u", event->type);
|
||||
}
|
||||
}
|
||||
|
||||
void modal_window_push(Window *window, ModalPriority priority, bool animated) {
|
||||
// Note: We do not have to adjust the `animated` argument to consider whether
|
||||
// this window is higher priority than the current visible window stack, as
|
||||
// this is taken care of by the transition context handlers in `window_stack.c`
|
||||
window_stack_push(modal_manager_get_window_stack(priority), window, animated);
|
||||
}
|
||||
|
||||
// Commands
|
||||
////////////////////////////
|
||||
|
||||
typedef struct WindowStackInfoContext {
|
||||
SemaphoreHandle_t interlock;
|
||||
WindowStackDump *dumps[NumModalPriorities];
|
||||
size_t counts[NumModalPriorities];
|
||||
bool disabled;
|
||||
} WindowStackInfoContext;
|
||||
|
||||
static void prv_modal_window_stack_info_cb(void *ctx) {
|
||||
WindowStackInfoContext *info = ctx;
|
||||
if (modal_manager_get_enabled()) {
|
||||
for (ModalPriority priority = 0;
|
||||
priority < NumModalPriorities;
|
||||
++priority) {
|
||||
WindowStack *window_stack = modal_manager_get_window_stack(priority);
|
||||
info->counts[priority] = window_stack_dump(window_stack,
|
||||
&info->dumps[priority]);
|
||||
}
|
||||
} else {
|
||||
info->disabled = true;
|
||||
}
|
||||
xSemaphoreGive(info->interlock);
|
||||
}
|
||||
|
||||
void command_modal_stack_info(void) {
|
||||
WindowStackInfoContext info = {
|
||||
.interlock = xSemaphoreCreateBinary(),
|
||||
};
|
||||
if (!info.interlock) {
|
||||
prompt_send_response("Couldn't allocate semaphore for modal stack");
|
||||
return;
|
||||
}
|
||||
|
||||
launcher_task_add_callback(prv_modal_window_stack_info_cb, &info);
|
||||
xSemaphoreTake(info.interlock, portMAX_DELAY);
|
||||
vSemaphoreDelete(info.interlock);
|
||||
|
||||
prompt_send_response("Modal Stack, top to bottom:");
|
||||
|
||||
char buffer[128];
|
||||
for (ModalPriority priority = NumModalPriorities - 1;
|
||||
priority > ModalPriorityInvalid;
|
||||
--priority) {
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "Priority: %d (%zu)",
|
||||
priority, info.counts[priority]);
|
||||
if (info.counts[priority] > 0 && !info.dumps[priority]) {
|
||||
prompt_send_response("Couldn't allocate buffers for modal stack data");
|
||||
} else {
|
||||
for (size_t i = 0; i < info.counts[priority]; ++i) {
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "window %p <%s>",
|
||||
info.dumps[priority][i].addr,
|
||||
info.dumps[priority][i].name);
|
||||
}
|
||||
}
|
||||
kernel_free(info.dumps[priority]);
|
||||
}
|
||||
}
|
||||
|
||||
void modal_manager_reset(void) {
|
||||
for (ModalPriority idx = ModalPriorityInvalid + 1; idx < NumModalPriorities; idx++) {
|
||||
memset(&s_modal_window_stacks[idx], 0, sizeof(ModalContext));
|
||||
}
|
||||
|
||||
s_modal_min_priority = ModalPriorityDiscreet;
|
||||
|
||||
modal_manager_init();
|
||||
}
|
176
src/fw/kernel/ui/modals/modal_manager.h
Normal file
176
src/fw/kernel/ui/modals/modal_manager.h
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 "modal_manager_private.h"
|
||||
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "applib/ui/click_internal.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "applib/ui/window_stack_animation.h"
|
||||
#include "applib/ui/window_stack_private.h"
|
||||
#include "kernel/events.h"
|
||||
|
||||
struct ModalContext;
|
||||
typedef struct ModalContext ModalContext;
|
||||
|
||||
typedef bool (*ModalContextFilterCallback)(ModalContext *context, void *data);
|
||||
|
||||
//! This defines the priorities for the various modals. The order in which they are
|
||||
//! defined is specified by the interruption policy and determine who is able to interrupt
|
||||
//! whom. If a modal has a higher priority than another modal, it is able to interrupt
|
||||
//! that modal. Before adding anything to here, consider how it would affect the interrupt
|
||||
//! policy.
|
||||
//! Interruption Policy:
|
||||
//! https://docs.google.com/presentation/d/1xIjOR9kh4jcBYonzRstdtFQW0ZNMoFZZwYOUMN2JHNI
|
||||
typedef enum ModalPriority {
|
||||
//! Invalid priority for a modal.
|
||||
ModalPriorityInvalid = -1,
|
||||
//! Min priority, all modals are below or equal to this priority.
|
||||
ModalPriorityMin = 0,
|
||||
//! Discreet mode for watchface overlay information such as Timeline Peek.
|
||||
//! This priority should be used for modals that meant to display above the watchface without
|
||||
//! completely obstructing the watchface. For this reason, Discreet modal windows do not have
|
||||
//! compositor transitions because partial obstruction requires notifying the app of the
|
||||
//! unobstructed regions which only the modal window is able to derive.
|
||||
ModalPriorityDiscreet = ModalPriorityMin,
|
||||
//! Priority for generic one-off windows such as the battery charging window.
|
||||
//! This priority should be used for any modals that don't necessarily care how
|
||||
//! they are shown, just that they are shown.
|
||||
ModalPriorityGeneric,
|
||||
//! Priority used for displaying the phone ui after a phone call has been answered.
|
||||
//! Note: Notifications should always be able to subvert this.
|
||||
ModalPriorityPhone,
|
||||
//! Priority used for displaying notifications.
|
||||
ModalPriorityNotification,
|
||||
//! Priority used for displaying alerts. Alerts are important, but shouldn't
|
||||
//! impact the user of the watch.
|
||||
ModalPriorityAlert,
|
||||
//! Priority used for displaying the voice screen for recording. Ensure this one is
|
||||
//! always kept second to last.
|
||||
ModalPriorityVoice,
|
||||
//! Priority for displaying time-sensitive/critical windows which provide information
|
||||
//! on things that may affect the user's watch experience. However, these should never
|
||||
//! prevent an alarm from displaying.
|
||||
ModalPriorityCritical,
|
||||
//! Priority used for displaying wake up events such as alarms.
|
||||
ModalPriorityAlarm,
|
||||
//! Max priority, all modals are below this priority
|
||||
ModalPriorityMax,
|
||||
NumModalPriorities = ModalPriorityMax,
|
||||
} ModalPriority;
|
||||
|
||||
typedef enum ModalProperty {
|
||||
ModalProperty_None = 0,
|
||||
//! Whether there exists a modal in a modal stack on screen.
|
||||
ModalProperty_Exists = (1 << 0),
|
||||
//! Whether there exists a modal in a modal stack on screen that uses compositor transitions.
|
||||
ModalProperty_CompositorTransitions = (1 << 1),
|
||||
//! Whether there exists a modal that requested to render.
|
||||
ModalProperty_RenderRequested = (1 << 2),
|
||||
//! Whether all modal stacks are transparent. Having no modal is treated as transparent.
|
||||
ModalProperty_Transparent = (1 << 3),
|
||||
//! Whether all modal stacks pass input through. Having no modal is treated as unfocused.
|
||||
ModalProperty_Unfocused = (1 << 4),
|
||||
//! The default properties equivalent to there being no modal windows.
|
||||
ModalPropertyDefault = (ModalProperty_Transparent | ModalProperty_Unfocused),
|
||||
} ModalProperty;
|
||||
|
||||
//! Initializes the modal window state. This should be called before any
|
||||
//! Modal applications attempt to push windows.
|
||||
void modal_manager_init(void);
|
||||
|
||||
//! Sets whether modal windows are enabled.
|
||||
//! @param enabled Boolean indicating whether enabled or disabled
|
||||
//! Note that this is usable before modal_manager_init is called and modal_manager_init will not
|
||||
//! reset this state.
|
||||
void modal_manager_set_min_priority(ModalPriority priority);
|
||||
|
||||
//! Gets whether modal windows are enabled.
|
||||
// @returns boolean indicating if modals are enabled
|
||||
bool modal_manager_get_enabled(void);
|
||||
|
||||
//! Returns the ClickManager for the modal windows.
|
||||
//! @returns pointer to a \ref ClickManager
|
||||
ClickManager *modal_manager_get_click_manager(void);
|
||||
|
||||
//! Returns the first \ref WindowStack to pass the given filter callback.
|
||||
//! Iterates down from the highest priority to the lowest priority.
|
||||
//! @param filter_cb The \ref ModalContextFilterCallback
|
||||
//! @param context Context to pass to the callback
|
||||
//! @returns pointer to a \ref WindowStack
|
||||
WindowStack *modal_manager_find_window_stack(ModalContextFilterCallback filter_cb, void *ctx);
|
||||
|
||||
//! Returns the stack with the given window priority.
|
||||
//! If the passed priority is invalid, raises an assertion.
|
||||
//! @param priority The \ref ModalPriority of the desired \ref WindowStack
|
||||
//! @returns pointer to a \ref WindowStack
|
||||
WindowStack *modal_manager_get_window_stack(ModalPriority priority);
|
||||
|
||||
//! Returns the \ref Window of the current visible stack if there is one,
|
||||
//! otherwise NULL.
|
||||
//! @returns Pointer to a \ref Window
|
||||
Window *modal_manager_get_top_window(void);
|
||||
|
||||
//! Handles a button press event for the Modal Window. Raises an
|
||||
//! assertion if the event is not a click event.
|
||||
//! @param event The \ref PebbleEvent to handle.
|
||||
void modal_manager_handle_button_event(PebbleEvent *event);
|
||||
|
||||
//! Pops all windows from all modal stacks.
|
||||
void modal_manager_pop_all(void);
|
||||
|
||||
//! Pops all windows from modal stacks with priorities less than the given priority
|
||||
//! @param the max priorirty stack to pop all windows from
|
||||
void modal_manager_pop_all_below_priority(ModalPriority priority);
|
||||
|
||||
//! Called from the kernel event loop between events to handle any changes that have been made
|
||||
//! to the modal window stacks.
|
||||
void modal_manager_event_loop_upkeep(void);
|
||||
|
||||
//! Enumerates through the modal stacks and returns the flattened properties of all stacks
|
||||
//! combined. For example, if all modals are transparent, the Transparent property will be
|
||||
//! returned. Flattened meaning the entire stack is considered in aggregate. For example, if any
|
||||
//! one modal is opaque, the Transparent property won't be returned.
|
||||
ModalProperty modal_manager_get_properties(void);
|
||||
|
||||
//! Renders the highest priority top opaque window and all windows with higher priority.
|
||||
//! @param ctx The \ref GContext in which to render.
|
||||
//! @return Modal properties such as whether the modals are transparent or unfocusable
|
||||
void modal_manager_render(GContext *ctx);
|
||||
|
||||
//! Determines whether the given modal window is visible. Use window_manager_is_window_visible if
|
||||
//! both app windows and modal windows need to be considered.
|
||||
//! @param window The modal window to determine whether it is visible.
|
||||
//! @return true if the modal window is visible, false otherwise
|
||||
bool modal_manager_is_window_visible(Window *window);
|
||||
|
||||
//! Determines whether the given modal window is focused. Use window_manager_is_window_focused if
|
||||
//! both app windows and modal windows need to be considered.
|
||||
//! @param window The modal window to determine whether it is focused.
|
||||
//! @return true if the modal window is focused, false otherwise
|
||||
bool modal_manager_is_window_focused(Window *window);
|
||||
|
||||
//! Wrapper to call \ref window_stack_push() with the appropriate stack for
|
||||
//! the given \ref ModalPriority
|
||||
//! @param window The window to push onto the stack
|
||||
//! @param priority The priority of the window stack to push to
|
||||
//! @param animated `True` for animated, otherwise `False`
|
||||
void modal_window_push(Window *window, ModalPriority priority, bool animated);
|
||||
|
||||
//! Reset the modal manager state. Useful for unit testing.
|
||||
void modal_manager_reset(void);
|
18
src/fw/kernel/ui/modals/modal_manager_private.h
Normal file
18
src/fw/kernel/ui/modals/modal_manager_private.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue