#include #include #include #include #include "liquid.h" #include "nokia.h" static const char *TAG = "Liquid"; extern struct Scene *NewScene_Root(void); static struct SceneEvent Liquid_initTopScene(struct Liquid *container); static bool processReturnValue(struct Liquid *container, struct SceneEvent result); struct RunningScene { struct Scene *scene; SLIST_ENTRY(RunningScene) next; } cmd_item_t; struct Liquid { SLIST_HEAD(, RunningScene) stack; // stack with the topmost scene as the first element / head }; static struct SceneEvent Default_onChildReturn(struct Scene *scene, uint32_t tag, int32_t status, void *data) { if (data) free(data); return SceneEvent_Repaint(); } static void addChild(struct Liquid *container, struct Scene *child) { struct RunningScene *elm = calloc(1, sizeof(struct RunningScene)); if (!child->onChildReturn) child->onChildReturn = Default_onChildReturn; elm->scene = child; SLIST_INSERT_HEAD(&container->stack, elm, next); } struct Liquid *Liquid_start(void) { struct Liquid *container = calloc(1, sizeof(struct Liquid)); addChild(container, NewScene_Root()); struct SceneEvent result = Liquid_initTopScene(container); if (processReturnValue(container, result)) { Liquid_paint(container); } return container; } static struct SceneEvent handleSceneEvent(struct Liquid *container, struct SceneEvent event) { struct RunningScene *topmost = SLIST_FIRST(&container->stack); struct Scene *newScene; uint32_t tag; switch (event.kind) { case SceneEventKind_Close: assert(SLIST_NEXT(topmost, next) != NULL); SLIST_REMOVE_HEAD(&container->stack, next); tag = topmost->scene->tag; if (topmost->scene->free) { topmost->scene->free(topmost->scene); } free(topmost->scene); free(topmost); topmost = SLIST_FIRST(&container->stack); // this is always set. return topmost->scene->onChildReturn(topmost->scene, tag, event.close.status, event.close.data); case SceneEventKind_OpenChild: if (!event.open.scene) { ESP_LOGE(TAG, "Attempt to open NULL scene!"); return SceneEvent_None(); } newScene = event.open.scene; newScene->tag = event.open.tag; addChild(container, newScene); return Liquid_initTopScene(container); case SceneEventKind_RequestRepaint: case SceneEventKind_None: default: // this shouldn't get here return event; } } /** * returns true if repaint is requested */ static bool processReturnValue(struct Liquid *container, struct SceneEvent result) { bool repaint = false; while (result.kind != SceneEventKind_None) { if (result.kind == SceneEventKind_RequestRepaint) { // Repaint explicitly requested, there's no more chained events. repaint = true; break; } if (result.kind == SceneEventKind_Close) { // child close always triggers repaint, but the event handler can return // another event (e.g. close itself, or open a new child). repaint = true; } // one event can lead to another... result = handleSceneEvent(container, result); } return repaint; } bool Liquid_handleInput(struct Liquid *container, struct InputEvent event) { struct RunningScene *topmost = SLIST_FIRST(&container->stack); assert(topmost != NULL); assert(topmost->scene != NULL); if (topmost->scene->onInput) { struct SceneEvent result = topmost->scene->onInput(topmost->scene, event); return processReturnValue(container, result); } else { return false; } } bool Liquid_handleTick(struct Liquid *container, uint32_t elapsed_millis) { struct RunningScene *topmost = SLIST_FIRST(&container->stack); assert(topmost != NULL); assert(topmost->scene != NULL); if (topmost->scene->onTick) { struct SceneEvent result = topmost->scene->onTick(topmost->scene, elapsed_millis); return processReturnValue(container, result); } else { return false; } } static struct SceneEvent Liquid_initTopScene(struct Liquid *container) { struct RunningScene *topmost = SLIST_FIRST(&container->stack); assert(topmost != NULL); assert(topmost->scene != NULL); if (topmost->scene->init) { return topmost->scene->init(topmost->scene); } else { return SceneEvent_Repaint(); } } void Liquid_paint(struct Liquid *container) { struct RunningScene *topmost = SLIST_FIRST(&container->stack); assert(topmost != NULL); if (topmost->scene->paint) { topmost->scene->paint(topmost->scene); } LCD_updateDisplay(); }