"liquid" GUI framework

gui-framework
Ondřej Hruška 4 years ago
parent 0e1e40254b
commit 8df7e693bf
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 13
      main/CMakeLists.txt
  2. 71
      main/gui.c
  3. 115
      main/liquid/liquid.c
  4. 137
      main/liquid/liquid.h
  5. 44
      main/liquid/scene_car.c
  6. 85
      main/liquid/scene_root.c
  7. 13
      main/liquid/scenes.h
  8. 2
      main/nokia.c
  9. 2
      main/nokia.h

@ -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()

@ -3,10 +3,15 @@
#include "analog.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <liquid.h>
#include <freertos/timers.h>
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<pos; i++) {
// printf(" ");
// }
// printf("#");
// for (int i=pos; i<NMAX; i++) {
// printf(" ");
// }
// printf("<\n");
if (want_repaint) {
Liquid_paint(liquid);
}
}
}

@ -0,0 +1,115 @@
#include "liquid.h"
#include "rom/queue.h"
#include "scenes.h"
#include <malloc.h>
#include <assert.h>
#include <esp_log.h>
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);
}

@ -0,0 +1,137 @@
/**
* GUI framework
*
* Created on 2020/01/03.
*/
#ifndef REFLOWER_LIQUID_H
#define REFLOWER_LIQUID_H
#include <stdint.h>
#include <stdbool.h>
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 <malloc.h>
*
* 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

@ -0,0 +1,44 @@
#include "scenes.h"
#include "liquid.h"
#include "../nokia.h"
#include <malloc.h>
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;
}

@ -0,0 +1,85 @@
#include "scenes.h"
#include "liquid.h"
#include "../nokia.h"
#include "../analog.h"
#include <malloc.h>
#include <stdio.h>
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;
}

@ -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

@ -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
{

@ -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).

Loading…
Cancel
Save