From 8df7e693bf60b598b0ef50eac50870f8dc76de27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 4 Jan 2020 00:19:35 +0100 Subject: [PATCH] "liquid" GUI framework --- main/CMakeLists.txt | 13 +++- main/gui.c | 71 +++++++++++--------- main/liquid/liquid.c | 115 ++++++++++++++++++++++++++++++++ main/liquid/liquid.h | 137 +++++++++++++++++++++++++++++++++++++++ main/liquid/scene_car.c | 44 +++++++++++++ main/liquid/scene_root.c | 85 ++++++++++++++++++++++++ main/liquid/scenes.h | 13 ++++ main/nokia.c | 2 +- main/nokia.h | 2 +- 9 files changed, 448 insertions(+), 34 deletions(-) create mode 100644 main/liquid/liquid.c create mode 100644 main/liquid/liquid.h create mode 100644 main/liquid/scene_car.c create mode 100644 main/liquid/scene_root.c create mode 100644 main/liquid/scenes.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 99f3269..1f75fa3 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,13 @@ -set(COMPONENT_SRCS "app_main.c" "nokia.c" "knob.c" "gui.c" "analog.c") -set(COMPONENT_ADD_INCLUDEDIRS "") +set(COMPONENT_SRCS + "app_main.c" + "nokia.c" + "knob.c" + "gui.c" + "analog.c" + "liquid/liquid.c" + "liquid/scene_root.c" + "liquid/scene_car.c" +) +set(COMPONENT_ADD_INCLUDEDIRS "liquid") register_component() diff --git a/main/gui.c b/main/gui.c index e3dd218..dba9008 100644 --- a/main/gui.c +++ b/main/gui.c @@ -3,10 +3,15 @@ #include "analog.h" #include #include +#include +#include static void gui_thread(void *arg); +static void gui_tick(TimerHandle_t xTimer); TaskHandle_t hGuiThread = NULL; +TimerHandle_t hTicker = NULL; + void gui_init() { printf("GUI init\n"); @@ -20,35 +25,55 @@ void gui_init() { int rv = xTaskCreate(gui_thread, "gui", 4096, NULL, 6, &hGuiThread); assert (rv == pdPASS); + + hTicker = xTimerCreate( + "gui_tick", /* name */ + pdMS_TO_TICKS(10), /* period/time */ + pdTRUE, /* auto reload */ + (void*)0, /* timer ID */ + gui_tick); /* callback */ + assert(hTicker != NULL); + + xTimerStart(hTicker, portMAX_DELAY); +} + +static void gui_tick(TimerHandle_t xTimer) { + xTaskNotify(hGuiThread, 0b10000, eSetBits); } /** * Notification API: * - * 0b1 - knob CW - * 0b10 - knowb CCW - * 0b100 - button released - * 0b1000 - button pressed + * 0b1 - knob CW + * 0b10 - knowb CCW + * 0b100 - button released + * 0b1000 - button pressed + * 0b10000 - ticker event * * @param arg */ static void __attribute__((noreturn)) gui_thread(void *arg) { - uint32_t pos = 0; - bool btn = 0; + struct Liquid *liquid = Liquid_start(); uint32_t last_wheel_time = 0; -#define NMAX 60 + while (1) { uint32_t value = 0; - xTaskNotifyWait(0, ULONG_MAX, &value, pdMS_TO_TICKS(250)); + xTaskNotifyWait(0, ULONG_MAX, &value, pdMS_TO_TICKS(10)); + + bool want_repaint = false; // printf("Knob event 0x%02x ", value); + if (value & 0b10000) { + // TICK + want_repaint |= Liquid_handleTick(liquid); + } + if (value & 0b1000) { -// printf("PUSH "); - btn = 1; + want_repaint |= Liquid_handleInput(liquid, InputEvent_Button(1)); } else if (value & 0b100) { - btn = 0; + want_repaint |= Liquid_handleInput(liquid, InputEvent_Button(0)); } if (value & 0b11) { @@ -67,30 +92,16 @@ static void __attribute__((noreturn)) gui_thread(void *arg) { last_wheel_time = time; if (value & 0b01) { - pos += increment; + want_repaint |= Liquid_handleInput(liquid, InputEvent_Wheel(increment)); } if (value & 0b10) { - pos -= increment; + want_repaint |= Liquid_handleInput(liquid, InputEvent_Wheel(-increment)); } } - LCD_setRect(0, 15, 83, 35, 1, 1); - char buf[10]; - sprintf(buf, "%3d %s", pos, btn?"BTN":""); - LCD_setStr(buf, 2, 17, 0); - sprintf(buf, "%.0f C", analog_read()); - LCD_setStr(buf, 2, 26, 0); - LCD_updateDisplay(); - -// printf(">"); -// for (int i=0; i +#include +#include + +static const char *TAG = "Liquid"; + +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, uint32_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; + assert(child->paint != NULL); + elm->scene = child; + SLIST_INSERT_HEAD(&container->stack, elm, next); +} + +struct Liquid *Liquid_start() { + struct Liquid *container = calloc(1, sizeof(struct Liquid)); + + addChild(container, NewScene_Root()); + Liquid_paint(container); + return container; +} + +static struct SceneEvent handleSceneEvent(struct Liquid *container, struct SceneEvent event) { + struct RunningScene *topmost = SLIST_FIRST(&container->stack); + switch (event.kind) { + case SceneEventKind_Close: + assert(SLIST_NEXT(topmost, next) != NULL); + SLIST_REMOVE_HEAD(&container->stack, next); + uint32_t tag = topmost->scene->tag; + + if (topmost->scene->options) { + free(topmost->scene->options); + } + 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(); + } + struct Scene *newScene = event.open.scene; + newScene->tag = event.open.tag; + addChild(container, newScene); + return SceneEvent_Repaint(); + + case SceneEventKind_RequestRepaint: + case SceneEventKind_None: + default: + // this shouldn't get here + return event; + } +} + +bool processReturnValue(struct Liquid *container, struct SceneEvent result) { + while (result.kind != SceneEventKind_None) { + if (result.kind == SceneEventKind_RequestRepaint) { + return 1; // repaint + } + result = handleSceneEvent(container, result); + } + return 0; +} + +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) { + 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); + return processReturnValue(container, result); + } else { + return false; + } +} + +void Liquid_paint(struct Liquid *container) { + struct RunningScene *topmost = SLIST_FIRST(&container->stack); + assert(topmost != NULL); + topmost->scene->paint(topmost->scene); +} diff --git a/main/liquid/liquid.h b/main/liquid/liquid.h new file mode 100644 index 0000000..961fc5f --- /dev/null +++ b/main/liquid/liquid.h @@ -0,0 +1,137 @@ +/** + * GUI framework + * + * Created on 2020/01/03. + */ + +#ifndef REFLOWER_LIQUID_H +#define REFLOWER_LIQUID_H + +#include +#include + +struct Liquid; + +enum InputEvent_Kind { + InputEventKind_Wheel, + InputEventKind_Button, +}; + +struct InputEvent { + enum InputEvent_Kind kind; + union { + struct { + // Wheel delta + int8_t delta; + } wheel; + struct { + // Button state + bool state; + } button; + }; +}; + +#define InputEvent_Wheel(delta_) ((struct InputEvent) { \ + .kind = InputEventKind_Wheel, \ + .wheel = { .delta = delta_ } \ +}) + +#define InputEvent_Button(state_) ((struct InputEvent) { \ + .kind = InputEventKind_Button, \ + .button = { .state = state_ }, \ +}) + +enum SceneEvent_Kind { + // Close this scene. + // The scene is responsible for cleaning up 'private'. + // 'options' and the scene itself will be freed by the GUI engine. + SceneEventKind_Close, + SceneEventKind_OpenChild, + SceneEventKind_RequestRepaint, + SceneEventKind_None, +}; + +struct SceneEvent { + enum SceneEvent_Kind kind; + union { + struct { + // Status code + uint32_t status; + // Return data on heap + void *data; + } close; + struct { + // Scene (initialized, with options loaded in through the constructor) + void *scene; + // Tag used by parent to identify the open child + uint32_t tag; + } open; + }; +}; + +struct Scene; + +#define SceneEvent_None() ((struct SceneEvent) { \ + .kind = SceneEventKind_None\ +}) + +#define SceneEvent_Repaint() ((struct SceneEvent) { \ + .kind = SceneEventKind_RequestRepaint\ +}) + +#define SceneEvent_OpenChild(child_, tag_) ((struct SceneEvent) { \ + .kind = SceneEventKind_OpenChild,\ + .open = { .scene = (child_), .tag=tag_ }, \ +}) + +#define SceneEvent_Close(status_, data_) ((struct SceneEvent) { \ + .kind = SceneEventKind_Close,\ + .close = { .status = (status_), .data=(data_) }, \ +}) + +typedef struct SceneEvent (*Scene_onInput_t)(struct Scene *scene, struct InputEvent event); +typedef struct SceneEvent (*Scene_onChildReturn_t)(struct Scene *scene, uint32_t tag, uint32_t status, void *data); +typedef struct SceneEvent (*Scene_onTick_t)(struct Scene *scene); +typedef void (*Scene_repaint_t)(struct Scene *scene); + +struct Scene { + uint32_t tag; + void *options; + void *private; + /** Handle input */ + Scene_onInput_t onInput; + /** Handle child scene return */ + Scene_onChildReturn_t onChildReturn; + /** Periodic tick */ + Scene_onTick_t onTick; + /** Periodic tick */ + Scene_repaint_t paint; +}; + +/** + * Allocate scene and the inner private type. + * + * Requires + * + * Must be used like: + * + * struct Scene *scene = SCENE_SAFE_ALLOC(struct private); + * scene->onInput = ... + * return scene; + */ +#define SCENE_SAFE_ALLOC(private_type) \ + calloc(1, sizeof(struct Scene)); \ + if (!scene) return NULL; \ + scene->private = calloc(1, sizeof(private_type)); \ + if (!scene->private) return NULL; + +/** return 1 if repaint requested */ +bool Liquid_handleInput(struct Liquid *container, struct InputEvent event); + +/** return 1 if repaint requested */ +bool Liquid_handleTick(struct Liquid *container); + +void Liquid_paint(struct Liquid *container); +struct Liquid *Liquid_start(); + +#endif //REFLOWER_LIQUID_H diff --git a/main/liquid/scene_car.c b/main/liquid/scene_car.c new file mode 100644 index 0000000..4a5023c --- /dev/null +++ b/main/liquid/scene_car.c @@ -0,0 +1,44 @@ +#include "scenes.h" +#include "liquid.h" +#include "../nokia.h" +#include + +struct private { + int32_t pos; +}; + +static struct SceneEvent Car_onInput(struct Scene *scene, struct InputEvent event) { + struct private *priv = scene->private; + switch (event.kind) { + case InputEventKind_Wheel: + priv->pos += event.wheel.delta; + if (priv->pos < 0) priv->pos = 0; + if (priv->pos > LCD_WIDTH-21) priv->pos = LCD_WIDTH-21; + return SceneEvent_Repaint(); + + case InputEventKind_Button: + if (event.button.state) { + return SceneEvent_Close(0, NULL); + } + // fall through + default: + return SceneEvent_None(); + } +} + +static void Car_paint(struct Scene *scene) +{ + struct private *priv = scene->private; + + LCD_clearDisplay(0); + LCD_setRect(priv->pos, LCD_HEIGHT/2-10, priv->pos+20,LCD_HEIGHT/2+10,0,1); + LCD_updateDisplay(); +} + +struct Scene *NewScene_Car(void) { + struct Scene *scene = SCENE_SAFE_ALLOC(struct private); + + scene->onInput = Car_onInput; + scene->paint = Car_paint; + return scene; +} diff --git a/main/liquid/scene_root.c b/main/liquid/scene_root.c new file mode 100644 index 0000000..27bae04 --- /dev/null +++ b/main/liquid/scene_root.c @@ -0,0 +1,85 @@ +#include "scenes.h" +#include "liquid.h" +#include "../nokia.h" +#include "../analog.h" +#include +#include + +struct private { + int32_t pos; + uint32_t timer_phase; + uint32_t timer_prescale; +}; + +static struct SceneEvent Root_onInput(struct Scene *scene, struct InputEvent event) { + struct private *priv = scene->private; + switch (event.kind) { + case InputEventKind_Wheel: + priv->pos += event.wheel.delta; + break; + + case InputEventKind_Button: + if (event.button.state) { + return SceneEvent_OpenChild(NewScene_Car(), 0); + } + break; + } + + return SceneEvent_Repaint(); +} + +static struct SceneEvent Root_onTick(struct Scene *scene) { + struct private *priv = scene->private; + priv->timer_prescale += 1; + + if (priv->timer_prescale == 100) { + priv->timer_prescale = 0; + priv->timer_phase += 1; + priv->timer_phase = priv->timer_phase & 0b11; // 0..3 + + return SceneEvent_Repaint(); + } + + return SceneEvent_None(); +} + +static void Root_paint(struct Scene *scene) +{ + struct private *priv = scene->private; + + LCD_clearDisplay(0); + const char *header = "???"; + switch (priv->timer_phase) { + case 0: + header = "ICE"; + break; + case 1: + header = " COLD"; + break; + case 2: + header = "COCA"; + break; + case 3: + header = " COLA"; + break; + } + + LCD_setStr(header, 20, 3, 1); + + LCD_setRect(0, 15, 83, 35, 1, 1); + char buf[10]; + sprintf(buf, "%3d", priv->pos); + LCD_setStr(buf, 2, 17, 0); + sprintf(buf, "%.0f C", analog_read()); + LCD_setStr(buf, 2, 26, 0); + LCD_updateDisplay(); +} + +struct Scene *NewScene_Root(void) { + struct Scene *scene = SCENE_SAFE_ALLOC(struct private); + + scene->onInput = Root_onInput; + scene->paint = Root_paint; + scene->onTick = Root_onTick; + return scene; +} diff --git a/main/liquid/scenes.h b/main/liquid/scenes.h new file mode 100644 index 0000000..850251e --- /dev/null +++ b/main/liquid/scenes.h @@ -0,0 +1,13 @@ +/** + * TODO file description + * + * Created on 2020/01/03. + */ + +#ifndef REFLOWER_SCENES_H +#define REFLOWER_SCENES_H + +struct Scene *NewScene_Root(void); +struct Scene *NewScene_Car(void); + +#endif //REFLOWER_SCENES_H diff --git a/main/nokia.c b/main/nokia.c index 7b1fa62..fcbaf82 100644 --- a/main/nokia.c +++ b/main/nokia.c @@ -345,7 +345,7 @@ void LCD_setChar(char character, int x, int y, bool bw) // progressive coordinates until it's done. // This function was grabbed from the SparkFun ColorLCDShield // library. -void LCD_setStr(char *dString, int x, int y, bool bw) +void LCD_setStr(const char *dString, int x, int y, bool bw) { while (*dString != 0x00) // loop until null terminator { diff --git a/main/nokia.h b/main/nokia.h index 2b9563b..f412f00 100644 --- a/main/nokia.h +++ b/main/nokia.h @@ -44,7 +44,7 @@ void LCD_setChar(char character, int x, int y, bool bw); // progressive coordinates until it's done. // This function was grabbed from the SparkFun ColorLCDShield // library. -void LCD_setStr(char * dString, int x, int y, bool bw); +void LCD_setStr(const char * dString, int x, int y, bool bw); // This function clears the entire display either white (0) or // black (1).