diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index a17bd32..0e87b13 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -5,12 +5,10 @@ set(COMPONENT_SRCS "analog.c" "liquid/liquid.c" "scenes/scene_root.c" - "scenes/scene_car.c" - "scenes/scene_demo.c" "scenes/scene_bootanim.c" "scenes/scene_menu.c" "scenes/scene_number.c" - "scenes/scene_test_menu.c" + "scenes/scene_manual_menu.c" "graphics/nokia.c" "graphics/utf8.c" "graphics/font.c" diff --git a/main/analog.h b/main/analog.h index d8b1133..58279b6 100644 --- a/main/analog.h +++ b/main/analog.h @@ -7,6 +7,8 @@ #ifndef REFLOWER_ANALOG_H #define REFLOWER_ANALOG_H +#include + #define ANALOG_SAMPLE_TIME_MS 1000 #define REG_HISTORY_LEN 241 diff --git a/main/graphics/drawing.c b/main/graphics/drawing.c index 67e6f30..f73f099 100644 --- a/main/graphics/drawing.c +++ b/main/graphics/drawing.c @@ -242,27 +242,37 @@ void LCD_setStrEx(const char *dString, int x, int y, enum Color color, struct Te int spacingy = style.spacing_y; struct Utf8Char uchar; - int limit = style.max_chars; + int limit = style.limit; + int skip = style.skip; + bool wrap; while ((uchar = Utf8Iterator_Next(&iter)).uint) { - LCD_setCharEx(uchar, x, y, color, style); + if (skip > 0) { + skip -= 1; + continue; + } + + wrap = uchar.bytes[0] == '\n'; + + if (!wrap) LCD_setCharEx(uchar, x, y, color, style); if (limit > 0) { limit -= 1; if (limit == 0) return; } - x += charw; - - if (style.bg) { - for (int i = y; i < y + charh; i++) { - for (int j = x; j < x + spacingx; j++) { - LCD_setPixel(j, i, !color); + if (!wrap) { + x += charw; + if (style.bg) { + for (int i = y; i < y + charh; i++) { + for (int j = x; j < x + spacingx; j++) { + LCD_setPixel(j, i, !color); + } } } } x += spacingx; - if (x > (LCD_WIDTH - charw)) { + if (wrap || x > (LCD_WIDTH - charw)) { if (style.nowrap) break; if (style.wrap_to_0) { x = 0; diff --git a/main/graphics/drawing.h b/main/graphics/drawing.h index 5f49767..c896ffd 100644 --- a/main/graphics/drawing.h +++ b/main/graphics/drawing.h @@ -51,9 +51,10 @@ enum TextSize { }; struct TextStyle { - bool bg; //!< Fill the characters background with the opposite color + bool bg; //!< Fill the characters background with the opposite color enum TextSize size; //!< Character size - int max_chars; //!< Number of characters to print from the string, if > 0 + int limit; //!< Number of characters to print from the string, if > 0 + int skip; //!< Characters to skip before printing int spacing_x; //!< Additional X spacing int spacing_y; //!< Additional Y spacing bool nowrap; //!< Stop painting when the right edge of the screen is reached diff --git a/main/graphics/utf8.c b/main/graphics/utf8.c index f865ec2..1b5f31d 100644 --- a/main/graphics/utf8.c +++ b/main/graphics/utf8.c @@ -21,6 +21,17 @@ const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) {.uint = 0}; // U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF // U+100000 - U+10FFFF F4 80 - *8F 80 - BF 80 - BF +size_t utf8_strlen(const char *text) +{ + // TODO optimize + struct Utf8Iterator iter; + Utf8Iterator_Init(&iter, text); + size_t num = 0; + while ((Utf8Iterator_Next(&iter)).uint) { + num++; + } + return num; +} /** * Handle a received character diff --git a/main/graphics/utf8.h b/main/graphics/utf8.h index 83a63e6..e799abb 100644 --- a/main/graphics/utf8.h +++ b/main/graphics/utf8.h @@ -63,6 +63,8 @@ static inline void Utf8Iterator_Init(struct Utf8Iterator *self, const char *sour self->source = source; } +size_t utf8_strlen(const char *text); + /** * Get the next character from the iterator; Returns empty character if there are no more characters to parse. * diff --git a/main/liquid/gui.c b/main/liquid/gui.c index c114b7b..1ddc8d9 100644 --- a/main/liquid/gui.c +++ b/main/liquid/gui.c @@ -8,10 +8,8 @@ #include "drawing.h" static void gui_thread(void *arg); -static void gui_tick(TimerHandle_t xTimer); TaskHandle_t hGuiThread = NULL; -static TimerHandle_t hTicker = NULL; void gui_init() { printf("GUI init\n"); @@ -20,20 +18,6 @@ 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); } /** @@ -59,8 +43,7 @@ static void __attribute__((noreturn)) gui_thread(void *arg) { xTaskNotifyWait(0, ULONG_MAX, &value, pdMS_TO_TICKS(10)); uint32_t time = xTaskGetTickCount(); - if (value & 0b10000) { - // TICK + if (time - last_time >= pdMS_TO_TICKS(2)) { want_repaint |= Liquid_handleTick(liquid, time - last_time); last_time = time; } diff --git a/main/scenes/scene_car.c b/main/scenes/scene_car.c deleted file mode 100644 index ba1f303..0000000 --- a/main/scenes/scene_car.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include "graphics/nokia.h" -#include "graphics/drawing.h" -#include "scenes.h" -#include "liquid.h" - -struct CarScene { - struct Scene base; - int32_t pos; -}; - -static struct SceneEvent onInput(struct CarScene *self, struct InputEvent event) { - switch (event.kind) { - case InputEventKind_Wheel: - self->pos += event.wheel.delta; - if (self->pos < 0) self->pos = 0; - if (self->pos > LCD_WIDTH-21) self->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 paint(struct CarScene *self) -{ - LCD_clearDisplay(0); - LCD_setRect(self->pos, LCD_HEIGHT/2-10, self->pos+20,LCD_HEIGHT/2+10,0,1); - LCD_updateDisplay(); -} - -struct Scene *NewScene_Car(void) { - struct CarScene *scene = calloc(1, sizeof(struct CarScene)); - if (!scene) return NULL; - - scene->base.onInput = (Scene_onInput_t) onInput; - scene->base.paint = (Scene_paint_t) paint; - return (struct Scene *) scene; -} diff --git a/main/scenes/scene_demo.c b/main/scenes/scene_demo.c deleted file mode 100644 index 0dfa33c..0000000 --- a/main/scenes/scene_demo.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include - -#include "scenes.h" -#include "liquid.h" -#include "analog.h" -#include "graphics/nokia.h" -#include "graphics/drawing.h" - -/** - * The struct is allocated bigger than 'Scene' to accommodate private fields. - * Since the base struct is located at the beginning, it can be cast and passed around as Scene. - */ -struct DemoScene { - struct Scene base; - int32_t pos; - uint32_t timer_phase; - uint32_t timer_prescale; -}; - -static struct SceneEvent onInput(struct DemoScene *self, struct InputEvent event) { - switch (event.kind) { - case InputEventKind_Wheel: - self->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 onTick(struct DemoScene *self, uint32_t millis) { - // top text anim - self->timer_prescale += millis; - - if (self->timer_prescale == 1000) { - self->timer_prescale = 0; - self->timer_phase += 1; - self->timer_phase = self->timer_phase & 0b11; // 0..3 - - return SceneEvent_Repaint(); - } - - return SceneEvent_None(); -} - -static void paint(struct DemoScene *self) -{ - LCD_clearDisplay(0); - - const char *header = "???"; - switch (self->timer_phase) { - case 0: - header = "Drink water"; - break; - case 1: - header = " Drink water"; - break; - case 2: - header = " Drink water"; - break; - case 3: - header = " Drink water"; - break; - } - - LCD_setStrEx(header, 3, 1, BLACK, (struct TextStyle) {.size = FONT_BOLD}); - - LCD_setRect(0, 12, 83, 31, true, BLACK); - char buf[10]; -// sprintf(buf, "%3d", priv->pos); -// LCD_setStr(buf, 2, 15, 0); - sprintf(buf, "🌡%.0f°C", analog_read()); - LCD_setStrEx(buf, 2, 14, WHITE, (struct TextStyle) {.size = FONT_DOUBLE}); - - LCD_setStr("×↑↓←→⏰⌛☸⏎🌡°🔙▶◀", 2, 33, BLACK); -} - -struct Scene *NewScene_Demo(void) { - struct DemoScene *scene = calloc(1, sizeof(struct DemoScene)); - - scene->base.onInput = (Scene_onInput_t) onInput; - scene->base.paint = (Scene_paint_t) paint; - scene->base.onTick = (Scene_onTick_t) onTick; - scene->base.onInput = (Scene_onInput_t) onInput; - return (struct Scene *) scene; -} diff --git a/main/scenes/scene_manual_menu.c b/main/scenes/scene_manual_menu.c new file mode 100644 index 0000000..a1f7c48 --- /dev/null +++ b/main/scenes/scene_manual_menu.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +#include "liquid.h" +#include "graphics/drawing.h" +#include "graphics/display_spec.h" + +#include "scenes.h" +#include "scene_menu.h" + +struct Priv { + Scene_paint_t basePaint; +}; + +static struct SceneEvent onSelect(struct MenuScene *self) { + if (self->selected == 0) { + struct NumberSceneOpts *opts = calloc(1, sizeof(struct NumberSceneOpts)); + strcpy(opts->unit, "°C"); + strcpy(opts->label, "Setpoint"); + opts->value = (int) roundf(fire_get_setpoint(false)); + opts->min = 0; + opts->max = 350; + return SceneEvent_OpenChild(NewScene_Number(opts), 0); + } + + if (self->selected == 1) { + fire_enable(!fire_enabled()); + return SceneEvent_Repaint(); + } + + return SceneEvent_None(); +} + +static void priv_deinit(struct MenuScene *self) +{ + free(self->extra); + self->extra = NULL; +} + +static struct SceneEvent onChildReturn(struct MenuScene *self, uint32_t tag, int32_t status, void *data) +{ + if (tag == 0) { + fire_setlevel((float) status); + } + + return SceneEvent_Repaint(); +} + +#define XLINE 38 + +void print_labels(struct MenuItem *items) { + snprintf(items[0].label, MENUITEM_LABEL_LEN, "▶%.0f°C", fire_get_setpoint(false)); + if (fire_enabled()) { + strncpy(items[1].label, "▶STOP", MENUITEM_LABEL_LEN); + } else { + strncpy(items[1].label, "▶START", MENUITEM_LABEL_LEN); + } +} + +static void paint(struct MenuScene *self) +{ + struct Priv *priv = self->extra; + + print_labels(self->items); + + LCD_clearDisplay(0); + + LCD_setLine(XLINE-1, 0, XLINE-1, LCD_HEIGHT, BLACK); + + char buf[10]; + sprintf(buf, "%.0f", analog_read()); + // TODO centering + LCD_setStrEx(buf, 0, 0, BLACK, (struct TextStyle) {.size = FONT_DOUBLE}); + LCD_setStrEx("°C", 0, 17, BLACK, (struct TextStyle) {.size = FONT_BOLD}); + + if (fire_enabled()) { + strcpy(buf, "RUN"); + } else { + strcpy(buf, "Off"); + } + LCD_setStrEx(buf, 0, 30, BLACK, (struct TextStyle) {.size = FONT_BOLD}); + + priv->basePaint((struct Scene *) self); +} + +struct Scene *NewScene_MenuTest() { + struct MenuItem *items = calloc(2, sizeof(struct MenuItem)); + + print_labels(items); + + struct MenuScene * scene = NewScene_Menu(items, 2); + scene->onSelect = onSelect; + + scene->x = XLINE; + scene->ncols = 8; + + // private data added by the subclass + struct Priv *priv = calloc(1, sizeof(struct Priv)); + priv->basePaint = scene->base.paint; + scene->base.paint = (Scene_paint_t) paint; + scene->extra = priv; + scene->extra_deinit = (Scene_deinit_t) priv_deinit; + + // add a child return handler (base does not use this) + scene->base.onChildReturn = (Scene_onChildReturn_t) onChildReturn; + + return (struct Scene *) scene; +} diff --git a/main/scenes/scene_menu.c b/main/scenes/scene_menu.c index a8c4dd5..31797a1 100644 --- a/main/scenes/scene_menu.c +++ b/main/scenes/scene_menu.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include "liquid.h" @@ -23,17 +25,29 @@ static void paint(struct MenuScene *self) } #endif +#define HSCROLL_START_MS 750 +#define HSCROLL_MS 250 + static void paint(struct MenuScene *self) { - LCD_clearDisplay(0); + LCD_setRect(self->x, self->y, self->x + self->ncols*6, self->y + self->nlines*9, 1, WHITE); int row = 0; - for (int i = self->scroll_up; i < MIN(self->items_len, self->scroll_up + self->visible_lines); i++, row++) { + for (int i = self->scroll_up; i < MIN(self->items_len, self->scroll_up + self->nlines); i++, row++) { bool selected = self->selected==i; - if (selected) LCD_setRect(0, row*9, LCD_WIDTH-1, row*9 + 8, 1, 1); - LCD_setStrEx(self->items[i].label, 1, row * 9 + 1, !selected, (struct TextStyle) { + if (selected) LCD_setRect(self->x, self->y + row*9, self->x + self->ncols*6, self->y + row*9 + 8, 1, 1); + size_t shift = 0; + if (selected) { + size_t len = utf8_strlen(self->items[i].label); + shift = self->hscroll; + if (shift > len - self->ncols) { + shift = len - self->ncols; + } + } + LCD_setStrEx(self->items[i].label, self->x + 1, self->y + row * 9 + 1, !selected, (struct TextStyle) { .bg = selected, - .max_chars = 13, + .nowrap = 1, + .skip = shift, }); } } @@ -54,10 +68,15 @@ static struct SceneEvent onInput(struct MenuScene *self, struct InputEvent event self->scroll_up--; } - while (self->selected - self->scroll_up >= (self->visible_lines - 1) && self->scroll_up < self->items_len - self->visible_lines) { + while (self->selected - self->scroll_up >= (self->nlines - 1) && + self->scroll_up < self->items_len - self->nlines) { self->scroll_up++; } + // reset hscroll + self->hscroll_cnt = 0; + self->hscroll = 0; + return SceneEvent_Repaint(); case InputEventKind_Button: @@ -73,6 +92,35 @@ static struct SceneEvent onInput(struct MenuScene *self, struct InputEvent event } } +static struct SceneEvent onTick(struct MenuScene *self, uint32_t millis) { + size_t len = utf8_strlen(self->items[self->selected].label); + self->hscroll_cnt += millis; + + if (len > self->ncols) { + size_t limit = (self->hscroll == 0 ? HSCROLL_START_MS : HSCROLL_MS); + + if (self->hscroll_cnt >= limit) { + self->hscroll_cnt -= limit; + self->hscroll++; + + if (self->hscroll > len - self->ncols + 2/* trailing delay */) { + self->hscroll = 0; + } + + return SceneEvent_Repaint(); + } + } else { + self->hscroll = 0; + + // ensure periodic repaint (for parent) + if (self->hscroll_cnt > 20) { + return SceneEvent_Repaint(); + } + } + + return SceneEvent_None(); +} + static void deinit(struct MenuScene *self) { free(self->items); @@ -90,14 +138,19 @@ struct MenuScene *NewScene_Menu(struct MenuItem *items, size_t items_len) { scene->base.paint = (Scene_paint_t) paint; scene->base.onInput = (Scene_onInput_t) onInput; scene->base.deinit = (Scene_deinit_t) deinit; + scene->base.onTick = (Scene_onTick_t) onTick; // onChildReturn is free to set by subclass // TODO long selected label scrolling on tick - scene->visible_lines = 5; scene->items = items; scene->items_len = items_len; + scene->nlines = 5; + scene->ncols = 14; + scene->x = 0; + scene->y = 0; + return scene; } diff --git a/main/scenes/scene_menu.h b/main/scenes/scene_menu.h index f5cbee9..3019bba 100644 --- a/main/scenes/scene_menu.h +++ b/main/scenes/scene_menu.h @@ -40,12 +40,22 @@ struct MenuScene { struct MenuItem *items; // items count size_t items_len; + // number of visible characters in a line at a time + uint8_t ncols; // Vertical scroll offset (how many steps is the topmost item moved above the screen) int32_t scroll_up; + // Start X + int x; + // Start Y + int y; // Selected item number int32_t selected; + // Horizontal scroll count + int32_t hscroll; + // Horizontal scroll timer + int32_t hscroll_cnt; // Number of lines visible at a time - uint8_t visible_lines; + uint8_t nlines; // selection handler MenuScene_onSelect_t onSelect; // Extra field for the subclass's private use. diff --git a/main/scenes/scene_test_menu.c b/main/scenes/scene_test_menu.c deleted file mode 100644 index e487ef0..0000000 --- a/main/scenes/scene_test_menu.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include -#include -#include - -#include "liquid.h" -#include "graphics/drawing.h" - -#include "scenes.h" -#include "scene_menu.h" - -struct Priv { - int32_t number; -}; - -static struct SceneEvent onSelect(struct MenuScene *self) { - struct Priv *priv = self->extra; - - if (self->selected == 0) { - return SceneEvent_Close(0, NULL); - } - - if (self->selected == 8) { - return SceneEvent_OpenChild(NewScene_Car(), 0); - } - if (self->selected == 9) { - return SceneEvent_OpenChild(NewScene_Demo(), 0); - } - if (self->selected == 2) { - struct NumberSceneOpts *opts = calloc(1, sizeof(struct NumberSceneOpts)); - strcpy(opts->unit, "°C"); - strcpy(opts->label, "Setpoint"); - opts->value = priv->number; - opts->min = 0; - opts->max = 350; - return SceneEvent_OpenChild(NewScene_Number(opts), 99); - } - if (self->selected == 5) { - self->items[5].tag++; - snprintf(self->items[5].label, MENUITEM_LABEL_LEN, "Clicked %dx", self->items[5].tag); - return SceneEvent_Repaint(); - } - - return SceneEvent_None(); -} - -static void priv_deinit(struct MenuScene *self) -{ - free(self->extra); - self->extra = NULL; -} - -static struct SceneEvent onChildReturn(struct MenuScene *self, uint32_t tag, int32_t status, void *data) -{ - struct Priv *priv = self->extra; - if (tag == 99) { - priv->number = status; - snprintf(self->items[2].label, MENUITEM_LABEL_LEN, "Set=%d°C", priv->number); - } - - // this should be unreachable - return SceneEvent_None(); -} - -struct Scene *NewScene_MenuTest() { - struct MenuItem *items = calloc(10, sizeof(struct MenuItem)); - - strncpy(items[0].label, "🔙Back", MENUITEM_LABEL_LEN); - strncpy(items[1].label, "▶#1", MENUITEM_LABEL_LEN); - strncpy(items[2].label, "Set=0°C", MENUITEM_LABEL_LEN); - strncpy(items[3].label, "▶#3", MENUITEM_LABEL_LEN); - strncpy(items[4].label, "▶#4", MENUITEM_LABEL_LEN); - strncpy(items[5].label, "Clicked 0x", MENUITEM_LABEL_LEN); - strncpy(items[6].label, "▶#6", MENUITEM_LABEL_LEN); - strncpy(items[7].label, "▶#7", MENUITEM_LABEL_LEN); - strncpy(items[8].label, "Car", MENUITEM_LABEL_LEN); - strncpy(items[9].label, "Demo Scene", MENUITEM_LABEL_LEN); - - struct MenuScene * scene = NewScene_Menu(items, 10); - scene->onSelect = onSelect; - - // private data added by the subclass - struct Priv *priv = calloc(1, sizeof(struct Priv)); - priv->number = 0; - scene->extra = priv; - scene->extra_deinit = (Scene_deinit_t) priv_deinit; - - // add a child return handler (base does not use this) - scene->base.onChildReturn = (Scene_onChildReturn_t) onChildReturn; - - return (struct Scene *) scene; -} diff --git a/main/scenes/scenes.h b/main/scenes/scenes.h index 1bf2809..50acafc 100644 --- a/main/scenes/scenes.h +++ b/main/scenes/scenes.h @@ -11,8 +11,6 @@ struct Scene *NewScene_Root(void); struct Scene *NewScene_Boot(void); -struct Scene *NewScene_Demo(void); -struct Scene *NewScene_Car(void); struct Scene *NewScene_MenuTest(void); #include "scene_menu.h"