Ken Van Hoeylandt b9a96ca26d
UX improvements (#686)
- Introduce "side" mode for system bar, versus classic statusbar on top with launcher bar below
- Automatic system bar mode detection with override by Kconfig/device.properties and secondary override by user via Appereance settings app
- Open a quick-settings menu from the status bar for available Wi-Fi, Bluetooth, GPS, and power controls.
- Apps can display overlays with backdrop or Escape-key dismissal.
- Added focus-container support and improved keyboard navigation, including Fn+Enter on Cardputer devices.
- Power-off controls are available on supported device cards.
- Updated status-bar layouts, simulator display dimensions, and toolbar navigation styling.
- I²C scanning now supports selecting individual buses to rescan.
- SD-card mount status is no longer shown in the status bar.
2026-10-11 00:32:27 +02:00

188 lines
9.4 KiB
C

// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/instance.h>
#include <lvgl.h>
#include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uint32_t WindowId;
enum WindowState {
/** id is the current topmost window and has live widgets. */
WINDOW_STATE_GRANTED,
/** id is not currently topmost - either buried under a newer window (its widgets don't
* exist right now, but it may resurface and get rebuilt if everything above it is removed)
* or it no longer exists at all (removed). */
WINDOW_STATE_REVOKED,
};
/**
* Called once by window_manager_start(), given the real root widget (a raw, full-size
* container created directly under the default display's active screen). May add extra chrome
* (e.g. a statusbar) as children of @a root_widget.
* @param[in] root_widget the real root widget; owned by this module, deleted automatically
* (along with everything added under it) by window_manager_stop()
* @return the widget windows should actually be placed into - @a root_widget itself, or a
* child of it. Returning NULL falls back to @a root_widget.
* @warning Called on the LVGL task with the LVGL lock already held.
* @warning Also called with window-manager's internal lifecycle_mutex held (non-recursive) -
* do NOT call window_manager_start()/window_manager_stop()/window_manager_create()/
* window_manager_remove() or any other window-manager API from this callback, that would
* deadlock.
*/
typedef lv_obj_t* (*WindowManagerScreenInitFn)(lv_obj_t* root_widget);
/**
* Configures the screen-init callback window_manager_start() invokes to build the root/content
* widgets. Pass NULL to restore the default (no chrome - the raw root widget is used directly).
* @warning Must be called before window_manager_start(); has no effect once already started.
*/
void window_manager_configure(WindowManagerScreenInitFn screen_init);
/**
* Creates the root widget (under the default display's active screen) and, via the configured
* screen-init callback, whatever chrome/content widget it wants around it. Idempotent - a
* second call while already started is a no-op.
* @retval ERROR_RESOURCE no default display is active (lv_screen_active() returned NULL)
* @retval ERROR_NONE on success (including if already started)
*/
error_t window_manager_start(void);
/**
* Deletes the root widget created by window_manager_start() (and everything under it - any
* chrome plus whatever the topmost window had drawn), removing it from the display, and drops
* every tracked window. Idempotent - a second call while already stopped is a no-op.
*/
error_t window_manager_stop(void);
/**
* Called to populate a window's widgets: once by window_manager_create() when the window is
* first created, and again later by window_manager_remove() if this window resurfaces as the
* new topmost after whatever was above it is removed. Only the current topmost window ever has
* live widgets - everything below it in the stack exists as tracked state only.
* @param[in] root a fresh, full-size container created directly under the content widget for
* this window; deleted automatically once this window stops being topmost
* @param[in] user_data whatever was passed to window_manager_create() for this window
* @warning Called on the LVGL task with the LVGL lock already held.
* @warning May run on a different kernel thread than the one that called window_manager_create()
* for this window - the rebuild-on-remove path runs on whichever thread called
* window_manager_remove() for the window that used to be on top (e.g. a dialog's own thread as
* it closes). Do NOT rely on thread_local state set by this window's own app thread; use
* @a user_data instead.
* @warning Also called with window-manager's internal lifecycle_mutex held (non-recursive) -
* do NOT call window_manager_start()/window_manager_stop()/window_manager_create()/
* window_manager_remove() or any other window-manager API from this callback, that would
* deadlock.
*/
typedef void (*WindowCreateWidgetsFn)(lv_obj_t* root, void* user_data);
/**
* Called whenever this window's live widgets are about to be deleted while the window record
* itself survives - i.e. its owning app is still running and may see this window resurface
* later.
* Always paired 1:1 with a prior @a create_widgets call that actually ran.
* Never called for a window whose widgets were never built.
* @param[in] user_data whatever was passed to window_manager_create_ext() for this window
* @warning Called on the LVGL task with the LVGL lock already held (same as
* WindowCreateWidgetsFn) - do NOT call window_manager_start()/stop()/create()/create_ext()/
* remove() from this callback, that would deadlock.
* @warning Do NOT acquire any other lock from this callback either. It runs while a thread
* elsewhere may already be holding that lock and blocked waiting for the LVGL lock this
* callback is running under - acquiring it here would deadlock against that thread. Only touch
* memory that needs no other synchronization, e.g. null out this window's own cached
* lv_obj_t* pointers (they're only ever otherwise touched under the LVGL lock anyway) so a
* stale update arriving after this call can detect the window is gone instead of using freed
* widgets.
*/
typedef void (*WindowDestroyWidgetsFn)(void* user_data);
/**
* Creates a new window on top of the stack (last created = topmost). Deletes the previously
* topmost window's widgets (if any) and builds this window's widgets immediately via
* @a create_widgets - only the topmost window ever has live widgets.
* @param[in] app_instance_id the application instance this window belongs to, should not be 0
* @param[in] user_data opaque; passed back to @a create_widgets on every call, including a
* later rebuild triggered by window_manager_remove() - see its @warning about which thread that
* can run on. Typically the calling app's own Context*.
* @return the new window's id, or 0 if window_manager_start() hasn't been called
*/
WindowId window_manager_create(AppInstanceId app_instance_id, WindowCreateWidgetsFn create_widgets, void* user_data);
/**
* Same as window_manager_create(), but also registers @a destroy_widgets - see its docs.
* @param[in] destroy_widgets may be NULL to opt out (equivalent to window_manager_create())
*/
WindowId window_manager_create_ext(AppInstanceId app_instance_id, WindowCreateWidgetsFn create_widgets, WindowDestroyWidgetsFn destroy_widgets, void* user_data);
/**
* Removes a window, wherever it is in the stack - not necessarily the topmost one. If it was
* topmost, its widgets are deleted and whichever window is now on top (if any) has its
* create_widgets called again to rebuild its widgets.
*/
void window_manager_remove(WindowId id);
/** @return the current state of @a id; WINDOW_STATE_REVOKED if @a id is buried or doesn't exist. */
enum WindowState window_manager_get_state(WindowId id);
/**
* Blocks the calling task until @a id's state changes away from WINDOW_STATE_GRANTED, or
* @a timeout elapses. Returns immediately with WINDOW_STATE_REVOKED if @a id isn't currently
* topmost (nothing to wait for).
* @warning At most one task may have an outstanding await() call per window at a time (each
* window tracks a single waiter). A second concurrent call for the same @a id asserts. Calls
* for different windows (e.g. from different app tasks in a stacked window manager) don't
* conflict with each other.
* @return the state after waking (or immediately, if there was nothing to wait for)
*/
enum WindowState window_manager_await_state_change(WindowId id, TickType_t timeout);
/**
* Windows live in layers. Only the active layer receives input: its widgets form the group that
* keypad indevs are attached to, and a shown overlay covers the app layer for pointer input.
*/
enum WindowLayer {
/** The chrome created by the screen-init callback and the app windows. */
WINDOW_LAYER_APP,
/** A single overlay on top of the app layer. */
WINDOW_LAYER_OVERLAY,
};
/**
* Shows an overlay on top of the app layer and makes it the active layer. A shown overlay is
* replaced. The overlay is a full-size backdrop: clicking the backdrop itself (outside of the
* widgets created by @a create_widgets) or pressing ESC on a focused overlay widget hides it.
* App windows that are created or rebuilt while the overlay is shown stay in the app layer.
* When a pointer device exists, the widget that is focused first doesn't show as selected.
* @param[in] create_widgets called once to populate the backdrop, with the LVGL lock held
* @param[in] destroy_widgets may be NULL, called once when the overlay is hidden, with the LVGL
* lock held
* @param[in] user_data passed to both callbacks
* @warning Only uses the LVGL lock, so it may be called from LVGL event callbacks.
* @retval ERROR_INVALID_STATE the window manager isn't started
* @retval ERROR_NONE on success
*/
error_t window_manager_overlay_show(WindowCreateWidgetsFn create_widgets, WindowDestroyWidgetsFn destroy_widgets, void* user_data);
/**
* Hides the overlay, if any, and makes the app layer the active layer.
* @warning Only uses the LVGL lock, so it may be called from LVGL event callbacks, including
* the events of the overlay's own widgets.
*/
void window_manager_overlay_hide(void);
/** @return the layer that currently receives input */
enum WindowLayer window_manager_get_active_layer(void);
#ifdef __cplusplus
}
#endif