|
|
|
#include <malloc.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <esp_log.h>
|
|
|
|
#include <rom/queue.h>
|
|
|
|
|
|
|
|
#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->deinit) {
|
|
|
|
topmost->scene->deinit(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();
|
|
|
|
}
|