Compare commits

...

9 Commits

Author SHA1 Message Date
Ben V. Brown
7a0308dbf4 Generate layout structures 2023-06-29 20:53:22 +10:00
Ben V. Brown
c31fb5725b Fixing import loop 2023-06-29 20:26:00 +10:00
Ben V. Brown
7535a64bc7 Images 2023-06-29 18:51:40 +10:00
Ben V. Brown
b599dec278 Start on renderers 2023-06-29 18:46:34 +10:00
Ben V. Brown
3a05d695f8 More Font utils 2023-06-29 18:46:26 +10:00
Ben V. Brown
45639e5c23 Number Bounded draw 2023-06-29 18:46:19 +10:00
Ben V. Brown
da7dbd4b55 Move out FontStyle 2023-06-29 18:38:50 +10:00
Ben V. Brown
62fb6d5209 Starting to implement Elements 2023-06-28 23:14:09 +10:00
Ben V. Brown
d9350fdc73 Scratching out layout 2023-06-28 22:49:37 +10:00
15 changed files with 629 additions and 10 deletions

1
.gitignore vendored
View File

@@ -178,3 +178,4 @@ CoreCompileInputs.cache
.vscode/settings.json
source/compile_commands.json
.idea/
source/UI/layout_96x16.cpp

View File

@@ -9,13 +9,13 @@ WORKDIR /build
# musl-dev is required for the multi lang firmwares
# clang is required for clang-format (for dev)
ARG APK_COMPS="gcc-riscv-none-elf gcc-arm-none-eabi newlib-riscv-none-elf \
newlib-arm-none-eabi"
newlib-arm-none-eabi"
ARG APK_PYTHON="python3 py3-pip black"
ARG APK_MISC="findutils make git"
ARG APK_DEV="musl-dev clang bash clang-extra-tools"
# PIP packages
ARG PIP_PKGS='bdflib'
ARG PIP_PKGS='bdflib pyyaml'
RUN apk add --no-cache ${APK_COMPS} ${APK_PYTHON} ${APK_MISC} ${APK_DEV}

View File

@@ -169,6 +169,7 @@ void OLED::drawChar(const uint16_t charCode, const FontStyle fontStyle) {
case FontStyle::SMALL:
case FontStyle::LARGE:
default:
// TODO handle 4 lines
if (charCode == '\x01' && cursor_y == 0) { // 0x01 is used as new line char
setCursor(0, 8);
return;
@@ -431,6 +432,57 @@ void OLED::setInverseDisplay(bool inverse) {
OLED_Setup_Array[21].val = normalInverseCmd;
I2C_CLASS::I2C_RegisterWrite(DEVICEADDR_OLED, 0x80, normalInverseCmd);
}
void OLED::printBounded(const char *str, const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h, FontStyle fontStyle) {
setCursor(x, y);
const uint8_t *next = reinterpret_cast<const uint8_t *>(str);
// Determine font size by newline magic marker
if (fontStyle == FontStyle::FROM_TEXT) {
fontStyle = FontStyle::SMALL;
if (next[0] == 0x01) {
fontStyle = FontStyle::LARGE;
next++;
}
} else if (fontStyle == FontStyle::FROM_HEIGHT) {
if (h > get_fontstyle_height(FontStyle::SMALL)) {
fontStyle = FontStyle::LARGE;
} else {
fontStyle = FontStyle::SMALL;
}
}
// Now we walk and print
while (next[0]) {
uint16_t index;
if (next[0] <= 0xF0) {
index = next[0];
next++;
} else {
if (!next[1]) {
return;
}
index = (next[0] - 0xF0) * 0xFF - 15 + next[1];
next += 2;
}
if (index > 0x01) {
// Not a newline or terminator
// Need to make sure we clip
if (cursor_x >= (x + w)) {
// About to write out of bounds, wrap
if (fontStyle == FontStyle::SMALL) {
cursor_y += 8;
} else {
cursor_y += 16;
}
}
if (cursor_y >= y + h) {
// Clipping off bottom, yeet
return;
}
}
drawChar(index, fontStyle);
}
}
// print a string to the current cursor location, len chars MAX
void OLED::print(const char *const str, FontStyle fontStyle, uint8_t len) {
@@ -493,6 +545,25 @@ void OLED::drawHex(uint32_t x, FontStyle fontStyle, uint8_t digits) {
drawChar(value + 2, fontStyle);
}
}
void OLED::printNumberBounded(const uint16_t num, bool noLeaderZeros, const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h) {
// Print the number to the screen, bounded by the given dimension
FontStyle fontStyle = get_fontstyle_fromHeight(h);
uint8_t places = w / get_fontstyle_width(fontStyle);
if (places > 5)
places = 5;
char buffer[7] = {0, 0, 0, 0, 0, 0, 0};
uint16_t number = num;
for (int i = places; i >= 0; i--) {
buffer[i] = 2 /*Skew past null and newline*/ + number % 10;
number /= 10;
}
if (noLeaderZeros)
stripLeaderZeros(buffer, places);
printBounded(buffer, x, y, w, h, FontStyle::FROM_HEIGHT);
}
// maximum places is 5
void OLED::printNumber(uint16_t number, uint8_t places, FontStyle fontStyle, bool noLeaderZeros) {
char buffer[7] = {0};

View File

@@ -10,6 +10,7 @@
#ifndef OLED_HPP_
#define OLED_HPP_
#include "Font.h"
#include "FontUtils.h"
#include "cmsis_os.h"
#include "configuration.h"
#include <BSP.h>
@@ -61,12 +62,6 @@ extern "C" {
#endif
#define FRAMEBUFFER_START 17
enum class FontStyle {
SMALL,
LARGE,
EXTRAS,
};
class OLED {
public:
enum DisplayState : bool { OFF = false, ON = true };
@@ -106,8 +101,13 @@ public:
static void setBrightness(uint8_t contrast);
static void setInverseDisplay(bool inverted);
static int16_t getCursorX() { return cursor_x; }
static void print(const char *string, FontStyle fontStyle, uint8_t length = 255); // Draw a string to the current location, with selected font; optionally - with MAX length only
static void printWholeScreen(const char *string);
static void printBounded(const char *str, const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h, FontStyle fontStyle = FontStyle::FROM_TEXT);
static void printNumberBounded(const uint16_t num, bool noLeaderZeros, const uint8_t x, const uint8_t y, const uint8_t w, const uint8_t h);
static void print(const char *string, FontStyle fontStyle,
uint8_t length = 255); // Draw a string to the current location, with selected font; optionally - with MAX length only
static void printWholeScreen(const char *string);
// Set the cursor location by pixels
static void setCursor(int16_t x, int16_t y) {
cursor_x = x;

View File

@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
enum class FontStyle {
SMALL,
LARGE,
EXTRAS,
FROM_TEXT, // Magically pick from the text
FROM_HEIGHT, // Pick font to best fill the height
};
constexpr uint8_t get_fontstyle_height(const FontStyle font);
constexpr uint8_t get_fontstyle_width(const FontStyle font);
constexpr FontStyle get_fontstyle_fromHeight(const uint8_t height);

View File

@@ -0,0 +1,14 @@
#include "FontUtils.h"
constexpr uint8_t get_fontstyle_height(const FontStyle font) {
if (font == FontStyle::SMALL)
return 8;
return 16;
}
constexpr uint8_t get_fontstyle_width(const FontStyle font) {
if (font == FontStyle::SMALL)
return 6;
return 12;
}
constexpr FontStyle get_fontstyle_fromHeight(const uint8_t height) { return (height <= 8) ? FontStyle::SMALL : FontStyle::LARGE; }

View File

@@ -171,6 +171,7 @@ SOURCE_DRIVERS_DIR = ./Core/Drivers
INC_PD_DRIVERS_DIR = ./Core/Drivers/usb-pd/include
PD_DRIVER_TESTS_DIR = ./Core/Drivers/usb-pd/tests
PD_DRIVER_DIR = ./Core/Drivers/usb-pd
UI_DIR = ./UI
# Find-all's used for formatting; have to exclude external modules
@@ -419,6 +420,7 @@ INCLUDES = -I$(APP_INC_DIR) \
-I$(FRTOS_INC_DIR) \
-I$(DRIVER_INC_DIR) \
-I$(BSP_INC_DIR) \
-I$(UI_DIR) \
-I$(THREADS_INC_DIR) \
-I$(THREADS_OP_MODES_INC_DIR) \
-I$(THREADS_OP_MODES_TOOLS_INC_DIR) \
@@ -433,6 +435,7 @@ EXCLUDED_DIRS := -path $(PINECILV2_VENDOR_BSP_ES8388_DIR) \
-o -path $(PINECILV2_VENDOR_BSP_USB_DIR) \
SOURCE := $(shell find $(SOURCE_THREADS_DIR) -type f -name '*.c') \
$(shell find $(UI_DIR) -type f -name '*.c') \
$(shell find $(SOURCE_CORE_DIR) -type f -name '*.c') \
$(shell find $(SOURCE_DRIVERS_DIR) -type f -name '*.c') \
$(shell find $(DEVICE_BSP_DIR) -type d \( $(EXCLUDED_DIRS) \) -prune -false -o -type f -name '*.c')\
@@ -441,6 +444,7 @@ $(SOURCE_BRIEFLZ_DIR)/depack.c
# We exclude the USB-PD stack tests $(PD_DRIVER_TESTS_DIR)
SOURCE_CPP := $(shell find $(SOURCE_THREADS_DIR) -type f -name '*.cpp') \
$(shell find $(SOURCE_CORE_DIR) -type f -name '*.cpp') \
$(shell find $(UI_DIR) -type f -name '*.cpp') \
$(shell find $(SOURCE_DRIVERS_DIR) -path $(PD_DRIVER_TESTS_DIR) -prune -false -o -type f -name '*.cpp') \
$(shell find $(DEVICE_BSP_DIR) -type d \( $(EXCLUDED_DIRS) \) -prune -false -o -type f -name '*.cpp') \
$(shell find $(SOURCE_MIDDLEWARES_DIR) -type f -name '*.cpp')

8
source/UI/UI.cpp Normal file
View File

@@ -0,0 +1,8 @@
#include "UI.h"
#include "OLED.hpp"
void ui_render_screen(screenLayout_t *screen, ScreenContext_t *context) {
// Walk the struct associated to the screen, calling render for each element of the screen
// Then start OLED refresh
OLED::refresh();
}

16
source/UI/UI.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "UI_Layouts.h"
#include <stdint.h>
typedef union {
int32_t i32;
void *ptr;
} screen_arg_t;
typedef struct {
screen_arg_t args[4];
} ScreenContext_t;
//
void ui_render_screen(screenLayout_t *screen, ScreenContext_t *context);
void ui_render_element(const ElementTypes_t element, const ElementSettings_t *settings, screen_arg_t *args);

77
source/UI/UI_Elements.cpp Normal file
View File

@@ -0,0 +1,77 @@
#include "UI_Elements.h"
#include "FontUtils.h"
#include "OLED.hpp"
#include "UI.h"
void render_Text(const ElementSettings_t *settings, screen_arg_t *args);
void render_Number(const ElementSettings_t *settings, screen_arg_t *args);
void render_Image(const ElementSettings_t *settings, screen_arg_t *args);
void render_PowerSource(const ElementSettings_t *settings, screen_arg_t *args);
void render_Temperature(const ElementSettings_t *settings, screen_arg_t *args);
void render_InputVoltage(const ElementSettings_t *settings, screen_arg_t *args);
void render_ScrollBar(const ElementSettings_t *settings, screen_arg_t *args);
void render_CheckBox(const ElementSettings_t *settings, screen_arg_t *args);
void render_TextScroller(const ElementSettings_t *settings, screen_arg_t *args);
void ui_render_element(const ElementTypes_t element, const ElementSettings_t *settings, screen_arg_t *args) {
switch (element) {
case ElementTypes_t::Text:
render_Text(settings, args);
break;
case ElementTypes_t::Number:
render_Number(settings, args);
break;
case ElementTypes_t::Image:
render_Image(settings, args);
break;
case ElementTypes_t::PowerSource:
render_PowerSource(settings, args);
break;
case ElementTypes_t::Temperature:
render_Temperature(settings, args);
break;
case ElementTypes_t::InputVoltage:
render_InputVoltage(settings, args);
break;
case ElementTypes_t::ScrollBar:
render_ScrollBar(settings, args);
break;
case ElementTypes_t::CheckBox:
render_CheckBox(settings, args);
break;
case ElementTypes_t::TextScroller:
render_TextScroller(settings, args);
break;
}
}
void render_Text(const ElementSettings_t *settings, screen_arg_t *args) {
// Draw text clipped into bounds
// Args shall be a pointer to an encoded string
OLED::printBounded((const char *)args->ptr, settings->position.x, settings->position.y, settings->size.w, settings->size.h);
}
void render_Number(const ElementSettings_t *settings, screen_arg_t *args) {
// Arg is an int32 of the number to display
OLED::printNumberBounded(args->i32, true, settings->position.x, settings->position.y, settings->size.w, settings->size.h);
}
void render_Image(const ElementSettings_t *settings, screen_arg_t *args) {
// arg is a pointer to the raw image data to display
OLED::drawArea(settings->position.x, settings->position.y, settings->size.w, settings->size.h, (uint8_t *)args->ptr);
}
void render_PowerSource(const ElementSettings_t *settings, screen_arg_t *args) {
//
}
void render_Temperature(const ElementSettings_t *settings, screen_arg_t *args) {
//
}
void render_InputVoltage(const ElementSettings_t *settings, screen_arg_t *args) {
//
}
void render_ScrollBar(const ElementSettings_t *settings, screen_arg_t *args) {
//
}
void render_CheckBox(const ElementSettings_t *settings, screen_arg_t *args) {
//
}
void render_TextScroller(const ElementSettings_t *settings, screen_arg_t *args) {
//
}

26
source/UI/UI_Elements.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <stdint.h>
typedef enum {
Text, // Basic text splat, using re-encoded strings
Number, // Draws numbers using best size for the height (always one line)
Image, // Pre-rendered bitmap that can be memcpy'ed in
PowerSource, // Draws a battery icon, or text for voltage, or DC power plug
Temperature, // Draws the number with temperature symbol following (height picks font)
InputVoltage, // Draws the number with V following and also 1 dp(height picks font)
ScrollBar, // Draws a vertical scrollbar, number sets percentage 0-100
CheckBox, // Draws checkbox, ticked = number!=0
TextScroller, // Renders text, scrolling with time
} ElementTypes_t;
typedef struct {
struct {
uint8_t x;
uint8_t y;
} position;
struct {
uint8_t w;
uint8_t h;
} size;
} ElementSettings_t;

26
source/UI/UI_Layouts.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include "UI_Elements.h"
typedef enum {
SimplifiedHome, // Basic home
SimplifiedHomeWarning, // Home with temp warning
DetailedHome, // Detailed home view
DetailedHomeWarning, // Home with temp warning
DebugView, // Debugging metrics
settingsCategory, // Settings category with icon
SettingsEntryBool, // Tickbox setting
SettingsEntry3Number, // Settings adjust with 3 number digits
SettingsEntry2Number, // Settings adjust with 2 number digits
SettingsEntry1Number, // Settings adjust with 2 number digits
SettingsEntry1Text, // Setting with single text char for state
ScrollingText, // Scrolling large text (warnings, help text)
SolderingMode, // Basic soldering mode
DetailedSolderingMode, // Detailed soldering mode
NumberAdjust, // Number adjust of number with <> either side
} screenLayout_t;
typedef struct {
struct {
ElementTypes_t elementType;
ElementSettings_t elementSettings;
} elements[5];
} ScreenLayoutRecord_t;

15
source/UI/UI_Screens.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "UI_Layouts.h"
typedef void (*render_prep_fn)();
typedef void (*tick_fn)();
typedef void (*button_handler_fn)();
typedef struct {
// on_enter
// on_exit
tick_fn tick;
render_prep_fn render_prepare;
screenLayout_t layout; // Render layout used
button_handler_fn handle_button;
} Screen_t;

78
source/UI/generate_ui.py Executable file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""
Read in the yaml config files in this folder
For each config file, generate a cpp file defining each layout
"""
import yaml
from string import Template
def element_to_str(element) -> str:
template = Template(
"""
{
.elementType = ElementTypes_t::$type,
.elementSettings={
.position ={
.x=$x,
.y=$y,
},
.size ={
.w=$w,
.h=$h,
}
},
},
"""
)
return template.substitute(
x=element["position"]["x"],
y=element["position"]["y"],
w=element["size"]["w"],
h=element["size"]["h"],
type=element["type"],
)
def layout_to_str(layout):
inner_elements = "".join(element_to_str(x) for x in layout["elements"])
template = Template(
"""{
.elements =
{
$elements
},
},
"""
)
return template.substitute(elements=inner_elements)
def layouts_to_cpp(layout_file_path, output_file_path):
with open(layout_file_path, "r") as file:
with open(output_file_path, "w") as output:
layout_defs = yaml.safe_load(file)
header = """
#include "UI.h"
#include "UI_Elements.h"
const ScreenLayoutRecord_t screenLayouts[] = {
"""
output.write(header)
for layout_name in layout_defs["layouts"]:
layout = layout_defs["layouts"][layout_name]
# Now we convert this layout into a structure definition
element_entry = layout_to_str(layout)
output.write(element_entry)
footer = """
};
"""
output.write(footer)
layouts_to_cpp("layout_96x16.yaml", "layout_96x16.cpp")

270
source/UI/layout_96x16.yaml Normal file
View File

@@ -0,0 +1,270 @@
layouts:
SimplifiedHome:
mirrorOnRotate: true
elements:
- type: Image
position:
x: 0
y: 0
size:
w: 42
h: 16
- type: Image
position:
x: 0
y: 0
size:
w: 42
h: 16
- type: PowerSource
position:
x: 84
y: 0
size:
w: 12
h: 16
simplifiedHomeWarning:
mirrorOnRotate: true
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 42
h: 16
- type: Image
position:
x: 0
y: 0
size:
w: 42
h: 16
- type: PowerSource
position:
x: 84
y: 0
size:
w: 12
h: 16
detailedHome:
elements:
- type: Temperature
position:
x: 0
y: 0
size:
w: 48
h: 16
- type: Temperature
position:
x: 48
y: 0
size:
w: 32
h: 8
- type: InputVoltage
position:
x: 48
y: 0
size:
w: 32
h: 8
detailedHomeWarning:
elements:
- type: Temperature
position:
x: 0
y: 0
size:
w: 48
h: 16
- type: InputVoltage
position:
x: 48
y: 0
size:
w: 32
h: 8
SebugMenu:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 96
h: 8
- type: Text
position:
x: 0
y: 8
size:
w: 48
h: 8
- type: Number
position:
x: 48
y: 8
size:
w: 48
h: 8
SettingsCategory:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: Image
position:
x: 79
y: 0
size:
w: 16
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
SettingsEntryBool:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: CheckBox
position:
x: 79
y: 0
size:
w: 16
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
SettingsEntry3Number:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: Number
position:
x: 79
y: 0
size:
w: 36
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
SettingsEntry2Number:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: Number
position:
x: 79
y: 0
size:
w: 36
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
SettingsEntry1Number:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: Number
position:
x: 79
y: 0
size:
w: 36
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
SettingsEntry1Text:
elements:
- type: Text
position:
x: 0
y: 0
size:
w: 78
h: 16
- type: Number
position:
x: 79
y: 0
size:
w: 36
h: 16
- type: ScrollBar
position:
x: 95
y: 0
size:
w: 1
h: 16
ScrollingText:
elements:
- type: TextScroller
position:
x: 0
y: 0
size:
w: 96
h: 16
# SolderingMode:
# elements:
# DetailedSolderingMode:
# elements:
# NumberAdjust:
# elements: