You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
zavlaha-kzk/src/lcd/lcdbuf.c

350 lines
11 KiB

/**
* HD44780 utf8-capable display buffer
*/
#include <string.h>
#include <assert.h>
//#include <stdio.h>
#include "lcdbuf.h"
/** Initialize the struct */
void LcdBuffer_Init(struct LcdBuffer *self, const struct LcdBuf_CGROM_Entry *cgrom, const struct LcdBuf_CGRAM_Symbol *custom_symbols)
{
assert(self);
assert(cgrom);
assert(custom_symbols);
memset(self, 0, sizeof(struct LcdBuffer));
LcdBuffer_Clear(self);
self->cgrom = cgrom;
self->custom_symbols = custom_symbols;
}
/** Clear the screen */
void LcdBuffer_Clear(struct LcdBuffer *self)
{
assert(self);
memset(self->cgram, 0, sizeof(self->cgram));
memset(self->screen, ' ', sizeof(self->screen));
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
// everything must be written, who knows what was left on the display before!
self->full_repaint_required = true;
}
/** Write what needs to be written to the HW, clear all dirty marks */
void LcdBuffer_Flush(struct LcdBuffer *self)
{
bool any_flush = self->full_repaint_required || self->cursor_dirty;
uint8_t flush_cgram_mask = 0;
// Check if any flushing is required
for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) {
if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) {
any_flush = true;
flush_cgram_mask |= 1 << i;
}
}
if (!any_flush) {
// check if we have dirty areas
for (int e = 0; e < LCDBUF_DIRTY_LIST_LEN; e++) {
struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e];
if (ext->count) {
any_flush = true;
break;
}
}
}
if (!any_flush) {
// really nothing to do
return;
}
LcdBuffer_IO_SetCursorStyle(0); // hide cursor for the time of this function
for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) {
if (flush_cgram_mask & (1 << i)) {
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data);
self->cgram[i].dirty = false;
}
}
if (self->full_repaint_required) {
self->full_repaint_required = false;
// Check if we have anything on the display - if not, just clear screen
bool any_nonspace = false;
for (int r = 0; !any_nonspace && r < LINE_NUM; r++) {
for (int c = 0; !any_nonspace && c < LINE_LEN; c++) {
if (self->screen[r][c] != ' ') {
any_nonspace = true;
}
}
}
if (!any_nonspace) {
LcdBuffer_IO_Clear();
goto done;
}
for (int r = 0; r < LINE_NUM; r++) {
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN);
}
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
} else {
for (int e = 0; e < LCDBUF_DIRTY_LIST_LEN; e++) {
struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[e];
if (!ext->count) {
continue;
}
LcdBuffer_IO_WriteAt(ext->row, ext->col, &self->screen[ext->row][ext->col], ext->count);
ext->count = 0; // mark the slot as free
}
}
done:
// Restore the visible cursor
if (self->cursor_style != 0) {
LcdBuffer_IO_SetCursorPos(self->cursor_row, self->cursor_col);
LcdBuffer_IO_SetCursorStyle(self->cursor_style); // hide cursor for the time of this function
}
self->cursor_dirty = false;
}
/** Fully write everything to the display */
void LcdBuffer_FlushAll(struct LcdBuffer *self)
{
LcdBuffer_IO_SetCursorStyle(0); // hide cursor for the time of this function
for (int i = 0; i < LCDBUF_CGRAM_CAPACITY; i++) {
if (self->cgram[i].refcount > 0) {
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data);
}
self->cgram[i].dirty = false;
}
for (int r = 0; r < LINE_NUM; r++) {
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN);
}
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
self->full_repaint_required = false;
// Restore the visible cursor
if (self->cursor_style != 0) {
LcdBuffer_IO_SetCursorPos(self->cursor_row, self->cursor_col);
LcdBuffer_IO_SetCursorStyle(self->cursor_style); // hide cursor for the time of this function
}
self->cursor_dirty = false;
}
//static void show_dirty_slots(const struct LcdBuffer *self) {
// printf("\n\n");
// for (int i = 0; i < LCDBUF_DIRTY_LIST_LEN; i++) {
// if (self->dirty_extents[i].count == 0) {
// continue;
// }
// printf("dirty_extent(%d): col %d, row %d, len %d\n", i, self->dirty_extents[i].row, self->dirty_extents[i].col, self->dirty_extents[i].count);
// }
// printf("\n");
//}
static void mark_dirty(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col)
{
// partial updates are not needed if everything will be written anyway
if (self->full_repaint_required) {
return;
}
int first_empty_extent_slot = -1;
for (int i = 0; i < LCDBUF_DIRTY_LIST_LEN; i++) {
struct LcdBuf_DirtyExtent *ext = &self->dirty_extents[i];
if (ext->count == 0) {
// unused
if (first_empty_extent_slot == -1) {
first_empty_extent_slot = i;
}
continue;
}
if (ext->row != row) {
// not this row
continue;
}
// this is a filled extent
if ((ext->col <= col) && (ext->col + ext->count > col)) {
// already in this extent
// show_dirty_slots(self);
return;
}
if ((col < ext->col) && ((ext->col - col) <= LCDBUF_DIRTY_EXTEND)) {
ext->count += ext->col - col;
ext->col = col;
// show_dirty_slots(self);
return;
}
if ((col >= ext->col + ext->count) && ((col - (ext->col + ext->count)) <= LCDBUF_DIRTY_EXTEND)) {
ext->count += col - (ext->col + ext->count) + 1;
// show_dirty_slots(self);
return;
}
}
if (first_empty_extent_slot == -1) {
// printf("Give up on dirty extents\n");
self->full_repaint_required = true;
} else {
// printf("New dirty extent: #%d\n", first_empty_extent_slot);
self->dirty_extents[first_empty_extent_slot].col = col;
self->dirty_extents[first_empty_extent_slot].row = row;
self->dirty_extents[first_empty_extent_slot].count = 1;
}
// show_dirty_slots(self);
}
/** Set one utf8 character at a position */
void LcdBuffer_Set(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, struct Utf8Char ch)
{
// printf("set %d:%d\n", row, col);
assert(self);
assert(row < LINE_NUM);
assert(col < LINE_LEN);
const uint8_t oldchar = self->screen[row][col];
if (oldchar >= LCDBUF_CGRAM_CAPACITY && oldchar == ch.uint) {
// No change
return;
}
// Fast path for standard ASCII - assuming this extent of the table is always the same!
if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH
// normal ASCII
self->screen[row][col] = ch.uint;
goto done_dirty;
}
// Find if it's in CGROM
const struct LcdBuf_CGROM_Entry *rom = self->cgrom;
for (;;) {
if (rom->symbol.uint == 0) {
// End of the lookup table
break;
}
if (rom->symbol.uint == ch.uint) {
// found it!
self->screen[row][col] = rom->address;
goto done_dirty;
}
rom++;
}
// Check if the same custom char is already used - if so, increment refcount and reuse it
int first_empty_custom_slot = -1;
for (uint8_t slot = 0; slot < LCDBUF_CGRAM_CAPACITY; slot++) {
if (self->cgram[slot].refcount > 0) {
if (self->cgram[slot].uint == ch.uint) {
if (oldchar == slot) {
// No change, was already the same custom
return;
}
self->cgram[slot].refcount++;
self->screen[row][col] = slot;
goto done_dirty;
}
} else if (first_empty_custom_slot == -1) {
first_empty_custom_slot = slot;
}
}
// New custom pattern is needed
uint16_t index = 0;
const struct LcdBuf_CGRAM_Symbol *pattern = self->custom_symbols;
for (;;) {
if (pattern->symbol.uint == 0) {
// End of the lookup table
break;
}
if (pattern->symbol.uint == ch.uint) {
// found it!
if (first_empty_custom_slot == -1) {
// Whoops, out of slots. Show a fallback glyph
if (pattern->fallback != 0) {
if (oldchar != pattern->fallback) {
self->screen[row][col] = pattern->fallback;
goto done_dirty;
}
} else {
// TODO kick out some other CGRAM entry that has fallback?
self->screen[row][col] = LCDBUF_SUBSTITUTION_CHAR;
goto done_dirty;
}
return;
}
// Allocate a new slot in the CGRAM
self->cgram[first_empty_custom_slot].refcount = 1;
self->cgram[first_empty_custom_slot].uint = ch.uint;
self->cgram[first_empty_custom_slot].dirty = true; // it should be flushed!
self->cgram[first_empty_custom_slot].symbol_index = index;
self->screen[row][col] = first_empty_custom_slot;
goto done_dirty;
}
index++;
pattern++;
}
// Fallback, no way to show this glyph
self->screen[row][col] = LCDBUF_SUBSTITUTION_CHAR;
done_dirty:
if (oldchar < LCDBUF_CGRAM_CAPACITY) {
// release refcount on the CGRAM cell
assert(self->cgram[oldchar].refcount > 0);
self->cgram[oldchar].refcount--;
}
mark_dirty(self, row, col);
}
/** Write a UTF8 string at a position */
void LcdBuffer_Write(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, char *utf_string)
{
struct Utf8Iterator iter;
Utf8Iterator_Init(&iter, utf_string);
struct Utf8Char uchar;
while ((uchar = Utf8Iterator_Next(&iter)).uint) {
if (col >= LINE_LEN) {
break;
}
LcdBuffer_Set(self, row, col, uchar);
col++;
}
}
void LcdBuffer_SetCursor(struct LcdBuffer *self, lcdbuf_pos_t row, lcdbuf_pos_t col, uint8_t cursor_style)
{
if ((self->cursor_style != cursor_style) || (self->cursor_row != row) || (self->cursor_col != col)) {
self->cursor_style = cursor_style;
self->cursor_row = row;
self->cursor_col = col;
self->cursor_dirty = true;
}
}