code splitting & refactors; more efficient font drawing

master
Ondřej Hruška 4 years ago
parent 1a2cc21fbb
commit 6db58e14a7
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 9
      main/CMakeLists.txt
  2. 13
      main/graphics/display_spec.h
  3. 313
      main/graphics/drawing.c
  4. 75
      main/graphics/drawing.h
  5. 254
      main/graphics/font.c
  6. 18
      main/graphics/font.h
  7. 316
      main/graphics/nokia.c
  8. 66
      main/graphics/nokia.h
  9. 13
      main/graphics/utf8.c
  10. 27
      main/graphics/utf8.h
  11. 19
      main/liquid/gui.c
  12. 0
      main/liquid/gui.h
  13. 51
      main/liquid/input_event.h
  14. 10
      main/liquid/liquid.c
  15. 229
      main/liquid/liquid.h
  16. 99
      main/liquid/scene_event.h
  17. 105
      main/liquid/scene_type.h
  18. 3
      main/scenes/scene_car.c
  19. 10
      main/scenes/scene_root.c
  20. 0
      main/scenes/scenes.h

@ -1,15 +1,16 @@
set(COMPONENT_SRCS set(COMPONENT_SRCS
"app_main.c" "app_main.c"
"knob.c" "knob.c"
"gui.c" "liquid/gui.c"
"analog.c" "analog.c"
"liquid/liquid.c" "liquid/liquid.c"
"liquid/scene_root.c" "scenes/scene_root.c"
"liquid/scene_car.c" "scenes/scene_car.c"
"graphics/nokia.c" "graphics/nokia.c"
"graphics/utf8.c" "graphics/utf8.c"
"graphics/font.c" "graphics/font.c"
"graphics/drawing.c"
) )
set(COMPONENT_ADD_INCLUDEDIRS "liquid" "graphics") set(COMPONENT_ADD_INCLUDEDIRS "." "liquid" "graphics")
register_component() register_component()

@ -0,0 +1,13 @@
/**
* TODO file description
*
* Created on 2020/01/05.
*/
#ifndef REFLOWER_DISPLAY_SPEC_H
#define REFLOWER_DISPLAY_SPEC_H
#define LCD_WIDTH 84 // Note: x-coordinates go wide
#define LCD_HEIGHT 48 // Note: y-coordinates go high
#endif //REFLOWER_DISPLAY_SPEC_H

@ -0,0 +1,313 @@
#include "drawing.h"
#include "display_spec.h"
#include "font.h"
extern uint8_t LCD_displayMap[LCD_WIDTH * LCD_HEIGHT / 8];
// This function sets a pixel on displayMap to your preferred
// color. 1=Black, 0= white.
void LCD_setPixel(int x, int y, enum Color bw)
{
// First, double check that the coordinate is in range.
if ((x >= 0) && (x < LCD_WIDTH) && (y >= 0) && (y < LCD_HEIGHT)) {
uint8_t shift = y % 8;
if (bw) // If black, set the bit.
LCD_displayMap[x + (y / 8) * LCD_WIDTH] |= 1 << shift;
else // If white clear the bit.
LCD_displayMap[x + (y / 8) * LCD_WIDTH] &= ~(1 << shift);
}
}
// setLine draws a line from x0,y0 to x1,y1 with the set color.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setLine(int x0, int y0, int x1, int y1, enum Color bw)
{
int dy = y1 - y0; // Difference between y0 and y1
int dx = x1 - x0; // Difference between x0 and x1
int stepx, stepy;
if (dy < 0) {
dy = -dy;
stepy = -1;
}
else
stepy = 1;
if (dx < 0) {
dx = -dx;
stepx = -1;
}
else
stepx = 1;
dy <<= 1; // dy is now 2*dy
dx <<= 1; // dx is now 2*dx
LCD_setPixel(x0, y0, bw); // Draw the first pixel.
if (dx > dy) {
int fraction = dy - (dx >> 1);
while (x0 != x1) {
if (fraction >= 0) {
y0 += stepy;
fraction -= dx;
}
x0 += stepx;
fraction += dy;
LCD_setPixel(x0, y0, bw);
}
}
else {
int fraction = dx - (dy >> 1);
while (y0 != y1) {
if (fraction >= 0) {
x0 += stepx;
fraction -= dy;
}
y0 += stepy;
fraction += dx;
LCD_setPixel(x0, y0, bw);
}
}
}
// setRect will draw a rectangle from x0,y0 top-left corner to
// a x1,y1 bottom-right corner. Can be filled with the fill
// parameter, and colored with bw.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, enum Color bw)
{
// check if the rectangle is to be filled
if (fill == 1) {
int xDiff;
if (x0 > x1)
xDiff = x0 - x1; //Find the difference between the x vars
else
xDiff = x1 - x0;
while (xDiff > 0) {
LCD_setLine(x0, y0, x0, y1, bw);
if (x0 > x1)
x0--;
else
x0++;
xDiff--;
}
}
else {
// best way to draw an unfilled rectangle is to draw four lines
LCD_setLine(x0, y0, x1, y0, bw);
LCD_setLine(x0, y1, x1, y1, bw);
LCD_setLine(x0, y0, x0, y1, bw);
LCD_setLine(x1, y0, x1, y1, bw);
}
}
// setCircle draws a circle centered around x0,y0 with a defined
// radius. The circle can be black or white. And have a line
// thickness ranging from 1 to the radius of the circle.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setCircle(int x0, int y0, int radius, enum Color bw, int lineThickness)
{
for (int r = 0; r < lineThickness; r++) {
int f = 1 - radius;
int ddF_x = 0;
int ddF_y = -2 * radius;
int x = 0;
int y = radius;
LCD_setPixel(x0, y0 + radius, bw);
LCD_setPixel(x0, y0 - radius, bw);
LCD_setPixel(x0 + radius, y0, bw);
LCD_setPixel(x0 - radius, y0, bw);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x + 1;
LCD_setPixel(x0 + x, y0 + y, bw);
LCD_setPixel(x0 - x, y0 + y, bw);
LCD_setPixel(x0 + x, y0 - y, bw);
LCD_setPixel(x0 - x, y0 - y, bw);
LCD_setPixel(x0 + y, y0 + x, bw);
LCD_setPixel(x0 - y, y0 + x, bw);
LCD_setPixel(x0 + y, y0 - x, bw);
LCD_setPixel(x0 - y, y0 - x, bw);
}
radius--;
}
}
// This function will draw a char (defined in the ASCII table
// near the beginning of this sketch) at a defined x and y).
// The color can be either black (1) or white (0).
void LCD_setChar(struct Utf8Char character, int x, int y, enum Color bw)
{
LCD_setCharEx(character, x, y, bw, 1);
}
void LCD_setCharEx(struct Utf8Char character, int x, int y, enum Color bw, uint8_t style)
{
const struct FontGraphic *symbol = Font_GetSymbol(character);
bool bg = style & 0x80;
enum FontStyle style_real = style & 0x7F;
uint8_t column; // temp byte to store character's column bitmap
for (int i = 0; i < 5; i++) // 5 columns (x) per character
{
column = symbol->columns[i];
for (int j = 0; j < 8; j++) // 8 rows (y) per character
{
bool bit = column & (1 << j);
if (style_real == FONT_NORMAL) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i, y + j, bw);
}
else if (bg) {
LCD_setPixel(x + i, y + j, !bw);
}
}
else if (style_real == FONT_DOUBLE) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i * 2, y + j * 2, bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2, bw);
LCD_setPixel(x + i * 2, y + j * 2 + 1, bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, bw);
}
else if (bg) {
LCD_setPixel(x + i * 2, y + j * 2, !bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2, !bw);
LCD_setPixel(x + i * 2, y + j * 2 + 1, !bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, !bw);
}
}
else if (style_real == FONT_BOLD) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i, y + j, bw);
LCD_setPixel(x + i + 1, y + j, bw);
}
else if (bg) {
LCD_setPixel(x + i, y + j, !bw);
LCD_setPixel(x + i + 1, y + j, !bw);
}
}
}
}
}
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStr(const char *dString, int x, int y, enum Color bw)
{
LCD_setStrEx(dString, x, y, bw, 1);
}
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStrEx(const char *dString, int x, int y, enum Color bw, uint8_t style)
{
struct Utf8Iterator iter;
Utf8Iterator_Init(&iter, dString);
bool bg = style & 0x80;
enum FontStyle style_real = style & 0x7F;
struct Utf8Char uchar;
while ((uchar = Utf8Iterator_Next(&iter)).uint) {
LCD_setCharEx(uchar, x, y, bw, style);
if (style_real == FONT_NORMAL) {
x += 5;
if (bg) {
for (int i = y; i < y + 8; i++) {
LCD_setPixel(x, i, !bw);
}
}
x++;
if (x > (LCD_WIDTH - 5)) // Enables wrap around
{
x = 0;
y += 8;
}
}
else if (style_real == FONT_DOUBLE) {
x += 10;
if (bg) {
for (int i = y; i < y + 16; i++) {
LCD_setPixel(x, i, !bw);
LCD_setPixel(x + 1, i, !bw);
}
}
x += 2;
if (x > (LCD_WIDTH - 10)) // Enables wrap around
{
x = 0;
y += 16;
}
}
else if (style_real == FONT_BOLD) {
x += 6;
if (bg) {
for (int i = y; i < y + 8; i++) {
LCD_setPixel(x, i, !bw);
LCD_setPixel(x + 1, i, !bw);
}
}
x += 1;
if (x > (LCD_WIDTH - 6)) // Enables wrap around
{
x = 0;
y += 8;
}
}
}
}
// This function will draw an array over the screen. (For now) the
// array must be the same size as the screen, covering the entirety
// of the display.
// Also, the array must reside in FLASH and declared with PROGMEM.
void LCD_setBitmap(const char *bitArray)
{
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
char c = bitArray[i];
LCD_displayMap[i] = c;
}
}
// This function clears the entire display either white (0) or
// black (1).
// The screen won't actually clear until you call updateDisplay()!
void LCD_clearDisplay(enum Color bw)
{
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
if (bw)
LCD_displayMap[i] = 0xFF;
else
LCD_displayMap[i] = 0;
}
}
void LCD_invertDisplayData()
{
/* Indirect, swap bits in displayMap option: */
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
LCD_displayMap[i] ^= 0xFF;
}
}

@ -0,0 +1,75 @@
/**
* TODO file description
*
* Created on 2020/01/05.
*/
#ifndef REFLOWER_DRAWING_H
#define REFLOWER_DRAWING_H
#include <stdint.h>
#include <stdbool.h>
#include "utf8.h"
enum Color {
WHITE = 0,
BLACK = 1,
};
// This function sets a pixel on displayMap to your preferred
// color. 1=Black, 0= white.
void LCD_setPixel(int x, int y, enum Color bw);
// setLine draws a line from x0,y0 to x1,y1 with the set color
void LCD_setLine(int x0, int y0, int x1, int y1, enum Color bw);
// setRect will draw a rectangle from x0,y0 top-left corner to
// a x1,y1 bottom-right corner. Can be filled with the fill
// parameter, and colored with bw.
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, enum Color bw);
// setCircle draws a circle centered around x0,y0 with a defined
// radius. The circle can be black or white. And have a line
// thickness ranging from 1 to the radius of the circle.
void LCD_setCircle (int x0, int y0, int radius, enum Color bw, int lineThickness);
/*
FONT FUNCTIONS
size = 1 ... normal
size = 2 ... 2x
size = 3 ... normal bold
size | 0x80 ... clear background behind characters
*/
enum FontStyle {
FONT_NORMAL = 1,
FONT_DOUBLE = 2,
FONT_BOLD = 3,
FONT_BG = 0x80,
};
// This function will draw a char (defined in the ASCII table
// near the beginning of this sketch) at a defined x and y).
// The color can be either black (1) or white (0).
void LCD_setChar(struct Utf8Char character, int x, int y, enum Color bw);
void LCD_setCharEx(struct Utf8Char character, int x, int y, enum Color bw, uint8_t style);
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStr(const char * dString, int x, int y, enum Color bw);
void LCD_setStrEx(const char *dString, int x, int y, enum Color bw, uint8_t style);
// This function clears the entire display either white (0) or
// black (1).
// The screen won't actually clear until you call updateDisplay()!
void LCD_clearDisplay(enum Color bw);
/* Invert colors (hard change in data; does NOT send to display immediately) */
void LCD_invertDisplayData();
#endif //REFLOWER_DRAWING_H

@ -4,142 +4,156 @@
#define FONT_EXTRAS_START (0x7e - 0x20 + 1) #define FONT_EXTRAS_START (0x7e - 0x20 + 1)
const struct FontSymbol font[] = { struct Utf8Symbol {
// ASCII from 0x20 to 0x7e are ordered at the beginning for quick access by index union {
{.symbol=" ", .graphic = {0x00, 0x00, 0x00, 0x00, 0x00}}, // 0x20 const char symbol[4];
{.symbol="!", .graphic = {0x00, 0x00, 0x5f, 0x00, 0x00}}, // 0x21 const uint32_t uint;
{.symbol="\"", .graphic = {0x00, 0x07, 0x00, 0x07, 0x00}}, // 0x22 };
{.symbol="#", .graphic = {0x14, 0x7f, 0x14, 0x7f, 0x14}}, // 0x23 struct FontGraphic graphic;
{.symbol="$", .graphic = {0x24, 0x2a, 0x7f, 0x2a, 0x12}}, // 0x24 };
{.symbol="%", .graphic = {0x23, 0x13, 0x08, 0x64, 0x62}}, // 0x25
{.symbol="&", .graphic = {0x36, 0x49, 0x55, 0x22, 0x50}}, // 0x26
{.symbol="'", .graphic = {0x00, 0x05, 0x03, 0x00, 0x00}}, // 0x27
{.symbol="(", .graphic = {0x00, 0x1c, 0x22, 0x41, 0x00}}, // 0x28
{.symbol=")", .graphic = {0x00, 0x41, 0x22, 0x1c, 0x00}}, // 0x29
{.symbol="*", .graphic = {0x14, 0x08, 0x3e, 0x08, 0x14}}, // 0x2a
{.symbol="+", .graphic = {0x08, 0x08, 0x3e, 0x08, 0x08}}, // 0x2b
{.symbol=",", .graphic = {0x00, 0x50, 0x30, 0x00, 0x00}}, // 0x2c
{.symbol="-", .graphic = {0x08, 0x08, 0x08, 0x08, 0x08}}, // 0x2d
{.symbol=".", .graphic = {0x00, 0x60, 0x60, 0x00, 0x00}}, // 0x2e
{.symbol="/", .graphic = {0x20, 0x10, 0x08, 0x04, 0x02}}, // 0x2f
{.symbol="0", .graphic = {0x3e, 0x51, 0x49, 0x45, 0x3e}}, // 0x30
{.symbol="1", .graphic = {0x00, 0x42, 0x7f, 0x40, 0x00}}, // 0x31
{.symbol="2", .graphic = {0x42, 0x61, 0x51, 0x49, 0x46}}, // 0x32
{.symbol="3", .graphic = {0x21, 0x41, 0x45, 0x4b, 0x31}}, // 0x33
{.symbol="4", .graphic = {0x18, 0x14, 0x12, 0x7f, 0x10}}, // 0x34
{.symbol="5", .graphic = {0x27, 0x45, 0x45, 0x45, 0x39}}, // 0x35
{.symbol="6", .graphic = {0x3c, 0x4a, 0x49, 0x49, 0x30}}, // 0x36
{.symbol="7", .graphic = {0x01, 0x71, 0x09, 0x05, 0x03}}, // 0x37
{.symbol="8", .graphic = {0x36, 0x49, 0x49, 0x49, 0x36}}, // 0x38
{.symbol="9", .graphic = {0x06, 0x49, 0x49, 0x29, 0x1e}}, // 0x39
{.symbol=":", .graphic = {0x00, 0x36, 0x36, 0x00, 0x00}}, // 0x3a
{.symbol=";", .graphic = {0x00, 0x56, 0x36, 0x00, 0x00}}, // 0x3b
{.symbol="<", .graphic = {0x08, 0x14, 0x22, 0x41, 0x00}}, // 0x3c
{.symbol="=", .graphic = {0x14, 0x14, 0x14, 0x14, 0x14}}, // 0x3d
{.symbol=">", .graphic = {0x00, 0x41, 0x22, 0x14, 0x08}}, // 0x3e
{.symbol="?", .graphic = {0x02, 0x01, 0x51, 0x09, 0x06}}, // 0x3f
{.symbol="@", .graphic = {0x32, 0x49, 0x79, 0x41, 0x3e}}, // 0x40
{.symbol="A", .graphic = {0x7e, 0x11, 0x11, 0x11, 0x7e}}, // 0x41
{.symbol="B", .graphic = {0x7f, 0x49, 0x49, 0x49, 0x36}}, // 0x42
{.symbol="C", .graphic = {0x3e, 0x41, 0x41, 0x41, 0x22}}, // 0x43
{.symbol="D", .graphic = {0x7f, 0x41, 0x41, 0x22, 0x1c}}, // 0x44
{.symbol="E", .graphic = {0x7f, 0x49, 0x49, 0x49, 0x41}}, // 0x45
{.symbol="F", .graphic = {0x7f, 0x09, 0x09, 0x09, 0x01}}, // 0x46
{.symbol="G", .graphic = {0x3e, 0x41, 0x49, 0x49, 0x7a}}, // 0x47
{.symbol="H", .graphic = {0x7f, 0x08, 0x08, 0x08, 0x7f}}, // 0x48
{.symbol="I", .graphic = {0x00, 0x41, 0x7f, 0x41, 0x00}}, // 0x49
{.symbol="J", .graphic = {0x20, 0x40, 0x41, 0x3f, 0x01}}, // 0x4a
{.symbol="K", .graphic = {0x7f, 0x08, 0x14, 0x22, 0x41}}, // 0x4b
{.symbol="L", .graphic = {0x7f, 0x40, 0x40, 0x40, 0x40}}, // 0x4c
{.symbol="M", .graphic = {0x7f, 0x02, 0x0c, 0x02, 0x7f}}, // 0x4d
{.symbol="N", .graphic = {0x7f, 0x04, 0x08, 0x10, 0x7f}}, // 0x4e
{.symbol="O", .graphic = {0x3e, 0x41, 0x41, 0x41, 0x3e}}, // 0x4f
{.symbol="P", .graphic = {0x7f, 0x09, 0x09, 0x09, 0x06}}, // 0x50
{.symbol="Q", .graphic = {0x3e, 0x41, 0x51, 0x21, 0x5e}}, // 0x51
{.symbol="R", .graphic = {0x7f, 0x09, 0x19, 0x29, 0x46}}, // 0x52
{.symbol="S", .graphic = {0x46, 0x49, 0x49, 0x49, 0x31}}, // 0x53
{.symbol="T", .graphic = {0x01, 0x01, 0x7f, 0x01, 0x01}}, // 0x54
{.symbol="U", .graphic = {0x3f, 0x40, 0x40, 0x40, 0x3f}}, // 0x55
{.symbol="V", .graphic = {0x1f, 0x20, 0x40, 0x20, 0x1f}}, // 0x56
{.symbol="W", .graphic = {0x3f, 0x40, 0x38, 0x40, 0x3f}}, // 0x57
{.symbol="X", .graphic = {0x63, 0x14, 0x08, 0x14, 0x63}}, // 0x58
{.symbol="Y", .graphic = {0x07, 0x08, 0x70, 0x08, 0x07}}, // 0x59
{.symbol="Z", .graphic = {0x61, 0x51, 0x49, 0x45, 0x43}}, // 0x5a
{.symbol="[", .graphic = {0x00, 0x7f, 0x41, 0x41, 0x00}}, // 0x5b
{.symbol="\\", .graphic = {0x02, 0x04, 0x08, 0x10, 0x20}}, // 0x5c
{.symbol="]", .graphic = {0x00, 0x41, 0x41, 0x7f, 0x00}}, // 0x5d
{.symbol="^", .graphic = {0x04, 0x02, 0x01, 0x02, 0x04}}, // 0x5e
{.symbol="_", .graphic = {0x40, 0x40, 0x40, 0x40, 0x40}}, // 0x5f
{.symbol="`", .graphic = {0x00, 0x01, 0x02, 0x04, 0x00}}, // 0x60
{.symbol="a", .graphic = {0x20, 0x54, 0x54, 0x54, 0x78}}, // 0x61
{.symbol="b", .graphic = {0x7f, 0x48, 0x44, 0x44, 0x38}}, // 0x62
{.symbol="c", .graphic = {0x38, 0x44, 0x44, 0x44, 0x20}}, // 0x63
{.symbol="d", .graphic = {0x38, 0x44, 0x44, 0x48, 0x7f}}, // 0x64
{.symbol="e", .graphic = {0x38, 0x54, 0x54, 0x54, 0x18}}, // 0x65
{.symbol="f", .graphic = {0x08, 0x7e, 0x09, 0x01, 0x02}}, // 0x66
{.symbol="g", .graphic = {0x0c, 0x52, 0x52, 0x52, 0x3e}}, // 0x67
{.symbol="h", .graphic = {0x7f, 0x08, 0x04, 0x04, 0x78}}, // 0x68
{.symbol="i", .graphic = {0x00, 0x44, 0x7d, 0x40, 0x00}}, // 0x69
{.symbol="j", .graphic = {0x20, 0x40, 0x44, 0x3d, 0x00}}, // 0x6a
{.symbol="k", .graphic = {0x7f, 0x10, 0x28, 0x44, 0x00}}, // 0x6b
{.symbol="l", .graphic = {0x00, 0x41, 0x7f, 0x40, 0x00}}, // 0x6c
{.symbol="m", .graphic = {0x7c, 0x04, 0x18, 0x04, 0x78}}, // 0x6d
{.symbol="n", .graphic = {0x7c, 0x08, 0x04, 0x04, 0x78}}, // 0x6e
{.symbol="o", .graphic = {0x38, 0x44, 0x44, 0x44, 0x38}}, // 0x6f
{.symbol="p", .graphic = {0x7c, 0x14, 0x14, 0x14, 0x08}}, // 0x70
{.symbol="q", .graphic = {0x08, 0x14, 0x14, 0x18, 0x7c}}, // 0x71
{.symbol="r", .graphic = {0x7c, 0x08, 0x04, 0x04, 0x08}}, // 0x72
{.symbol="s", .graphic = {0x48, 0x54, 0x54, 0x54, 0x20}}, // 0x73
{.symbol="t", .graphic = {0x04, 0x3f, 0x44, 0x40, 0x20}}, // 0x74
{.symbol="u", .graphic = {0x3c, 0x40, 0x40, 0x20, 0x7c}}, // 0x75
{.symbol="v", .graphic = {0x1c, 0x20, 0x40, 0x20, 0x1c}}, // 0x76
{.symbol="w", .graphic = {0x3c, 0x40, 0x30, 0x40, 0x3c}}, // 0x77
{.symbol="x", .graphic = {0x44, 0x28, 0x10, 0x28, 0x44}}, // 0x78
{.symbol="y", .graphic = {0x0c, 0x50, 0x50, 0x50, 0x3c}}, // 0x79
{.symbol="z", .graphic = {0x44, 0x64, 0x54, 0x4c, 0x44}}, // 0x7a
{.symbol="{", .graphic = {0x00, 0x08, 0x36, 0x41, 0x00}}, // 0x7b
{.symbol="|", .graphic = {0x00, 0x00, 0x7f, 0x00, 0x00}}, // 0x7c
{.symbol="}", .graphic = {0x00, 0x41, 0x36, 0x08, 0x00}}, // 0x7d
{.symbol="~", .graphic = {0x10, 0x08, 0x08, 0x10, 0x08}}, // 0x7e
// start of UTF8 glyphs. These can include custom graphics mapped to codepoints // ASCII symbols are stored as bare graphic to reduce ROM size
{.symbol="<EFBFBD>", .graphic = {0xFF, 0x81, 0x81, 0x81, 0xFF}}, // box const struct FontGraphic ascii_glyphs[95] = {
{{0x00, 0x00, 0x00, 0x00, 0x00}}, // " 0x20
{{0x00, 0x00, 0x5f, 0x00, 0x00}}, // ! 0x21
{{0x00, 0x07, 0x00, 0x07, 0x00}}, // \" 0x22
{{0x14, 0x7f, 0x14, 0x7f, 0x14}}, // # 0x23
{{0x24, 0x2a, 0x7f, 0x2a, 0x12}}, // $ 0x24
{{0x23, 0x13, 0x08, 0x64, 0x62}}, // % 0x25
{{0x36, 0x49, 0x55, 0x22, 0x50}}, // & 0x26
{{0x00, 0x05, 0x03, 0x00, 0x00}}, // ' 0x27
{{0x00, 0x1c, 0x22, 0x41, 0x00}}, // ( 0x28
{{0x00, 0x41, 0x22, 0x1c, 0x00}}, // ) 0x29
{{0x14, 0x08, 0x3e, 0x08, 0x14}}, // * 0x2a
{{0x08, 0x08, 0x3e, 0x08, 0x08}}, // + 0x2b
{{0x00, 0x50, 0x30, 0x00, 0x00}}, // , 0x2c
{{0x08, 0x08, 0x08, 0x08, 0x08}}, // - 0x2d
{{0x00, 0x60, 0x60, 0x00, 0x00}}, // . 0x2e
{{0x20, 0x10, 0x08, 0x04, 0x02}}, // / 0x2f
{{0x3e, 0x51, 0x49, 0x45, 0x3e}}, // 0 0x30
{{0x00, 0x42, 0x7f, 0x40, 0x00}}, // 1 0x31
{{0x42, 0x61, 0x51, 0x49, 0x46}}, // 2 0x32
{{0x21, 0x41, 0x45, 0x4b, 0x31}}, // 3 0x33
{{0x18, 0x14, 0x12, 0x7f, 0x10}}, // 4 0x34
{{0x27, 0x45, 0x45, 0x45, 0x39}}, // 5 0x35
{{0x3c, 0x4a, 0x49, 0x49, 0x30}}, // 6 0x36
{{0x01, 0x71, 0x09, 0x05, 0x03}}, // 7 0x37
{{0x36, 0x49, 0x49, 0x49, 0x36}}, // 8 0x38
{{0x06, 0x49, 0x49, 0x29, 0x1e}}, // 9 0x39
{{0x00, 0x36, 0x36, 0x00, 0x00}}, // : 0x3a
{{0x00, 0x56, 0x36, 0x00, 0x00}}, // ; 0x3b
{{0x08, 0x14, 0x22, 0x41, 0x00}}, // < 0x3c
{{0x14, 0x14, 0x14, 0x14, 0x14}}, // = 0x3d
{{0x00, 0x41, 0x22, 0x14, 0x08}}, // > 0x3e
{{0x02, 0x01, 0x51, 0x09, 0x06}}, // ? 0x3f
{{0x32, 0x49, 0x79, 0x41, 0x3e}}, // @ 0x40
{{0x7e, 0x11, 0x11, 0x11, 0x7e}}, // A 0x41
{{0x7f, 0x49, 0x49, 0x49, 0x36}}, // B 0x42
{{0x3e, 0x41, 0x41, 0x41, 0x22}}, // C 0x43
{{0x7f, 0x41, 0x41, 0x22, 0x1c}}, // D 0x44
{{0x7f, 0x49, 0x49, 0x49, 0x41}}, // E 0x45
{{0x7f, 0x09, 0x09, 0x09, 0x01}}, // F 0x46
{{0x3e, 0x41, 0x49, 0x49, 0x7a}}, // G 0x47
{{0x7f, 0x08, 0x08, 0x08, 0x7f}}, // H 0x48
{{0x00, 0x41, 0x7f, 0x41, 0x00}}, // I 0x49
{{0x20, 0x40, 0x41, 0x3f, 0x01}}, // J 0x4a
{{0x7f, 0x08, 0x14, 0x22, 0x41}}, // K 0x4b
{{0x7f, 0x40, 0x40, 0x40, 0x40}}, // L 0x4c
{{0x7f, 0x02, 0x0c, 0x02, 0x7f}}, // M 0x4d
{{0x7f, 0x04, 0x08, 0x10, 0x7f}}, // N 0x4e
{{0x3e, 0x41, 0x41, 0x41, 0x3e}}, // O 0x4f
{{0x7f, 0x09, 0x09, 0x09, 0x06}}, // P 0x50
{{0x3e, 0x41, 0x51, 0x21, 0x5e}}, // Q 0x51
{{0x7f, 0x09, 0x19, 0x29, 0x46}}, // R 0x52
{{0x46, 0x49, 0x49, 0x49, 0x31}}, // S 0x53
{{0x01, 0x01, 0x7f, 0x01, 0x01}}, // T 0x54
{{0x3f, 0x40, 0x40, 0x40, 0x3f}}, // U 0x55
{{0x1f, 0x20, 0x40, 0x20, 0x1f}}, // V 0x56
{{0x3f, 0x40, 0x38, 0x40, 0x3f}}, // W 0x57
{{0x63, 0x14, 0x08, 0x14, 0x63}}, // X 0x58
{{0x07, 0x08, 0x70, 0x08, 0x07}}, // Y 0x59
{{0x61, 0x51, 0x49, 0x45, 0x43}}, // Z 0x5a
{{0x00, 0x7f, 0x41, 0x41, 0x00}}, // [ 0x5b
{{0x02, 0x04, 0x08, 0x10, 0x20}}, // \\ 0x5c
{{0x00, 0x41, 0x41, 0x7f, 0x00}}, // ] 0x5d
{{0x04, 0x02, 0x01, 0x02, 0x04}}, // ^ 0x5e
{{0x40, 0x40, 0x40, 0x40, 0x40}}, // _ 0x5f
{{0x00, 0x01, 0x02, 0x04, 0x00}}, // ` 0x60
{{0x20, 0x54, 0x54, 0x54, 0x78}}, // a 0x61
{{0x7f, 0x48, 0x44, 0x44, 0x38}}, // b 0x62
{{0x38, 0x44, 0x44, 0x44, 0x20}}, // c 0x63
{{0x38, 0x44, 0x44, 0x48, 0x7f}}, // d 0x64
{{0x38, 0x54, 0x54, 0x54, 0x18}}, // e 0x65
{{0x08, 0x7e, 0x09, 0x01, 0x02}}, // f 0x66
{{0x0c, 0x52, 0x52, 0x52, 0x3e}}, // g 0x67
{{0x7f, 0x08, 0x04, 0x04, 0x78}}, // h 0x68
{{0x00, 0x44, 0x7d, 0x40, 0x00}}, // i 0x69
{{0x20, 0x40, 0x44, 0x3d, 0x00}}, // j 0x6a
{{0x7f, 0x10, 0x28, 0x44, 0x00}}, // k 0x6b
{{0x00, 0x41, 0x7f, 0x40, 0x00}}, // l 0x6c
{{0x7c, 0x04, 0x18, 0x04, 0x78}}, // m 0x6d
{{0x7c, 0x08, 0x04, 0x04, 0x78}}, // n 0x6e
{{0x38, 0x44, 0x44, 0x44, 0x38}}, // o 0x6f
{{0x7c, 0x14, 0x14, 0x14, 0x08}}, // p 0x70
{{0x08, 0x14, 0x14, 0x18, 0x7c}}, // q 0x71
{{0x7c, 0x08, 0x04, 0x04, 0x08}}, // r 0x72
{{0x48, 0x54, 0x54, 0x54, 0x20}}, // s 0x73
{{0x04, 0x3f, 0x44, 0x40, 0x20}}, // t 0x74
{{0x3c, 0x40, 0x40, 0x20, 0x7c}}, // u 0x75
{{0x1c, 0x20, 0x40, 0x20, 0x1c}}, // v 0x76
{{0x3c, 0x40, 0x30, 0x40, 0x3c}}, // w 0x77
{{0x44, 0x28, 0x10, 0x28, 0x44}}, // x 0x78
{{0x0c, 0x50, 0x50, 0x50, 0x3c}}, // y 0x79
{{0x44, 0x64, 0x54, 0x4c, 0x44}}, // z 0x7a
{{0x00, 0x08, 0x36, 0x41, 0x00}}, // { 0x7b
{{0x00, 0x00, 0x7f, 0x00, 0x00}}, // | 0x7c
{{0x00, 0x41, 0x36, 0x08, 0x00}}, // } 0x7d
{{0x10, 0x08, 0x08, 0x10, 0x08}}, // ~ 0x7e
};
{.symbol="×", .graphic = { 0x22, 0x14, 0x08, 0x14, 0x22 }}, // cross // utf8 characters list ending with empty struct
{.symbol="", .graphic = { 0x08, 0x04, 0x3e, 0x04, 0x08 }}, // arrow_up const struct Utf8Symbol utf8_glyphs[] = {
{.symbol="", .graphic = { 0x08, 0x10, 0x3e, 0x10, 0x08 }}, // arrow_down { .symbol="<EFBFBD>", {{0xFE, 0x82, 0x82, 0x82, 0xFE}} }, // box
{.symbol="", .graphic = { 0x08, 0x1c, 0x2a, 0x08, 0x08 }}, // arrow_left { .symbol="×", {{0x22, 0x14, 0x08, 0x14, 0x22}} }, // cross
{.symbol="", .graphic = { 0x08, 0x08, 0x2a, 0x1c, 0x08 }}, // arrow_right { .symbol="", {{0x08, 0x04, 0x3e, 0x04, 0x08}} }, // arrow_up
{.symbol="", .graphic = { 0x1c, 0x22, 0x2e, 0x2a, 0x1c }}, // clock { .symbol="", {{0x08, 0x10, 0x3e, 0x10, 0x08}} }, // arrow_down
{.symbol="", .graphic = { 0x63, 0x55, 0x4d, 0x55, 0x63 }}, // hourglass { .symbol="", {{0x08, 0x1c, 0x2a, 0x08, 0x08}} }, // arrow_left
{.symbol="", .graphic = { 0x1c, 0x22, 0x2a, 0x22, 0x1c }}, // wheel { .symbol="", {{0x08, 0x08, 0x2a, 0x1c, 0x08}} }, // arrow_right
{.symbol="", .graphic = { 0x10, 0x38, 0x54, 0x10, 0x1e }}, // return { .symbol="", {{0x1c, 0x22, 0x2e, 0x2a, 0x1c}} }, // clock
{.symbol="🌡", .graphic = { 0x60, 0x9e, 0x81, 0x9e, 0x6a }}, // thermometer { .symbol="", {{0x63, 0x55, 0x4d, 0x55, 0x63}} }, // hourglass
{.symbol="°", .graphic = { 0x00, 0x07, 0x05, 0x07, 0x00 }}, // degree { .symbol="", {{0x1c, 0x22, 0x2a, 0x22, 0x1c}} }, // wheel
{.symbol="🔙", .graphic = { 0x04, 0x4e, 0x55, 0x44, 0x38 }}, // back { .symbol="", {{0x10, 0x38, 0x54, 0x10, 0x1e}} }, // return
{.symbol="", .graphic = { 0x7f, 0x3e, 0x1c, 0x08, 0x00 }}, // tri_right { .symbol="🌡", {{0x60, 0x9e, 0x81, 0x9e, 0x6a}} }, // thermometer
{.symbol="", .graphic = { 0x00, 0x08, 0x1c, 0x3e, 0x7f }}, // tri_left { .symbol="°", {{0x00, 0x07, 0x05, 0x07, 0x00}} }, // degree
{ .symbol="🔙", {{0x04, 0x4e, 0x55, 0x44, 0x38}} }, // back
{ .symbol="", {{0x7f, 0x3e, 0x1c, 0x08, 0x00}} }, // tri_right
{ .symbol="", {{0x00, 0x08, 0x1c, 0x3e, 0x7f}} }, // tri_left
// End of the list // End of the list
{}, {},
}; };
const struct FontSymbol *Font_GetSymbol(struct Utf8Char ch) { const struct FontGraphic *Font_GetSymbol(struct Utf8Char ch) {
if (ch.bytes[0] < ' ') { if (ch.bytes[0] < 32) {
return &font[FONT_EXTRAS_START]; // replacement character // low ASCII is not supported.
goto fail;
} }
if (ch.bytes[0] < 127) { if (ch.bytes[0] < 127) {
return &font[ch.bytes[0] - 0x20]; // valid ASCII at hard positions.
// In this case only the first byte is significant.
return &ascii_glyphs[ch.bytes[0] - 32];
} }
const struct FontSymbol *sym = &font[FONT_EXTRAS_START]; // search UTF8
const struct Utf8Symbol *sym = &utf8_glyphs[0];
while (sym->symbol[0]) { while (sym->symbol[0]) {
if (sym->uint == ch.uint) { if (sym->uint == ch.uint) {
return sym; return &sym->graphic;
} }
sym++; sym++;
} }
return &font[FONT_EXTRAS_START]; // replacement character fail:
return &utf8_glyphs[0].graphic; // replacement character
} }

@ -1,24 +1,20 @@
/** /**
* TODO file description * UTF-8 capable bitmap font
* *
* Created on 2020/01/04. * Created on 2020/01/04.
*/ */
#ifndef REFLOWER_FONT_H #ifndef GFX_FONT_H
#define REFLOWER_FONT_H #define GFX_FONT_H
#include "font.h" #include "font.h"
#include "utf8.h" #include "utf8.h"
#include <stdint.h> #include <stdint.h>
struct FontSymbol { struct FontGraphic {
union { uint8_t columns[5];
const char symbol[4];
const uint32_t uint;
};
uint8_t graphic[5];
}; };
const struct FontSymbol *Font_GetSymbol(struct Utf8Char ch); const struct FontGraphic *Font_GetSymbol(struct Utf8Char ch);
#endif //REFLOWER_FONT_H #endif //GFX_FONT_H

@ -1,9 +1,6 @@
#include <driver/gpio.h> #include <driver/gpio.h>
#include <arch/cc.h>
#include <driver/spi_master.h> #include <driver/spi_master.h>
#include "nokia.h" #include "nokia.h"
#include "utf8.h"
#include "font.h"
#include <string.h> #include <string.h>
/* Pin definitions: /* Pin definitions:
@ -38,7 +35,7 @@ to the PCD8544.
Because the PCD8544 won't let us write individual pixels at a Because the PCD8544 won't let us write individual pixels at a
time, this is how we can make targeted changes to the display. */ time, this is how we can make targeted changes to the display. */
static uint8_t displayMap[LCD_WIDTH * LCD_HEIGHT / 8]; uint8_t LCD_displayMap[LCD_WIDTH * LCD_HEIGHT / 8] = {};
// There are two memory banks in the LCD, data/RAM and commands. // There are two memory banks in the LCD, data/RAM and commands.
// This function sets the DC pin high or low depending, and then // This function sets the DC pin high or low depending, and then
@ -72,298 +69,6 @@ static void LCD_SendByte(bool data_or_command, uint8_t data)
assert(ret == ESP_OK); //Should have had no issues. assert(ret == ESP_OK); //Should have had no issues.
} }
// This function sets a pixel on displayMap to your preferred
// color. 1=Black, 0= white.
void LCD_setPixel(int x, int y, bool bw)
{
// First, double check that the coordinate is in range.
if ((x >= 0) && (x < LCD_WIDTH) && (y >= 0) && (y < LCD_HEIGHT)) {
uint8_t shift = y % 8;
if (bw) // If black, set the bit.
displayMap[x + (y / 8) * LCD_WIDTH] |= 1 << shift;
else // If white clear the bit.
displayMap[x + (y / 8) * LCD_WIDTH] &= ~(1 << shift);
}
}
// setLine draws a line from x0,y0 to x1,y1 with the set color.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setLine(int x0, int y0, int x1, int y1, bool bw)
{
int dy = y1 - y0; // Difference between y0 and y1
int dx = x1 - x0; // Difference between x0 and x1
int stepx, stepy;
if (dy < 0) {
dy = -dy;
stepy = -1;
}
else
stepy = 1;
if (dx < 0) {
dx = -dx;
stepx = -1;
}
else
stepx = 1;
dy <<= 1; // dy is now 2*dy
dx <<= 1; // dx is now 2*dx
LCD_setPixel(x0, y0, bw); // Draw the first pixel.
if (dx > dy) {
int fraction = dy - (dx >> 1);
while (x0 != x1) {
if (fraction >= 0) {
y0 += stepy;
fraction -= dx;
}
x0 += stepx;
fraction += dy;
LCD_setPixel(x0, y0, bw);
}
}
else {
int fraction = dx - (dy >> 1);
while (y0 != y1) {
if (fraction >= 0) {
x0 += stepx;
fraction -= dy;
}
y0 += stepy;
fraction += dx;
LCD_setPixel(x0, y0, bw);
}
}
}
// setRect will draw a rectangle from x0,y0 top-left corner to
// a x1,y1 bottom-right corner. Can be filled with the fill
// parameter, and colored with bw.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, bool bw)
{
// check if the rectangle is to be filled
if (fill == 1) {
int xDiff;
if (x0 > x1)
xDiff = x0 - x1; //Find the difference between the x vars
else
xDiff = x1 - x0;
while (xDiff > 0) {
LCD_setLine(x0, y0, x0, y1, bw);
if (x0 > x1)
x0--;
else
x0++;
xDiff--;
}
}
else {
// best way to draw an unfilled rectangle is to draw four lines
LCD_setLine(x0, y0, x1, y0, bw);
LCD_setLine(x0, y1, x1, y1, bw);
LCD_setLine(x0, y0, x0, y1, bw);
LCD_setLine(x1, y0, x1, y1, bw);
}
}
// setCircle draws a circle centered around x0,y0 with a defined
// radius. The circle can be black or white. And have a line
// thickness ranging from 1 to the radius of the circle.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setCircle(int x0, int y0, int radius, bool bw, int lineThickness)
{
for (int r = 0; r < lineThickness; r++) {
int f = 1 - radius;
int ddF_x = 0;
int ddF_y = -2 * radius;
int x = 0;
int y = radius;
LCD_setPixel(x0, y0 + radius, bw);
LCD_setPixel(x0, y0 - radius, bw);
LCD_setPixel(x0 + radius, y0, bw);
LCD_setPixel(x0 - radius, y0, bw);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x + 1;
LCD_setPixel(x0 + x, y0 + y, bw);
LCD_setPixel(x0 - x, y0 + y, bw);
LCD_setPixel(x0 + x, y0 - y, bw);
LCD_setPixel(x0 - x, y0 - y, bw);
LCD_setPixel(x0 + y, y0 + x, bw);
LCD_setPixel(x0 - y, y0 + x, bw);
LCD_setPixel(x0 + y, y0 - x, bw);
LCD_setPixel(x0 - y, y0 - x, bw);
}
radius--;
}
}
// This function will draw a char (defined in the ASCII table
// near the beginning of this sketch) at a defined x and y).
// The color can be either black (1) or white (0).
void LCD_setChar(struct Utf8Char character, int x, int y, bool bw)
{
LCD_setCharEx(character, x, y, bw, 1);
}
void LCD_setCharEx(struct Utf8Char character, int x, int y, bool bw, uint8_t size)
{
const struct FontSymbol *symbol = Font_GetSymbol(character);
bool backfill = size &0x80;
size &= 0x7F;
uint8_t column; // temp byte to store character's column bitmap
for (int i = 0; i < 5; i++) // 5 columns (x) per character
{
column = symbol->graphic[i];
for (int j = 0; j < 8; j++) // 8 rows (y) per character
{
bool bit = column & (0x01 << j);
if (size == 1) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i, y + j, bw);
} else if(backfill) {
LCD_setPixel(x + i, y + j, !bw);
}
} else if (size == 2) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i * 2, y + j * 2, bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2, bw);
LCD_setPixel(x + i * 2, y + j * 2 + 1, bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, bw);
} else if(backfill) {
LCD_setPixel(x + i * 2, y + j * 2, !bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2, !bw);
LCD_setPixel(x + i * 2, y + j * 2 + 1, !bw);
LCD_setPixel(x + i * 2 + 1, y + j * 2 + 1, !bw);
}
} else if (size == 3) {
if (bit) {// test bits to set pixels
LCD_setPixel(x + i, y + j, bw);
LCD_setPixel(x + i + 1, y + j, bw);
} else if(backfill) {
LCD_setPixel(x + i, y + j, !bw);
LCD_setPixel(x + i + 1, y + j, !bw);
}
}
}
}
}
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStr(const char *dString, int x, int y, bool bw)
{
LCD_setStrEx(dString, x, y, bw, 1);
}
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStrEx(const char *dString, int x, int y, bool bw, uint8_t size)
{
struct Utf8Iterator iter;
Utf8Iterator_Start(&iter, dString);
bool backfill = size & 0x80;
uint8_t size_real = size & 0x7F;
struct Utf8Char uchar;
while ((uchar = Utf8Iterator_Next(&iter)).uint) {
LCD_setCharEx(uchar, x, y, bw, size);
if (size_real == 1) {
x += 5;
if (backfill) {
for (int i = y; i < y + 8; i++) {
LCD_setPixel(x, i, !bw);
}
}
x++;
if (x > (LCD_WIDTH - 5)) // Enables wrap around
{
x = 0;
y += 8;
}
} else if (size_real == 2) {
x += 10;
if (backfill) {
for (int i = y; i < y + 16; i++) {
LCD_setPixel(x, i, !bw);
LCD_setPixel(x + 1, i, !bw);
}
}
x+=2;
if (x > (LCD_WIDTH - 10)) // Enables wrap around
{
x = 0;
y += 16;
}
} else if (size_real == 3) {
x += 6;
if (backfill) {
for (int i = y; i < y + 8; i++) {
LCD_setPixel(x, i, !bw);
LCD_setPixel(x + 1, i, !bw);
}
}
x+=1;
if (x > (LCD_WIDTH - 6)) // Enables wrap around
{
x = 0;
y += 8;
}
}
}
}
// This function will draw an array over the screen. (For now) the
// array must be the same size as the screen, covering the entirety
// of the display.
// Also, the array must reside in FLASH and declared with PROGMEM.
void LCD_setBitmap(const char *bitArray)
{
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
char c = bitArray[i];
displayMap[i] = c;
}
}
// This function clears the entire display either white (0) or
// black (1).
// The screen won't actually clear until you call updateDisplay()!
void LCD_clearDisplay(bool bw)
{
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
if (bw)
displayMap[i] = 0xFF;
else
displayMap[i] = 0;
}
}
// Helpful function to directly command the LCD to go to a // Helpful function to directly command the LCD to go to a
// specific x,y coordinate. // specific x,y coordinate.
@ -382,7 +87,7 @@ void LCD_updateDisplay()
{ {
spi_device_acquire_bus(hSPI, portMAX_DELAY); spi_device_acquire_bus(hSPI, portMAX_DELAY);
gotoXY(0, 0); gotoXY(0, 0);
LCD_SendBytes(LCD_DATA, &displayMap[0], LCD_WIDTH * (LCD_HEIGHT / 8)); LCD_SendBytes(LCD_DATA, &LCD_displayMap[0], LCD_WIDTH * (LCD_HEIGHT / 8));
spi_device_release_bus(hSPI); spi_device_release_bus(hSPI);
} }
@ -405,14 +110,6 @@ void LCD_invertDisplay(bool invert)
LCD_SendByte(LCD_COMMAND, 0x0C | invert); LCD_SendByte(LCD_COMMAND, 0x0C | invert);
} }
void LCD_invertDisplayData()
{
/* Indirect, swap bits in displayMap option: */
for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
displayMap[i] ^= 0xFF;
}
}
//This function is called (in irq context!) just before a transmission starts. It will //This function is called (in irq context!) just before a transmission starts. It will
//set the D/C line to the value indicated in the user field. //set the D/C line to the value indicated in the user field.
void lcd_spi_pre_transfer_callback(spi_transaction_t *t) void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
@ -424,11 +121,8 @@ void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
//This sends the magical commands to the PCD8544 //This sends the magical commands to the PCD8544
void LCD_setup(void) void LCD_setup(void)
{ {
// clear the display array
LCD_clearDisplay(0);
gpio_config_t output = { gpio_config_t output = {
.pin_bit_mask = (1<<scePin) | (1<<rstPin) | (1<<dcPin) | (1<<sdinPin) | (1<<sclkPin), .pin_bit_mask = (1 << scePin) | (1 << rstPin) | (1 << dcPin) | (1 << sdinPin) | (1 << sclkPin),
.mode = GPIO_MODE_OUTPUT .mode = GPIO_MODE_OUTPUT
}; };
gpio_config(&output); gpio_config(&output);
@ -479,8 +173,6 @@ void LCD_setup(void)
} }
spi_device_release_bus(hSPI); spi_device_release_bus(hSPI);
// show the blank screen (display is in indefined state after boot) // show the blank screen (display is in an undefined state after boot)
LCD_updateDisplay(); LCD_updateDisplay();
} }

@ -1,3 +1,10 @@
/**
* Driver for Nokia 5110 SPI LCD display
*/
#ifndef GFX_NOKIA
#define GFX_NOKIA
/* Pin definitions: /* Pin definitions:
Most of these pins can be moved to any digital or analog pin. Most of these pins can be moved to any digital or analog pin.
DN(MOSI)and SCLK should be left where they are (SPI pins). The DN(MOSI)and SCLK should be left where they are (SPI pins). The
@ -13,57 +20,7 @@ LED (backlight) pin should remain on a PWM-capable pin. */
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "utf8.h" #include "utf8.h"
#include "display_spec.h"
#define LCD_WIDTH 84 // Note: x-coordinates go wide
#define LCD_HEIGHT 48 // Note: y-coordinates go high
#define WHITE 0 // For drawing pixels. A 0 draws white.
#define BLACK 1 // A 1 draws black.
// This function sets a pixel on displayMap to your preferred
// color. 1=Black, 0= white.
void LCD_setPixel(int x, int y, bool bw);
// setLine draws a line from x0,y0 to x1,y1 with the set color
void LCD_setLine(int x0, int y0, int x1, int y1, bool bw);
// setRect will draw a rectangle from x0,y0 top-left corner to
// a x1,y1 bottom-right corner. Can be filled with the fill
// parameter, and colored with bw.
void LCD_setRect(int x0, int y0, int x1, int y1, bool fill, bool bw);
// setCircle draws a circle centered around x0,y0 with a defined
// radius. The circle can be black or white. And have a line
// thickness ranging from 1 to the radius of the circle.
void LCD_setCircle (int x0, int y0, int radius, bool bw, int lineThickness);
/*
FONT FUNCTIONS
size = 1 ... normal
size = 2 ... 2x
size = 3 ... normal bold
size | 0x80 ... clear background behind characters
*/
// This function will draw a char (defined in the ASCII table
// near the beginning of this sketch) at a defined x and y).
// The color can be either black (1) or white (0).
void LCD_setChar(struct Utf8Char character, int x, int y, bool bw);
void LCD_setCharEx(struct Utf8Char character, int x, int y, bool bw, uint8_t size);
// setStr draws a string of characters, calling setChar with
// progressive coordinates until it's done.
// This function was grabbed from the SparkFun ColorLCDShield
// library.
void LCD_setStr(const char * dString, int x, int y, bool bw);
void LCD_setStrEx(const char *dString, int x, int y, bool bw, uint8_t size);
// This function clears the entire display either white (0) or
// black (1).
// The screen won't actually clear until you call updateDisplay()!
void LCD_clearDisplay(bool bw);
// This will actually draw on the display, whatever is currently // This will actually draw on the display, whatever is currently
// in the displayMap array. // in the displayMap array.
@ -74,10 +31,9 @@ void LCD_updateDisplay();
void LCD_setContrast(uint8_t contrast); void LCD_setContrast(uint8_t contrast);
/* Invert colors */ /* Invert colors */
void LCD_invertDisplay(bool in_data); void LCD_invertDisplay(bool invert);
/* Invert colors (hard change in data; does NOT send to display immediately) */
void LCD_invertDisplayData();
//This sends the magical commands to the PCD8544 //This sends the magical commands to the PCD8544
void LCD_setup(void); void LCD_setup(void);
#endif // GFX_NOKIA

@ -1,6 +1,5 @@
#include <stdint.h> #include <stdint.h>
#include "utf8.h" #include "utf8.h"
#include <string.h>
// //
// Created by MightyPork on 2017/08/20. // Created by MightyPork on 2017/08/20.
@ -9,7 +8,7 @@
// into a screen cell. // into a screen cell.
// //
const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) { .uint = 0 }; const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) {.uint = 0};
// Code Points First Byte Second Byte Third Byte Fourth Byte // Code Points First Byte Second Byte Third Byte Fourth Byte
// U+0000 - U+007F 00 - 7F // U+0000 - U+007F 00 - 7F
@ -30,7 +29,7 @@ struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c)
{ {
uint8_t *bytes = self->buffer.bytes; uint8_t *bytes = self->buffer.bytes;
uint8_t uc = (uint8_t)c; uint8_t uc = (uint8_t) c;
// collecting unicode glyphs... // collecting unicode glyphs...
if (uc & 0x80) { if (uc & 0x80) {
if (self->utf_len == 0) { if (self->utf_len == 0) {
@ -98,12 +97,8 @@ fail:
return EMPTY_CHAR; return EMPTY_CHAR;
} }
void Utf8Iterator_Start(struct Utf8Iterator *self, const char *source) { struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self)
memset(self, 0, sizeof(struct Utf8Iterator)); {
self->source = source;
}
struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self) {
char c; char c;
struct Utf8Char uchar; struct Utf8Char uchar;
while ((c = *self->source++) != 0) { while ((c = *self->source++) != 0) {

@ -1,5 +1,5 @@
/** /**
* TODO file description * UTF-8 string parsing and character iteration
* *
* Created on 2020/01/04. * Created on 2020/01/04.
*/ */
@ -10,33 +10,47 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
/**
* UTF-8 encoded character.
*/
struct Utf8Char { struct Utf8Char {
union { union {
/** character bytes; padded by zero bytes if shorter than 4 */
uint8_t bytes[4]; uint8_t bytes[4];
/** u32 view of the bytes */
uint32_t uint; uint32_t uint;
}; };
}; };
/** UTF8 string parser internal state */
struct Utf8Parser { struct Utf8Parser {
/** UTF-8 bytes buffer */
struct Utf8Char buffer; struct Utf8Char buffer;
/** Currently collected UTF-8 character length */
uint8_t utf_len; uint8_t utf_len;
/** Position in the current character */
uint8_t utf_j; uint8_t utf_j;
}; };
static inline void Utf8Parser_Clear(struct Utf8Parser *self) {
self->buffer.uint = 0;
self->utf_j = 0;
self->utf_len = 0;
}
/** /**
* Utf8 character iterator. * Utf8 character iterator.
* *
* Usage: * Usage:
* struct Utf8Iterator iter; * struct Utf8Iterator iter;
* Utf8Iterator_Start(&iter, myString); * Utf8Iterator_Init(&iter, myString);
* *
* union Utf8Char uchar; * union Utf8Char uchar;
* while ((uchar = Utf8Iterator_Next(&iter)).uint) { * while ((uchar = Utf8Iterator_Next(&iter)).uint) {
* // do something with the char * // do something with the char
* } * }
* *
* // Free myString if needed. * // Free myString if needed, it is not mutated.
* // The iterator does not need any cleanup if it lives on stack.
*/ */
struct Utf8Iterator { struct Utf8Iterator {
/* Characters to parse. The pointer is advanced as the iterator progresses. */ /* Characters to parse. The pointer is advanced as the iterator progresses. */
@ -44,7 +58,10 @@ struct Utf8Iterator {
struct Utf8Parser parser; struct Utf8Parser parser;
}; };
void Utf8Iterator_Start(struct Utf8Iterator *self, const char *source); static inline void Utf8Iterator_Init(struct Utf8Iterator *self, const char *source) {
Utf8Parser_Clear(&self->parser);
self->source = source;
}
/** /**
* Get the next character from the iterator; Returns empty character if there are no more characters to parse. * Get the next character from the iterator; Returns empty character if there are no more characters to parse.

@ -1,28 +1,23 @@
#include "gui.h"
#include "graphics/nokia.h"
#include "analog.h"
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/task.h> #include <freertos/task.h>
#include <liquid.h>
#include <freertos/timers.h> #include <freertos/timers.h>
#include "gui.h"
#include "nokia.h"
#include "liquid.h"
#include "drawing.h"
static void gui_thread(void *arg); static void gui_thread(void *arg);
static void gui_tick(TimerHandle_t xTimer); static void gui_tick(TimerHandle_t xTimer);
TaskHandle_t hGuiThread = NULL; TaskHandle_t hGuiThread = NULL;
TimerHandle_t hTicker = NULL; static TimerHandle_t hTicker = NULL;
void gui_init() { void gui_init() {
printf("GUI init\n"); printf("GUI init\n");
LCD_setup(); LCD_setup();
LCD_setContrast(48); LCD_setContrast(48);
LCD_clearDisplay(0);
LCD_setStr("Hello World", 0, 0, 1);
LCD_updateDisplay();
int rv = xTaskCreate(gui_thread, "gui", 4096, NULL, 6, &hGuiThread); int rv = xTaskCreate(gui_thread, "gui", 4096, NULL, 6, &hGuiThread);
assert (rv == pdPASS); assert (rv == pdPASS);
@ -63,8 +58,6 @@ static void __attribute__((noreturn)) gui_thread(void *arg) {
bool want_repaint = false; bool want_repaint = false;
// printf("Knob event 0x%02x ", value);
if (value & 0b10000) { if (value & 0b10000) {
// TICK // TICK
want_repaint |= Liquid_handleTick(liquid); want_repaint |= Liquid_handleTick(liquid);

@ -0,0 +1,51 @@
/**
* Input event structural enum
*
* Created on 2020/01/05.
*/
#ifndef LIQUID_INPUT_EVENT_H
#define LIQUID_INPUT_EVENT_H
enum InputEvent_Kind {
InputEventKind_Wheel,
InputEventKind_Button,
};
struct InputEvent {
enum InputEvent_Kind kind;
union {
struct {
// Wheel delta
int32_t delta;
} wheel;
struct {
// Button state
bool state;
} button;
};
};
static inline struct InputEvent InputEvent_Wheel(int32_t delta)
{
return (struct InputEvent) {
.kind = InputEventKind_Wheel,
.wheel = {.delta = delta}
};
}
/**
* Button event (push, release)
*
* @param state - pushed
* @return event
*/
static inline struct InputEvent InputEvent_Button(bool state)
{
return (struct InputEvent) {
.kind = InputEventKind_Button,
.button = {.state = state},
};
}
#endif //LIQUID_INPUT_EVENT_H

@ -1,13 +1,15 @@
#include "liquid.h"
#include "rom/queue.h"
#include "scenes.h"
#include <malloc.h> #include <malloc.h>
#include <assert.h> #include <assert.h>
#include <esp_log.h> #include <esp_log.h>
#include <nokia.h> #include <rom/queue.h>
#include "liquid.h"
#include "nokia.h"
static const char *TAG = "Liquid"; static const char *TAG = "Liquid";
extern struct Scene *NewScene_Root(void);
struct RunningScene { struct RunningScene {
struct Scene *scene; struct Scene *scene;
SLIST_ENTRY(RunningScene) next; SLIST_ENTRY(RunningScene) next;

@ -4,234 +4,17 @@
* Created on 2020/01/03. * Created on 2020/01/03.
*/ */
#ifndef REFLOWER_LIQUID_H #ifndef LIQUID_H
#define REFLOWER_LIQUID_H #define LIQUID_H
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "input_event.h"
#include "scene_event.h"
#include "scene_type.h"
struct Liquid; struct Liquid;
enum InputEvent_Kind {
InputEventKind_Wheel,
InputEventKind_Button,
};
struct InputEvent {
enum InputEvent_Kind kind;
union {
struct {
// Wheel delta
int32_t delta;
} wheel;
struct {
// Button state
bool state;
} button;
};
};
static inline struct InputEvent InputEvent_Wheel(int32_t delta)
{
return (struct InputEvent) {
.kind = InputEventKind_Wheel,
.wheel = {.delta = delta}
};
}
/**
* Button event (push, release)
*
* @param state - pushed
* @return event
*/
static inline struct InputEvent InputEvent_Button(bool state)
{
return (struct InputEvent) {
.kind = InputEventKind_Button,
.button = {.state = state},
};
}
enum SceneEvent_Kind {
SceneEventKind_Close,
SceneEventKind_OpenChild,
SceneEventKind_RequestRepaint,
SceneEventKind_None,
};
// forward declaration
struct Scene;
/**
* Scene event, returned from some scene methods.
*
* Use the constructor functions to create this struct.
*/
struct SceneEvent {
/** Event kind enum */
enum SceneEvent_Kind kind;
union {
/* data for Close event kind */
struct {
// Status code
int32_t status;
// Return data on heap
void *data;
} close;
/* Data for Open event kind */
struct {
// Scene (initialized, with options loaded in through the constructor)
struct Scene *scene;
// Tag used by parent to identify the open child
uint32_t tag;
} open;
};
};
/**
* Create empty (null object) scene event.
*
* @return event
*/
static inline struct SceneEvent SceneEvent_None(void)
{
return (struct SceneEvent) {
.kind = SceneEventKind_None,
};
}
/**
* Request scene repaint
*
* @return event
*/
static inline struct SceneEvent SceneEvent_Repaint(void)
{
return (struct SceneEvent) {
.kind = SceneEventKind_RequestRepaint
};
}
/**
* Request a sub-scene to be opened
*
* @param child - child scene
* @param tag - scene tag
* @return event
*/
static inline struct SceneEvent SceneEvent_OpenChild(struct Scene *child, uint32_t tag) {
return (struct SceneEvent) {
.kind = SceneEventKind_OpenChild,
.open = { .scene = child, .tag=tag },
};
}
/**
* Close this scene, returning to parent.
*
* @param status - status number for the parent
* @param data - heap-allocated data for parent, can be NULL; e.g. user input as string.
* @return event
*/
static inline struct SceneEvent SceneEvent_Close(int32_t status, void *data)
{
return (struct SceneEvent) {
.kind = SceneEventKind_Close,
.close = {.status = status, .data=data},
};
}
/**
* Scene::onInput fp type - handle user input
*
* @param scene - self
* @param event - the input event
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onInput_t)(struct Scene *scene, struct InputEvent event);
/**
* Scene::onChildReturn fp type
*
* @param scene - self
* @param tag - child's tag
* @param status - status code returned from the child
* @param data - data returned from the child, must be heap-allocated if not NULL.
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onChildReturn_t)(struct Scene *scene, uint32_t tag, int32_t status, void *data);
/**
* Scene::onTick fp type
*
* @param scene - self
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onTick_t)(struct Scene *scene);
/**
* Scene::paint fp type
*
* @param scene - self
*/
typedef void (*Scene_paint_t)(struct Scene *scene);
/**
* Scene::free fp type.
* Release internally allocated resources.
* This is called by the GUI engine when the scene is closed.
*
* @param scene - self
*/
typedef void (*Scene_free_t)(struct Scene *scene);
/**
* Scene instance in the framework
*/
struct Scene {
/**
* Tag given to the scene by its parent to identify its close event.
*/
uint32_t tag;
/**
* Handle input event.
* Can return a follow-up scene event, e.g. repaint request.
* Nullable field.
*/
Scene_onInput_t onInput;
/**
* Child scene closed, handle its return value and data, if any.
* Can return a follow-up scene event.
* In any case, the scene will be re-painted, if it stays open and on top.
* Nullable field.
*/
Scene_onChildReturn_t onChildReturn;
/**
* Handle a periodic tick (10ms).
* Can return a follow-up scene event, e.g. repaint request.
* Nullable field.
*/
Scene_onTick_t onTick;
/**
* Draw the scene to the LCD buffer.
* DO NOT write to the display yet, it will be done by the GUI engine.
*
* MANDATORY FIELD
*/
Scene_paint_t paint;
/**
* Release internally allocated resources, if any. Called on close.
* Nullable field.
*/
Scene_free_t free;
};
/** return 1 if repaint requested */ /** return 1 if repaint requested */
bool Liquid_handleInput(struct Liquid *container, struct InputEvent event); bool Liquid_handleInput(struct Liquid *container, struct InputEvent event);
@ -244,4 +27,4 @@ void Liquid_paint(struct Liquid *container);
/** Initialize the GUI system with a root scene */ /** Initialize the GUI system with a root scene */
struct Liquid *Liquid_start(void); struct Liquid *Liquid_start(void);
#endif //REFLOWER_LIQUID_H #endif //LIQUID_H

@ -0,0 +1,99 @@
/**
* Scene event structural enum
*
* Created on 2020/01/05.
*/
#ifndef LIQUID_SCENE_EVENT_H
#define LIQUID_SCENE_EVENT_H
// forward declaration
struct Scene;
enum SceneEvent_Kind {
SceneEventKind_Close,
SceneEventKind_OpenChild,
SceneEventKind_RequestRepaint,
SceneEventKind_None,
};
/**
* Scene event, returned from some scene methods.
*
* Use the constructor functions to create this struct.
*/
struct SceneEvent {
/** Event kind enum */
enum SceneEvent_Kind kind;
union {
/* data for Close event kind */
struct {
// Status code
int32_t status;
// Return data on heap
void *data;
} close;
/* Data for Open event kind */
struct {
// Scene (initialized, with options loaded in through the constructor)
struct Scene *scene;
// Tag used by parent to identify the open child
uint32_t tag;
} open;
};
};
/**
* Create empty (null object) scene event.
*
* @return event
*/
static inline struct SceneEvent SceneEvent_None(void)
{
return (struct SceneEvent) {
.kind = SceneEventKind_None,
};
}
/**
* Request scene repaint
*
* @return event
*/
static inline struct SceneEvent SceneEvent_Repaint(void)
{
return (struct SceneEvent) {
.kind = SceneEventKind_RequestRepaint
};
}
/**
* Request a sub-scene to be opened
*
* @param child - child scene
* @param tag - scene tag
* @return event
*/
static inline struct SceneEvent SceneEvent_OpenChild(struct Scene *child, uint32_t tag) {
return (struct SceneEvent) {
.kind = SceneEventKind_OpenChild,
.open = { .scene = child, .tag=tag },
};
}
/**
* Close this scene, returning to parent.
*
* @param status - status number for the parent
* @param data - heap-allocated data for parent, can be NULL; e.g. user input as string.
* @return event
*/
static inline struct SceneEvent SceneEvent_Close(int32_t status, void *data)
{
return (struct SceneEvent) {
.kind = SceneEventKind_Close,
.close = {.status = status, .data=data},
};
}
#endif //LIQUID_SCENE_EVENT_H

@ -0,0 +1,105 @@
/**
* Scene struct
*
* Created on 2020/01/05.
*/
#ifndef LIQUID_SCENE_TYPE_H
#define LIQUID_SCENE_TYPE_H
#include <stdint.h>
struct Scene;
struct InputEvent;
/**
* Scene::onInput fp type - handle user input
*
* @param scene - self
* @param event - the input event
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onInput_t)(struct Scene *scene, struct InputEvent event);
/**
* Scene::onChildReturn fp type
*
* @param scene - self
* @param tag - child's tag
* @param status - status code returned from the child
* @param data - data returned from the child, must be heap-allocated if not NULL.
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onChildReturn_t)(struct Scene *scene, uint32_t tag, int32_t status, void *data);
/**
* Scene::onTick fp type
*
* @param scene - self
* @return follow-up scene event, can be SceneEvent_None() if not used.
*/
typedef struct SceneEvent (*Scene_onTick_t)(struct Scene *scene);
/**
* Scene::paint fp type
*
* @param scene - self
*/
typedef void (*Scene_paint_t)(struct Scene *scene);
/**
* Scene::free fp type.
* Release internally allocated resources.
* This is called by the GUI engine when the scene is closed.
*
* @param scene - self
*/
typedef void (*Scene_free_t)(struct Scene *scene);
/**
* Scene instance in the framework
*/
struct Scene {
/**
* Tag given to the scene by its parent to identify its close event.
*/
uint32_t tag;
/**
* Handle input event.
* Can return a follow-up scene event, e.g. repaint request.
* Nullable field.
*/
Scene_onInput_t onInput;
/**
* Child scene closed, handle its return value and data, if any.
* Can return a follow-up scene event.
* In any case, the scene will be re-painted, if it stays open and on top.
* Nullable field.
*/
Scene_onChildReturn_t onChildReturn;
/**
* Handle a periodic tick (10ms).
* Can return a follow-up scene event, e.g. repaint request.
* Nullable field.
*/
Scene_onTick_t onTick;
/**
* Draw the scene to the LCD buffer.
* DO NOT write to the display yet, it will be done by the GUI engine.
*
* MANDATORY FIELD
*/
Scene_paint_t paint;
/**
* Release internally allocated resources, if any. Called on close.
* Nullable field.
*/
Scene_free_t free;
};
#endif //LIQUID_SCENE_TYPE_H

@ -1,7 +1,8 @@
#include "scenes.h" #include "scenes.h"
#include "liquid.h" #include "liquid.h"
#include "../graphics/nokia.h"
#include <malloc.h> #include <malloc.h>
#include "graphics/nokia.h"
#include "graphics/drawing.h"
struct CarScene { struct CarScene {
struct Scene base; struct Scene base;

@ -1,10 +1,12 @@
#include "scenes.h"
#include "liquid.h"
#include "../graphics/nokia.h"
#include "../analog.h"
#include <malloc.h> #include <malloc.h>
#include <stdio.h> #include <stdio.h>
#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. * 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. * Since the base struct is located at the beginning, it can be cast and passed around as Scene.
Loading…
Cancel
Save