From baeac2e62c42dbec44dd699a2e75af501af11d48 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 21 Feb 2025 18:51:09 +0100 Subject: [PATCH 01/54] UTF-8 support --- src/CMakeLists.txt | 1 + src/bitmapbuffer.cpp | 108 +++++++--- src/bitmapbuffer.h | 11 +- src/font.cpp | 165 +++++++++++++++ src/font.h | 201 ++++++++++++------- src/keyboard_text.cpp | 38 ++-- src/keyboard_text.h | 49 +++-- src/libopenui_defines.h | 4 +- src/static.cpp | 21 +- src/textedit.cpp | 114 ++++++----- src/textedit.h | 36 ++-- src/unicode.h | 150 ++++++++++++++ tools/{encode-bitmap.py => encode_bitmap.py} | 69 ++++--- 13 files changed, 721 insertions(+), 246 deletions(-) create mode 100644 src/font.cpp create mode 100644 src/unicode.h rename tools/{encode-bitmap.py => encode_bitmap.py} (78%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c31e40..53f5cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set(LIBOPENUI_SRC libopenui_file.cpp bitmapbuffer.cpp + font.cpp window.cpp layer.cpp form.cpp diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 90c02ca..818888c 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -899,17 +899,74 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const } } -uint8_t BitmapBuffer::drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color) +#define INCREMENT_POS(delta) do { if (flags & VERTICAL) y -= delta; else x += delta; } while(0) + +coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags) { - if (glyph.width) { - drawMask(x, y, glyph.font->getBitmapData(), color, glyph.offset, 0, glyph.width); + MOVE_OFFSET(); + + auto font = getFont(flags); + int height = font->getHeight(); + + if (y + height <= ymin || y >= ymax) { + RESTORE_OFFSET(); + return x; + } + + if (flags & (RIGHT | CENTERED)) { + int width = font->getTextWidth(s, len); + if (flags & RIGHT) { + INCREMENT_POS(-width); + } + else if (flags & CENTERED) { + INCREMENT_POS(-width / 2); + } } - return glyph.width; + + coord_t & pos = (flags & VERTICAL) ? y : x; + const coord_t orig_pos = pos; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); + + if (!c) { + break; + } + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; + } + + if (c == '\n') { + pos = orig_pos; + if (flags & VERTICAL) + x += height; + else + y += height; + continue; + } + + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } + } + + RESTORE_OFFSET(); + + return ((flags & RIGHT) ? orig_pos : pos) - offsetX; } -#define INCREMENT_POS(delta) do { if (flags & VERTICAL) y -= delta; else x += delta; } while(0) -coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags) +coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags) { MOVE_OFFSET(); @@ -934,37 +991,38 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_ coord_t & pos = (flags & VERTICAL) ? y : x; const coord_t orig_pos = pos; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned int c = uint8_t(*s); - // TRACE("c = %d %o 0x%X '%c'", c, c, c, c); + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - // CJK char - auto glyph = font->getCJKChar(c, *++s); - // TRACE("CJK = %d", c); - uint8_t width = drawChar(x, y, glyph, color); - INCREMENT_POS(width + CHAR_SPACING); - } - else if (c >= 0x20) { - auto glyph = font->getChar(c); - uint8_t width = drawChar(x, y, glyph, color); - if (c >= '0' && c <= '9') - INCREMENT_POS(font->getChar('9').width + CHAR_SPACING); - else - INCREMENT_POS(width + CHAR_SPACING); + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; } - else if (c == '\n') { + + if (c == '\n') { pos = orig_pos; if (flags & VERTICAL) x += height; else y += height; + continue; } - s++; + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } } RESTORE_OFFSET(); diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 51b79e9..5473551 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -647,12 +647,18 @@ class BitmapBuffer: public BitmapBufferBase void drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); coord_t drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags = 0); + coord_t drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags = 0); coord_t drawText(coord_t x, coord_t y, const char * s, LcdColor color, LcdFlags flags = 0) { return drawSizedText(x, y, s, 0, color, flags); } + coord_t drawText(coord_t x, coord_t y, const wchar_t * s, LcdColor color, LcdFlags flags = 0) + { + return drawSizedText(x, y, s, 0, color, flags); + } + coord_t drawTextAtIndex(coord_t x, coord_t y, const char * s, uint8_t idx, LcdColor color, LcdFlags flags = 0) { char length = *s++; @@ -716,7 +722,10 @@ class BitmapBuffer: public BitmapBufferBase return applyClippingRect(x, y, w, h); } - uint8_t drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color); + void drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color) + { + drawMask(x, y, glyph.data, color, glyph.offset, 0, glyph.width); + } inline void drawPixel(pixel_t * p, pixel_t value) { diff --git a/src/font.cpp b/src/font.cpp new file mode 100644 index 0000000..4af8872 --- /dev/null +++ b/src/font.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#include +#include +#include "bitmapbuffer.h" +#include "font.h" +#include "debug.h" + +BitmapData * decodeBitmapData(const uint8_t * data) +{ + auto bitmap = (const BitmapData *)data; + size_t fontSize = bitmap->width() * bitmap->height(); + auto * buffer = (uint8_t *)malloc(fontSize + 4); + + // copy width / height + memcpy(buffer, data, 4); + + RLEBitmap::decode(buffer + 4, fontSize, bitmap->getData()); + return (BitmapData *)buffer; +} + +void flipBitmapData(BitmapData * bitmap) +{ + auto ptr1 = &bitmap->data[0]; + auto ptr2 = &bitmap->data[bitmap->height() * bitmap->width() - 1]; + while (ptr2 > ptr1) { + std::swap(*ptr1++, *ptr2--); + } +} + +void rotateBitmapData(BitmapData * bitmap) +{ + auto dataSize = bitmap->width() * bitmap->height(); + auto * srcData = (uint8_t *)malloc(dataSize); + memcpy(srcData, bitmap->data, dataSize); + auto * destData = &bitmap->data[0]; + for (coord_t y = 0; y < bitmap->height(); y++) { + for (coord_t x = 0; x < bitmap->width(); x++) { + destData[x * bitmap->height() + y] = srcData[y * bitmap->width() + x]; + } + } + free(srcData); +} + +/* Font format + * 'F', 'N', 'T', '\0' + * begin: 4bytes (glyphs index start) + * end: 4bytes (glyphs index end, not included) + * specs: 2bytes * (count + 1) + * data: bitmap data in RLE format + */ +bool Font::loadFile(const char * path) +{ + TRACE("Font::loadFile('%s')", path); + + auto file = (FIL *)malloc(sizeof(FIL)); + if (!file) { + return false; + } + + FRESULT result = f_open(file, path, FA_READ); + if (result != FR_OK) { + free(file); + return false; + } + + struct { + char fmt[4]; + char name[LEN_FONT_NAME + 1]; + uint8_t rangesCount; + uint8_t spacing; + uint8_t spaceWidth; + } header; + UINT read; + result = f_read(file, (uint8_t *)&header, sizeof(header), &read); + if (result != FR_OK || read != sizeof(header) || strncmp(header.fmt, "FNT0", sizeof(header.fmt)) != 0) { + TRACE("loadFont('%s'): invalid header", path); + f_close(file); + free(file); + return false; + } + + strlcpy(this->name, header.name, sizeof(this->name)); + this->spacing = header.spacing; + this->spaceWidth = header.spaceWidth; + + for (auto i = 0; i < header.rangesCount; i++) { + struct { + uint32_t begin; + uint32_t end; + uint32_t dataSize; + } rangeHeader; + + result = f_read(file, (uint8_t *)&rangeHeader, sizeof(rangeHeader), &read); + if (result != FR_OK || read != sizeof(rangeHeader) || rangeHeader.begin >= rangeHeader.end) { + TRACE("loadFont('%s'): invalid range header", path); + f_close(file); + free(file); + return false; + } + + uint32_t specsSize = sizeof(uint16_t) * (rangeHeader.end - rangeHeader.begin + 1); + auto specs = (uint16_t *)malloc(specsSize); + if (!specs) { + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)specs, specsSize, &read); + if (result != FR_OK || read != specsSize) { + free(specs); + f_close(file); + free(file); + return false; + } + + auto data = (uint8_t *)malloc(rangeHeader.dataSize); + if (!data) { + free(specs); + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)data, rangeHeader.dataSize, &read); + if (result != FR_OK || read != rangeHeader.dataSize) { + free(specs); + free(data); + f_close(file); + free(file); + return false; + } + + auto bitmapData = decodeBitmapData(data); +#if LCD_ORIENTATION == 180 + flipBitmapData(bitmapData); +#elif LCD_ORIENTATION == 270 + rotateBitmapData(bitmapData); +#endif + ranges.push_back({rangeHeader.begin, rangeHeader.end, bitmapData, specs}); + } + + f_close(file); + free(file); + + return true; +} diff --git a/src/font.h b/src/font.h index 624d1f2..304d494 100644 --- a/src/font.h +++ b/src/font.h @@ -19,56 +19,78 @@ #pragma once +#include #include "libopenui_types.h" #include "libopenui_config.h" #include "bitmapdata.h" - -constexpr uint8_t CJK_BYTE1_MIN = 0xFD; - -inline bool hasChineseChars(const char * str) -{ - while (*str) { - uint8_t c = *str++; - if (c >= CJK_BYTE1_MIN) { - return true; - } - } - return false; -} - -inline const char * findNextLine(const char * stack) -{ - while (true) { - const char * pos = strchr(stack, '\n'); - if (!pos) - return nullptr; - if (pos == stack || *((uint8_t *)(pos - 1)) < CJK_BYTE1_MIN) - return pos; - stack = pos + 1; - } -} +#include "unicode.h" + +constexpr uint8_t LEN_FONT_NAME = 8; + +// TODO +// inline const char * findNextLine(const char * str) +// { + // while (true) { + // auto c = getNextUnicodeChar(str); + // if (c == '\0') + // return nullptr; + // else if (c == '\n') + // return + + + // const char * pos = strchr(stack, '\n'); + // if (!pos) + // return nullptr; + // if (pos == stack || *((uint8_t *)(pos - 1)) < CJK_BYTE1_MIN) + // return pos; + // stack = pos + 1; + // } +// } class Font { public: + + struct GlyphRange + { + uint32_t begin; + uint32_t end; + const BitmapData * data; + const uint16_t * specs; + }; + struct Glyph { - const Font * font; + const BitmapData * data; unsigned offset; uint8_t width; }; - Font(const char * name, uint16_t count, const BitmapData * data, const uint16_t * specs): - name(name), - count(count), - data(data), - specs(specs) + Font() = default; + + Font(const char * name) { + strlcpy(this->name, name, sizeof(this->name)); + } + + Font(const char * name, const BitmapData * data, const uint16_t * specs): + ranges({{0x21, 0x7F, data, specs}}) + { + strlcpy(this->name, name, sizeof(this->name)); + } + + bool loadFile(const char * path); + + void addGlyphs(const Font * other) + { + for (auto range: other->ranges) { + ranges.push_back(range); + } } coord_t getHeight() const { - return specs[0]; + return ranges.empty() ? 0 : ranges.front().data->height(); } const char * getName() const @@ -76,78 +98,114 @@ class Font return name; } - Glyph getGlyph(unsigned index) const + Glyph getGlyph(wchar_t index) const { - // TODO check index not over table - auto offset = specs[index + 1]; - return {this, offset, uint8_t(specs[index + 2] - offset)}; + for (auto & range: ranges) { + if (range.begin <= index && index < range.end) { + index -= range.begin; + auto offset = range.specs[index]; + return {range.data, offset, uint8_t(range.specs[index + 1] - offset)}; + } + } + return {}; } - Glyph getChar(uint8_t c) const + uint8_t getGlyphWidth(wchar_t c) const { - if (c >= 0x20) { - return getGlyph(c - 0x20); + if (c == ' ') { + return spaceWidth; } else { - return {this, 0, 0}; + auto glyph = getGlyph(c); + return glyph.width + spacing; } } - [[nodiscard]] Glyph getCJKChar(uint8_t byte1, uint8_t byte2) const - { - unsigned result = byte2 + ((byte1 - CJK_BYTE1_MIN) << 8u) - 1; - if (result >= 0x200) - result -= 1; - if (result >= 0x100) - result -= 1; - return getGlyph(CJK_FIRST_LETTER_INDEX + result); - } - coord_t getTextWidth(const char * s, int len = 0) const { int currentLineWidth = 0; int result = 0; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned c = uint8_t(*s); + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - currentLineWidth += getCJKChar(c, *++s).width + CHAR_SPACING; + if (c == '\n') { + if (currentLineWidth > result) + result = currentLineWidth; + currentLineWidth = 0; } - else if (c >= 0x20) { - if (c >= '0' && c <= '9') - currentLineWidth += getChar('9').width + CHAR_SPACING; - else - currentLineWidth += getChar(c).width + CHAR_SPACING; + else { + currentLineWidth += getGlyphWidth(c); } - else if (c == '\n') { + } + + return currentLineWidth > result ? currentLineWidth : result; + } + + coord_t getTextWidth(const wchar_t * s, int len = 0) const + { + int currentLineWidth = 0; + int result = 0; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; + if (!c) { + break; + } + if (c == '\n') { if (currentLineWidth > result) result = currentLineWidth; currentLineWidth = 0; } - - ++s; + else { + currentLineWidth += getGlyphWidth(c); + } } return currentLineWidth > result ? currentLineWidth : result; } - [[nodiscard]] const BitmapData * getBitmapData() const + uint8_t getSpacing() const { - return data; + return spacing; } - [[nodiscard]] bool hasCJKChars() const + uint8_t getSpaceWidth() const { - return count > CJK_FIRST_LETTER_INDEX; + return spaceWidth; + } + + wchar_t begin() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.begin < result) { + result = range.begin; + } + } + return result; + } + + wchar_t end() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.end > result) { + result = range.end; + } + } + return result; } protected: - const char * name; - uint16_t count; - const BitmapData * data; - const uint16_t * specs; + char name[LEN_FONT_NAME + 1]; + uint8_t spacing = 1; + uint8_t spaceWidth = 4; + std::list ranges; }; const Font * getFont(LcdFlags flags); @@ -158,7 +216,8 @@ inline coord_t getFontHeight(LcdFlags flags) return getFont(flags)->getHeight(); } -inline coord_t getTextWidth(const char * s, int len = 0, LcdFlags flags = 0) +template +inline coord_t getTextWidth(const T * s, int len = 0, LcdFlags flags = 0) { return getFont(flags)->getTextWidth(s, len); } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index dc343ec..34b6553 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -100,8 +100,8 @@ const char * const KEYBOARD_QWERTZ_UPPERCASE[] = { const char * const KEYBOARD_NUMBERS[] = { "1234567890", - KEYBOARD_NUMBERS_FIRST_LINE_SPECIAL_CHARS, - KEYBOARD_NUMBERS_SECOND_LINE_SPECIAL_CHARS KEYBOARD_BACKSPACE, + KEYBOARD_SPECIAL_CHAR_1 KEYBOARD_SPECIAL_CHAR_2 KEYBOARD_SPECIAL_CHAR_3 KEYBOARD_SPECIAL_CHAR_4 KEYBOARD_SPECIAL_CHAR_5 KEYBOARD_SPECIAL_CHAR_6 KEYBOARD_SPECIAL_CHAR_7 KEYBOARD_SPECIAL_CHAR_8 KEYBOARD_SPECIAL_CHAR_9 KEYBOARD_SPECIAL_CHAR_10, + KEYBOARD_SPECIAL_CHAR_11 KEYBOARD_SPECIAL_CHAR_12 KEYBOARD_SPECIAL_CHAR_13 KEYBOARD_SPECIAL_CHAR_14 KEYBOARD_SPECIAL_CHAR_15 KEYBOARD_SPECIAL_CHAR_16 KEYBOARD_SPECIAL_CHAR_17 KEYBOARD_SPECIAL_CHAR_18 KEYBOARD_SPECIAL_CHAR_19 KEYBOARD_BACKSPACE, KEYBOARD_SET_LETTERS KEYBOARD_SPACE KEYBOARD_ENTER }; @@ -139,6 +139,11 @@ void TextKeyboard::paint(BitmapBuffer * dc) if (*c == ' ') { x += 15; } + else if (uint8_t(*c) < KEYBOARD_SPACE[0]) { + // special keys drawn with a bitmap + dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); + x += 45; + } else if (*c == KEYBOARD_SPACE[0]) { // spacebar dc->drawMask(x, y, (const BitmapData *)LBM_KEY_SPACEBAR, DEFAULT_COLOR); @@ -147,12 +152,15 @@ void TextKeyboard::paint(BitmapBuffer * dc) else if (*c == KEYBOARD_ENTER[0]) { // enter dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); - dc->drawText(x+40, y, "ENTER", DEFAULT_COLOR, CENTERED); + dc->drawText(x + 40, y, "ENTER", DEFAULT_COLOR, CENTERED); x += 80; } - else if (uint8_t(*c) <= KEYBOARD_SET_NUMBERS[0]) { - dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); - x += 45; + else if (uint8_t(*c) <= KEYBOARD_SPECIAL_CHAR_LAST[0]) { + // special chars + dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); + auto pos = getUnicodeStringAtPosition(STR(SPECIAL_CHARS), uint8_t(*c) - KEYBOARD_SPECIAL_CHAR_1[0]); + dc->drawSizedText(x + 40, y, pos, 1, DEFAULT_COLOR, CENTERED); + x += 80; } else { dc->drawSizedText(x, y, c, 1, DEFAULT_COLOR); @@ -194,26 +202,26 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) if (x <= 45) { uint8_t specialKey = *key; switch (specialKey) { - case SPECIAL_KEY_BACKSPACE: + case KEYBOARD_BACKSPACE[0]: // backspace events.push(EVT_VIRTUAL_KEY(KEYBOARD_BACKSPACE[0])); break; - case SPECIAL_KEY_SET_LOWERCASE: - layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; - invalidate(); - break; - case SPECIAL_KEY_SET_UPPERCASE: + case KEYBOARD_SET_UPPERCASE[0]: layoutIndex = getKeyboardLayout(); invalidate(); break; - case SPECIAL_KEY_SET_NUMBERS: - layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + case KEYBOARD_SET_LOWERCASE[0]: + layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; - case SPECIAL_KEY_SET_LETTERS: + case KEYBOARD_SET_LETTERS[0]: layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; + case KEYBOARD_SET_NUMBERS[0]: + layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + invalidate(); + break; } break; } diff --git a/src/keyboard_text.h b/src/keyboard_text.h index 6505b5d..853cce8 100644 --- a/src/keyboard_text.h +++ b/src/keyboard_text.h @@ -22,24 +22,33 @@ #include "keyboard_base.h" #include "textedit.h" -enum SpecialKey -{ - SPECIAL_KEY_BACKSPACE = 1, - SPECIAL_KEY_SET_UPPERCASE = 2, - SPECIAL_KEY_SET_LOWERCASE = 3, - SPECIAL_KEY_SET_LETTERS = 4, - SPECIAL_KEY_SET_NUMBERS = 5, - SPECIAL_KEY_SPACE = '\t', - SPECIAL_KEY_ENTER = '\n', -}; - -#define KEYBOARD_BACKSPACE "\001" -#define KEYBOARD_SET_UPPERCASE "\002" -#define KEYBOARD_SET_LOWERCASE "\003" -#define KEYBOARD_SET_LETTERS "\004" -#define KEYBOARD_SET_NUMBERS "\005" -#define KEYBOARD_SPACE "\t" -#define KEYBOARD_ENTER "\n" +#define KEYBOARD_BACKSPACE "\x01" +#define KEYBOARD_SET_UPPERCASE "\x02" +#define KEYBOARD_SET_LOWERCASE "\x03" +#define KEYBOARD_SET_LETTERS "\x04" +#define KEYBOARD_SET_NUMBERS "\x05" +#define KEYBOARD_SPACE "\x06" +#define KEYBOARD_ENTER "\x07" +#define KEYBOARD_SPECIAL_CHAR_1 "\x08" +#define KEYBOARD_SPECIAL_CHAR_2 "\x09" +#define KEYBOARD_SPECIAL_CHAR_3 "\x0A" +#define KEYBOARD_SPECIAL_CHAR_4 "\x0B" +#define KEYBOARD_SPECIAL_CHAR_5 "\x0C" +#define KEYBOARD_SPECIAL_CHAR_6 "\x0D" +#define KEYBOARD_SPECIAL_CHAR_7 "\x0E" +#define KEYBOARD_SPECIAL_CHAR_8 "\x0F" +#define KEYBOARD_SPECIAL_CHAR_9 "\x10" +#define KEYBOARD_SPECIAL_CHAR_10 "\x11" +#define KEYBOARD_SPECIAL_CHAR_11 "\x12" +#define KEYBOARD_SPECIAL_CHAR_12 "\x13" +#define KEYBOARD_SPECIAL_CHAR_13 "\x14" +#define KEYBOARD_SPECIAL_CHAR_14 "\x15" +#define KEYBOARD_SPECIAL_CHAR_15 "\x16" +#define KEYBOARD_SPECIAL_CHAR_16 "\x17" +#define KEYBOARD_SPECIAL_CHAR_17 "\x18" +#define KEYBOARD_SPECIAL_CHAR_18 "\x19" +#define KEYBOARD_SPECIAL_CHAR_19 "\x1A" +#define KEYBOARD_SPECIAL_CHAR_LAST KEYBOARD_SPECIAL_CHAR_19 extern const uint8_t LBM_KEY_SPACEBAR[]; extern const uint8_t * const LBM_SPECIAL_KEYS[]; @@ -67,7 +76,7 @@ enum KeyboardLayout namespace ui { -class TextKeyboard: public Keyboard +class TextKeyboard: public Keyboard { public: TextKeyboard(); @@ -91,7 +100,7 @@ class TextKeyboard: public Keyboard return _instance; } - static void show(TextEdit * field) + static void show(FormField * field) { if (!_instance) { _instance = new TextKeyboard(); diff --git a/src/libopenui_defines.h b/src/libopenui_defines.h index 10a0a35..f0c40e5 100644 --- a/src/libopenui_defines.h +++ b/src/libopenui_defines.h @@ -51,8 +51,8 @@ constexpr uint8_t DECIMALS(uint8_t value) return value << 4u; } -#define FONT_MASK 0x0F00u -#define FONTS_MAX_COUNT 16 +#define FONT_MASK 0x1F00u +#define MAX_FONTS 0x20 #define FONT_INDEX(flags) (((flags) & FONT_MASK) >> 8u) #define FONT(xx) (unsigned(FONT_ ## xx ## _INDEX) << 8u) diff --git a/src/static.cpp b/src/static.cpp index a76000f..2cafff6 100755 --- a/src/static.cpp +++ b/src/static.cpp @@ -39,15 +39,18 @@ coord_t StaticText::drawText(BitmapBuffer * dc, const rect_t & rect, const std:: auto start = text.c_str(); auto current = start; - auto nextline = findNextLine(start); - if (nextline) { - do { - dc->drawText(x, y, text.substr(current - start, nextline - current).c_str(), textColor, textFlags); - current = nextline + 1; - nextline = findNextLine(current); - y += fontHeight + 2; - } while (nextline); - } + + // TODO pas du code dupliqué ici ??? + + // auto nextline = findNextLine(start); + // if (nextline) { + // do { + // dc->drawText(x, y, text.substr(current - start, nextline - current).c_str(), textColor, textFlags); + // current = nextline + 1; + // nextline = findNextLine(current); + // y += fontHeight + 2; + // } while (nextline); + // } dc->drawText(x, y, current, textColor, textFlags); y += fontHeight + 2; return y; diff --git a/src/textedit.cpp b/src/textedit.cpp index f3ea379..b42e9de 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -90,13 +90,42 @@ void TextEdit::paint(BitmapBuffer * dc) void TextEdit::trim() { - for (int i = length - 1; i >= 0; i--) { + /* TODO */ + /*for (int i = length - 1; i >= 0; i--) { if (value[i] == ' ' || value[i] == '\0') value[i] = '\0'; else break; + }*/ +} + +#if defined(SOFTWARE_KEYBOARD) +void TextEdit::onVirtualKeyEvent(event_t event) +{ + auto c = event & MSK_VIRTUAL_KEY; + if (c == (uint8_t)KEYBOARD_BACKSPACE[0]) { + if (cursorPos > 0) { + auto pos = getUnicodeStringAtPosition(value, cursorPos - 1); + char * tmp = pos; + auto len = getUnicodeCharLength(getNextUnicodeChar(tmp)); + memmove(pos, pos + len, value + length - pos - len); + memset(value + length - len, 0, len); + --cursorPos; + invalidate(); + changed = true; + } + } + else { + auto len = getUnicodeCharLength(c); + if (strlen(value) + len <= length) { + insertUnicodeChar(value, cursorPos, c, length); + cursorPos++; + invalidate(); + changed = true; + } } } +#endif void TextEdit::onEvent(event_t event) { @@ -104,41 +133,26 @@ void TextEdit::onEvent(event_t event) #if defined(SOFTWARE_KEYBOARD) if (IS_VIRTUAL_KEY_EVENT(event)) { - uint8_t c = event & 0xFF; - if (c == (uint8_t)KEYBOARD_BACKSPACE[0]) { - if (cursorPos > 0) { - memmove(value + cursorPos - 1, value + cursorPos, length - cursorPos); - value[length - 1] = '\0'; - --cursorPos; - invalidate(); - changed = true; - } - } - else if (cursorPos < length) { - memmove(value + cursorPos + 1, value + cursorPos, length - cursorPos - 1); - value[cursorPos++] = c; - invalidate(); - changed = true; - } + onVirtualKeyEvent(event); + return; } #endif #if defined(HARDWARE_KEYS) if (editMode) { - char previousChar = (cursorPos > 0 ? value[cursorPos - 1] : 0); - int c = (cursorPos < length ? value[cursorPos] : 0); - int v = c; + auto currentChar = getUnicodeCharAtPosition(value, cursorPos); + auto nextChar = currentChar; switch (event) { case EVT_ROTARY_RIGHT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getNextChar(v, previousChar); + nextChar = getNextAvailableChar(nextChar); } break; case EVT_ROTARY_LEFT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getPreviousChar(v); + nextChar = getPreviousAvailableChar(nextChar); } break; @@ -151,38 +165,32 @@ void TextEdit::onEvent(event_t event) case EVT_KEY_BREAK(KEY_RIGHT): { -#if defined(SOFTWARE_KEYBOARD) - if (cursorPos < length && value[cursorPos] != '\0') { - cursorPos++; - invalidate(); - } -#else - if (cursorPos < length - 1 && value[cursorPos + 1] != '\0') { + if (cursorPos < getUnicodeStringLength(value)) { cursorPos++; invalidate(); } -#endif break; } - case EVT_KEY_BREAK(KEY_ENTER): - if (cursorPos < length - 1) { - if (value[cursorPos] == '\0') { - value[cursorPos] = ' '; - changed = true; - } - cursorPos++; - if (value[cursorPos] == '\0') { - value[cursorPos] = ' '; - changed = true; - } - invalidate(); - } - else { - changeEnd(); - FormField::onEvent(event); - } - break; + // TODO + // case EVT_KEY_BREAK(KEY_ENTER): + // if (cursorPos < length - 1) { + // if (value[cursorPos] == '\0') { + // value[cursorPos] = ' '; + // changed = true; + // } + // cursorPos++; + // if (value[cursorPos] == '\0') { + // value[cursorPos] = ' '; + // changed = true; + // } + // invalidate(); + // } + // else { + // changeEnd(); + // FormField::onEvent(event); + // } + // break; case EVT_KEY_BREAK(KEY_EXIT): changeEnd(); @@ -216,12 +224,12 @@ void TextEdit::onEvent(event_t event) } case EVT_KEY_BREAK(KEY_UP): - v = toggleCase(v); + nextChar = toggleCase(nextChar); break; case EVT_KEY_LONG(KEY_LEFT): case EVT_KEY_LONG(KEY_RIGHT): - v = toggleCase(v); + nextChar = toggleCase(nextChar); if (event == EVT_KEY_LONG(KEY_LEFT)) { killEvents(KEY_LEFT); } @@ -240,9 +248,9 @@ void TextEdit::onEvent(event_t event) break; } - if (cursorPos < length && c != v) { + if (cursorPos < length && currentChar != nextChar) { // TRACE("value[%d] = %d", cursorPos, v); - value[cursorPos] = v; + // write(value[cursorPos] = nextChar; invalidate(); changed = true; } @@ -276,7 +284,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) char c = value[cursorPos]; if (c == '\0') break; - uint8_t w = font->getChar(c).width + 1; + uint8_t w = font->getGlyph(c).width + 1; if (rest < w) break; rest -= w; diff --git a/src/textedit.h b/src/textedit.h index b065e24..f8807cb 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -92,33 +92,29 @@ class TextEdit: public FormField } } - static uint8_t getNextChar(uint8_t c, uint8_t previous) + static uint32_t getNextAvailableChar(uint32_t index) { - if (c == ' ' || c == 0) - return (previous >= 'a' && previous <= 'z') ? 'a' : 'A'; - - for (auto & suite: charsSuite) { - if (c == suite[0]) - return suite[1]; + auto end = stdFont.end(); + while (++index < end) { + if (stdFont.getGlyph(index).width > 0) { + return index; + } } - - return c + 1; + return index; } - static uint8_t getPreviousChar(uint8_t c) + static uint32_t getPreviousAvailableChar(uint32_t index) { - if (c == 'A') - return ' '; - - for (auto & suite: charsSuite) { - if (c == suite[1]) - return suite[0]; + auto begin = stdFont.begin(); + while (--index >= begin) { + if (stdFont.getGlyph(index).width > 0) { + return index; + } } - - return c - 1; + return index; } - static uint8_t toggleCase(uint8_t c) + static wchar_t toggleCase(wchar_t c) { if (c >= 'A' && c <= 'Z') return c + 32; // tolower @@ -127,6 +123,8 @@ class TextEdit: public FormField else return c; } + + void onVirtualKeyEvent(event_t event); }; } diff --git a/src/unicode.h b/src/unicode.h new file mode 100644 index 0000000..d3f8d84 --- /dev/null +++ b/src/unicode.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#pragma once + +#include + +inline bool isUnicodeContinuationByte(uint8_t c) +{ + return (c & 0b11000000) == 0b10000000; +} + +template +inline uint32_t getNextUnicodeChar(T & s) +{ + uint8_t c1 = uint8_t(*s++); + if (c1 < 0x80) { + return c1; + } + else if (c1 < 0xE0) { + uint8_t c2 = uint8_t(*s++); + return ((c1 & 0b11111) << 6) + (c2 & 0b111111); + } + else if (c1 < 0xF0) { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + return ((c1 & 0b1111) << 12) + ((c2 & 0b111111) << 6) + (c3 & 0b111111); + } + else { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + uint8_t c4 = uint8_t(*s++); + return ((c1 & 0b111) << 18) + ((c2 & 0b111111) << 12) + ((c3 & 0b111111) << 6) + (c4 & 0b111111); + } +} + +template +inline uint32_t getPreviousUnicodeChar(T * & s) +{ + while (isUnicodeContinuationByte(*(--s))); + auto tmp = s; + return getNextUnicodeChar(tmp); +} + +inline uint32_t getUnicodeStringLength(const char * s, uint8_t size = 0) +{ + const char * pos = s; + uint32_t result = 0; + while (true) { + uint8_t c = *pos; + if (c == 0) + return result; + else if (c < 0x80) + pos += 1; + else if (c < 0xE0) + pos += 2; + else if (c < 0xF0) + pos += 3; + else + pos += 4; + if (size && pos >= s + size) + return result; + result++; + } + return result; +} + +template +inline T * getUnicodeStringAtPosition(T * s, uint8_t index) +{ + while (index-- > 0) { + uint8_t c = *s; + if (c == 0) + return nullptr; + else if (c < 0x80) + s += 1; + else if (c < 0xE0) + s += 2; + else if (c < 0xF0) + s += 3; + else + s += 4; + } + return s; +} + +template +inline uint32_t getUnicodeCharAtPosition(T * s, uint8_t index) +{ + auto pos = getUnicodeStringAtPosition(s, index); + return getNextUnicodeChar(pos); +} + + +inline uint8_t getUnicodeCharLength(uint32_t c) +{ + if (c < 0x80) + return 1; + else if (c <= 0b11111111111) + return 2; + else if (c <= 0b1111111111111111) + return 3; + else + return 4; +} + +inline void writeUnicodeChar(char * s, uint32_t c) +{ + if (c < 0x80) { + *s = c; + } + else if (c <= 0b11111111111) { + *s++ = 0xC0 | ((c >> 6) & 0b11111); + *s = 0b10000000 | (c & 0b111111); + } + else if (c <= 0b1111111111111111) { + *s++ = 0xE0 | ((c >> 12) & 0b1111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } + else { + *s++ = 0xF0 | ((c >> 18) & 0b111); + *s++ = 0b10000000 | ((c >> 12) & 0b111111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } +} + +inline void insertUnicodeChar(char * s, uint8_t position, uint32_t c, uint8_t maxLength) +{ + auto len = getUnicodeCharLength(c); + auto pos = getUnicodeStringAtPosition(s, position); + memmove(pos + len, pos, s + maxLength - pos - len); +} diff --git a/tools/encode-bitmap.py b/tools/encode_bitmap.py similarity index 78% rename from tools/encode-bitmap.py rename to tools/encode_bitmap.py index 7c8751c..5321e8c 100755 --- a/tools/encode-bitmap.py +++ b/tools/encode_bitmap.py @@ -6,7 +6,7 @@ class RawMixin: def encode_byte(self, byte): - self.write(byte) + self.append(byte) def encode_end(self): pass @@ -28,7 +28,7 @@ def eq_prev_byte(self, byte): def encode_byte(self, byte): if self.state == self.RLE_BYTE: - self.write(byte) + self.append(byte) if self.eq_prev_byte(byte): self.state = self.RLE_SEQ self.count = 0 @@ -38,39 +38,42 @@ def encode_byte(self, byte): if self.eq_prev_byte(byte): self.count += 1 if self.count == 255: - self.write(self.count) + self.append(self.count) self.prev_byte = None self.state = self.RLE_BYTE else: - self.write(self.count) - self.write(byte) + self.append(self.count) + self.append(byte) self.prev_byte = byte self.state = self.RLE_BYTE def encode_end(self): if self.state == self.RLE_SEQ: - self.write(self.count) + self.append(self.count) class ImageEncoder: - def __init__(self, filename, size_format, orientation=0): - self.f = open(filename, "w") + def __init__(self, size_format, orientation=0): self.size_format = size_format self.orientation = orientation + self.bytes = [] - def write(self, value): - self.f.write("0x%02x," % value) + def append(self, value): + self.bytes.append(value) + + def extend(self, values): + self.bytes.extend(values) - def write_size(self, width, height): + def append_size(self, width, height): if self.size_format == 2: - self.f.write("%d,%d,%d,%d,\n" % (width % 256, width // 256, height % 256, height // 256)) + self.extend([width % 256, width // 256, height % 256, height // 256]) else: - self.f.write("%d,%d,\n" % (width, height)) + self.extend(width, height) def encode_1bit(self, image, rows): image = image.convert(mode='1') width, height = image.size - self.write_size(width, height // rows) + self.append_size(width, height // rows) for y in range(0, height, 8): for x in range(width): value = 0 @@ -83,13 +86,13 @@ def encode_1bit(self, image, rows): if image.getpixel((x, y + z)) == 0: value += 1 << z self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_4bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) + self.append_size(width, height) for y in range(0, height, 2): for x in range(width): value = 0xFF @@ -104,30 +107,29 @@ def encode_4bits(self, image): if gray2 & (1 << (4 + i)): value -= 1 << (4 + i) self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) + self.append_size(width, height) if self.orientation == 270: for x in range(width): for y in range(height): value = 0xFF - self.get_pixel(image, x, y) self.encode_byte(value) - self.f.write("\n") else: for y in range(height): for x in range(width): value = 0xFF - self.get_pixel(image, x, y) self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_5_6_5(self, image): width, height = image.size - self.write_size(width, height) + self.append_size(width, height) for y in range(height): for x in range(width): pixel = self.get_pixel(image, x, y) @@ -135,10 +137,11 @@ def encode_5_6_5(self, image): self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def encode_4_4_4_4(self, image): width, height = image.size - self.write_size(width, height) + self.append_size(width, height) for y in range(height): for x in range(width): pixel = self.get_pixel(image, x, y) @@ -146,6 +149,7 @@ def encode_4_4_4_4(self, image): self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def get_pixel(self, image, x, y): if self.orientation == 180: @@ -154,12 +158,12 @@ def get_pixel(self, image, x, y): return image.getpixel((x, y)) @staticmethod - def create(filename, size_format=1, orientation=0, encode_mixin=RawMixin): + def create(size_format=1, orientation=0, encode_mixin=RawMixin): class ResultClass(ImageEncoder, encode_mixin): def __init__(self, *args, **kwargs): ImageEncoder.__init__(self, *args, **kwargs) encode_mixin.__init__(self) - return ResultClass(filename, size_format, orientation) + return ResultClass(size_format, orientation) def main(): @@ -175,19 +179,22 @@ def main(): args = parser.parse_args() image = Image.open(args.input) - output = args.output - encoder = ImageEncoder.create(output, args.size_format, args.orientation, RleMixin if args.rle else RawMixin) + encoder = ImageEncoder.create(args.size_format, args.orientation, RleMixin if args.rle else RawMixin) if args.format == "1bit": - encoder.encode_1bit(image, args.rows) + bytes = encoder.encode_1bit(image, args.rows) elif args.format == "4bits": - encoder.encode_4bits(image) + bytes = encoder.encode_4bits(image) elif args.format == "8bits": - encoder.encode_8bits(image) + bytes = encoder.encode_8bits(image) elif args.format == "4/4/4/4": - encoder.encode_4_4_4_4(image) + bytes = encoder.encode_4_4_4_4(image) elif args.format == "5/6/5": - encoder.encode_5_6_5(image) + bytes = encoder.encode_5_6_5(image) + + with open(args.output, "w") as f: + for byte in bytes: + f.write("%d," % byte) if __name__ == "__main__": From 0b20771a3f8dbfab5a38ee173628a47a6d776eaf Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 15 Apr 2025 10:50:37 +0200 Subject: [PATCH 02/54] .gitignore updated --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bb7933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.py[cod] +__pycache__/ From fb4383c8f87559ee40adb4a6df539aba2e3e8224 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 15 Apr 2025 22:59:58 +0200 Subject: [PATCH 03/54] Fixes --- src/font.h | 6 +++--- src/keyboard_text.cpp | 48 ++++++++++++++++++------------------------- src/textedit.h | 4 ++-- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/font.h b/src/font.h index 304d494..29fa846 100644 --- a/src/font.h +++ b/src/font.h @@ -101,7 +101,7 @@ class Font Glyph getGlyph(wchar_t index) const { for (auto & range: ranges) { - if (range.begin <= index && index < range.end) { + if (range.begin <= uint32_t(index) && uint32_t(index) < range.end) { index -= range.begin; auto offset = range.specs[index]; return {range.data, offset, uint8_t(range.specs[index + 1] - offset)}; @@ -183,7 +183,7 @@ class Font { wchar_t result = ' '; for (auto & range: ranges) { - if (range.begin < result) { + if (range.begin < uint32_t(result)) { result = range.begin; } } @@ -194,7 +194,7 @@ class Font { wchar_t result = ' '; for (auto & range: ranges) { - if (range.end > result) { + if (range.end > uint32_t(result)) { result = range.end; } } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index 2343a93..234c6f0 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -60,49 +60,49 @@ const char * const KEYBOARD_QWERTY_LOWERCASE[] = { "qwertyuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "zxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_LOWERCASE[] = { "azertyuiop", " qsdfghjklm", KEYBOARD_SET_UPPERCASE "wxcvbn," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_LOWERCASE[] = { "qwertzuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "yxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTY_UPPERCASE[] = { "QWERTYUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "ZXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_UPPERCASE[] = { "AZERTYUIOP", " QSDFGHJKLM", KEYBOARD_SET_LOWERCASE "WXCVBN," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_UPPERCASE[] = { "QWERTZUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "YXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_NUMBERS[] = { "1234567890", KEYBOARD_SPECIAL_CHAR_1 KEYBOARD_SPECIAL_CHAR_2 KEYBOARD_SPECIAL_CHAR_3 KEYBOARD_SPECIAL_CHAR_4 KEYBOARD_SPECIAL_CHAR_5 KEYBOARD_SPECIAL_CHAR_6 KEYBOARD_SPECIAL_CHAR_7 KEYBOARD_SPECIAL_CHAR_8 KEYBOARD_SPECIAL_CHAR_9 KEYBOARD_SPECIAL_CHAR_10, KEYBOARD_SPECIAL_CHAR_11 KEYBOARD_SPECIAL_CHAR_12 KEYBOARD_SPECIAL_CHAR_13 KEYBOARD_SPECIAL_CHAR_14 KEYBOARD_SPECIAL_CHAR_15 KEYBOARD_SPECIAL_CHAR_16 KEYBOARD_SPECIAL_CHAR_17 KEYBOARD_SPECIAL_CHAR_18 KEYBOARD_SPECIAL_CHAR_19 KEYBOARD_BACKSPACE, - KEYBOARD_SET_LETTERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_LETTERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const * KEYBOARDS[] = { @@ -139,29 +139,22 @@ void TextKeyboard::paint(BitmapBuffer * dc) if (*c == ' ') { x += 15; } - else if (uint8_t(*c) < KEYBOARD_SPACE[0]) { + else if (uint8_t(*c) <= SPECIAL_KEY_WITH_BITMAP_LAST) { // special keys drawn with a bitmap dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); x += 45; } - else if (*c == KEYBOARD_SPACE[0]) { + else if (*c == SPECIAL_KEY_SPACEBAR) { // spacebar dc->drawMask(x, y, (const BitmapData *)LBM_KEY_SPACEBAR, DEFAULT_COLOR); x += 135; } - else if (*c == KEYBOARD_ENTER[0]) { + else if (*c == SPECIAL_KEY_ENTER) { // enter dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); dc->drawText(x + 40, y, "ENTER", DEFAULT_COLOR, CENTERED); x += 80; } - else if (uint8_t(*c) <= KEYBOARD_SPECIAL_CHAR_LAST[0]) { - // special chars - dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); - auto pos = getUnicodeStringAtPosition(STR(SPECIAL_CHARS), uint8_t(*c) - KEYBOARD_SPECIAL_CHAR_1[0]); - dc->drawSizedText(x + 40, y, pos, 1, DEFAULT_COLOR, CENTERED); - x += 80; - } else { dc->drawSizedText(x, y, c, 1, DEFAULT_COLOR); x += 30; @@ -178,19 +171,19 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) onKeyPress(); uint8_t row = max(0, y - 5) / 40; - const char * key = layout[row]; + const uint8_t * key = (uint8_t *)layout[row]; while (*key) { if (*key == ' ') { x -= 15; } - else if (*key == KEYBOARD_SPACE[0]) { + else if (*key == SPECIAL_KEY_SPACEBAR) { if (x <= 135) { pushEvent(EVT_VIRTUAL_KEY(' ')); return true; } x -= 135; } - else if (*key == KEYBOARD_ENTER[0]) { + else if (*key == SPECIAL_KEY_ENTER) { if (x <= 80) { // enter hide(); @@ -198,27 +191,26 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) } x -= 80; } - else if (uint8_t(*key) <= KEYBOARD_SET_NUMBERS[0]) { + else if (*key <= SPECIAL_KEY_WITH_BITMAP_LAST) { if (x <= 45) { - uint8_t specialKey = *key; - switch (specialKey) { - case KEYBOARD_BACKSPACE[0]: + switch (*key) { + case SPECIAL_KEY_BACKSPACE: // backspace events.push(EVT_VIRTUAL_KEY(SPECIAL_KEY_BACKSPACE)); break; - case KEYBOARD_SET_UPPERCASE[0]: + case SPECIAL_KEY_SET_UPPERCASE: layoutIndex = getKeyboardLayout(); invalidate(); break; - case KEYBOARD_SET_LOWERCASE[0]: + case SPECIAL_KEY_SET_LOWERCASE: layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; - case KEYBOARD_SET_LETTERS[0]: + case SPECIAL_KEY_SET_LETTERS: layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; - case KEYBOARD_SET_NUMBERS[0]: + case SPECIAL_KEY_SET_NUMBERS: layoutIndex = KEYBOARD_LAYOUT_NUMBERS; invalidate(); break; diff --git a/src/textedit.h b/src/textedit.h index 5116090..dde0642 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -101,7 +101,7 @@ class TextEdit: public FormField static uint32_t getNextAvailableChar(uint32_t index) { auto end = stdFont.end(); - while (++index < end) { + while (++index < uint32_t(end)) { if (stdFont.getGlyph(index).width > 0) { return index; } @@ -112,7 +112,7 @@ class TextEdit: public FormField static uint32_t getPreviousAvailableChar(uint32_t index) { auto begin = stdFont.begin(); - while (--index >= begin) { + while (--index >= uint32_t(begin)) { if (stdFont.getGlyph(index).width > 0) { return index; } From 38edee44aa15aa47ae9d200facc95ddd3c09b94d Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 16 Apr 2025 12:17:38 +0200 Subject: [PATCH 04/54] Fixes --- src/font.h | 29 ++++++++------------- src/textedit.cpp | 65 +++++++++++++++--------------------------------- 2 files changed, 30 insertions(+), 64 deletions(-) diff --git a/src/font.h b/src/font.h index 29fa846..4eb4995 100644 --- a/src/font.h +++ b/src/font.h @@ -27,25 +27,16 @@ constexpr uint8_t LEN_FONT_NAME = 8; -// TODO -// inline const char * findNextLine(const char * str) -// { - // while (true) { - // auto c = getNextUnicodeChar(str); - // if (c == '\0') - // return nullptr; - // else if (c == '\n') - // return - - - // const char * pos = strchr(stack, '\n'); - // if (!pos) - // return nullptr; - // if (pos == stack || *((uint8_t *)(pos - 1)) < CJK_BYTE1_MIN) - // return pos; - // stack = pos + 1; - // } -// } +inline const char * findNextLine(const char * str) +{ + while (true) { + auto c = getNextUnicodeChar(str); + if (c == '\0') + return nullptr; + else if (c == '\n') + return str; + } +} class Font { diff --git a/src/textedit.cpp b/src/textedit.cpp index 78fbe32..e5917d5 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -97,13 +97,12 @@ void TextEdit::paint(BitmapBuffer * dc) void TextEdit::trim() { - /* TODO */ - /*for (int i = length - 1; i >= 0; i--) { + for (int i = length - 1; i >= 0; i--) { if (value[i] == ' ' || value[i] == '\0') value[i] = '\0'; else break; - }*/ + } } #if defined(SOFTWARE_KEYBOARD) @@ -122,27 +121,6 @@ void TextEdit::onVirtualKeyEvent(event_t event) changed = true; } } -#if defined(KEYBOARD_DELETE) - else if (c == SPECIAL_KEY_DELETE) { - // TODO check this! - if (cursorPos < length - 1) { - memmove(value + cursorPos, value + cursorPos + 1, length - cursorPos - 1); - value[length - 1] = '\0'; - invalidate(); - changed = true; - } - } -#endif -#if defined(KEYBOARD_HOME) - else if (c == SPECIAL_KEY_HOME) { - setCursorPos(0); - } -#endif -#if defined(KEYBOARD_END) - else if (c == SPECIAL_KEY_END) { - setCursorPos(strlen(value)); // TODO NOT GOOD! - } -#endif else { auto len = getUnicodeCharLength(c); if (strlen(value) + len <= length) { @@ -191,33 +169,30 @@ void TextEdit::onEvent(event_t event) break; case EVT_KEY_BREAK(KEY_RIGHT): - { if (cursorPos < getUnicodeStringLength(value)) { cursorPos++; invalidate(); } break; - } - // TODO - // case EVT_KEY_BREAK(KEY_ENTER): - // if (cursorPos < length - 1) { - // if (value[cursorPos] == '\0') { - // value[cursorPos] = ' '; - // changed = true; - // } - // cursorPos++; - // if (value[cursorPos] == '\0') { - // value[cursorPos] = ' '; - // changed = true; - // } - // invalidate(); - // } - // else { - // changeEnd(); - // FormField::onEvent(event); - // } - // break; + case EVT_KEY_BREAK(KEY_ENTER): + if (cursorPos < length - 1) { + if (value[cursorPos] == '\0') { + value[cursorPos] = ' '; + changed = true; + } + cursorPos++; + if (value[cursorPos] == '\0') { + value[cursorPos] = ' '; + changed = true; + } + invalidate(); + } + else { + changeEnd(); + FormField::onEvent(event); + } + break; case EVT_KEY_BREAK(KEY_EXIT): changeEnd(); From c6877fb19b4049e7a99e741b35e793389e171d16 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 16 Apr 2025 12:21:02 +0200 Subject: [PATCH 05/54] Fixes --- src/static.cpp | 3 +-- src/textedit.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/static.cpp b/src/static.cpp index 9ebf323..6716bb1 100755 --- a/src/static.cpp +++ b/src/static.cpp @@ -39,7 +39,6 @@ coord_t StaticText::drawText(BitmapBuffer * dc, const rect_t & rect, const std:: auto start = text.c_str(); auto current = start; - /* TODO duplicated code! auto nextline = findNextLine(start); if (nextline) { do { @@ -48,7 +47,7 @@ coord_t StaticText::drawText(BitmapBuffer * dc, const rect_t & rect, const std:: nextline = findNextLine(current); y += fontHeight + STATIC_TEXT_INTERLINE_HEIGHT; } while (nextline); - } */ + } dc->drawText(x, y, current, textColor, textFlags); y += fontHeight + STATIC_TEXT_INTERLINE_HEIGHT; return y; diff --git a/src/textedit.cpp b/src/textedit.cpp index e5917d5..c648691 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -105,7 +105,7 @@ void TextEdit::trim() } } -#if defined(SOFTWARE_KEYBOARD) +#if defined(SOFTWARE_KEYBOARD) || defined(SIMULATION) void TextEdit::onVirtualKeyEvent(event_t event) { auto c = event & MSK_VIRTUAL_KEY; From ca970dcac539b762ae7cbe0e8dcc023d44ee6bca Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 16 Apr 2025 23:16:26 +0200 Subject: [PATCH 06/54] Unused definition --- src/libopenui_types.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libopenui_types.h b/src/libopenui_types.h index 6eea3ce..48e167b 100644 --- a/src/libopenui_types.h +++ b/src/libopenui_types.h @@ -104,4 +104,3 @@ typedef uint16_t event_t; typedef uint16_t Color565; typedef uint32_t LcdColor; typedef uint32_t WindowFlags; -typedef uint8_t charSuite[2]; From 48ef1f67db05f3554b8b16200b57ce403d82654c Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 17 Apr 2025 09:01:18 +0200 Subject: [PATCH 07/54] Font files format has changed. Use a different fourcc --- src/font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font.cpp b/src/font.cpp index 4af8872..ca01099 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -90,7 +90,7 @@ bool Font::loadFile(const char * path) } header; UINT read; result = f_read(file, (uint8_t *)&header, sizeof(header), &read); - if (result != FR_OK || read != sizeof(header) || strncmp(header.fmt, "FNT0", sizeof(header.fmt)) != 0) { + if (result != FR_OK || read != sizeof(header) || strncmp(header.fmt, "FNT1", sizeof(header.fmt)) != 0) { TRACE("loadFont('%s'): invalid header", path); f_close(file); free(file); From 47a94a025af1e6a00277567f7338ec723d5eab5a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 17 Apr 2025 11:12:37 +0200 Subject: [PATCH 08/54] Rename BOLD to STD_BOLD --- src/static.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static.h b/src/static.h index 97979df..561a819 100644 --- a/src/static.h +++ b/src/static.h @@ -111,7 +111,7 @@ class Subtitle: public StaticText { public: Subtitle(Window * parent, const rect_t & rect, const char * text): - StaticText(parent, rect, text, 0, FONT(BOLD)) + StaticText(parent, rect, text, 0, FONT(STD_BOLD)) { } From 557dc6e89f1bd467af50b1c13e05c56752c18db3 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 17 Apr 2025 11:34:07 +0200 Subject: [PATCH 09/54] Font files format has changed. Use a different fourcc --- src/font.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/font.cpp b/src/font.cpp index ca01099..ae7be8d 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -60,7 +60,7 @@ void rotateBitmapData(BitmapData * bitmap) } /* Font format - * 'F', 'N', 'T', '\0' + * 'F', 'N', 'T', '1' * begin: 4bytes (glyphs index start) * end: 4bytes (glyphs index end, not included) * specs: 2bytes * (count + 1) From 61d6e328ae3c4ed86e073c38076bb53e5f640d42 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 21 Apr 2025 23:26:56 +0200 Subject: [PATCH 10/54] Fix --- src/libopenui_compat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libopenui_compat.h b/src/libopenui_compat.h index a552df5..376235d 100644 --- a/src/libopenui_compat.h +++ b/src/libopenui_compat.h @@ -20,6 +20,7 @@ #pragma once #if defined(__MINGW32__) || !defined(__GNUC__) + #undef UNICODE #include #include #include From bab87f2e62aa492b903906bb55c1be50e0065bf7 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 22 Apr 2025 15:20:00 +0200 Subject: [PATCH 11/54] Fix --- src/font.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/font.h b/src/font.h index 4eb4995..9ac6f27 100644 --- a/src/font.h +++ b/src/font.h @@ -30,11 +30,12 @@ constexpr uint8_t LEN_FONT_NAME = 8; inline const char * findNextLine(const char * str) { while (true) { + auto current = str; auto c = getNextUnicodeChar(str); if (c == '\0') return nullptr; else if (c == '\n') - return str; + return current; } } From c7d06def3c43815938e4be17cac4e4e1c30f69a8 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 23 Apr 2025 13:09:46 +0200 Subject: [PATCH 12/54] Change FONT "STD" to FONT "M" --- src/menutoolbar.cpp | 4 ++-- src/modal_window.cpp | 2 +- src/roller.h | 2 +- src/static.h | 2 +- src/textedit.cpp | 2 +- src/textedit.h | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/menutoolbar.cpp b/src/menutoolbar.cpp index dd13169..a5610b9 100644 --- a/src/menutoolbar.cpp +++ b/src/menutoolbar.cpp @@ -26,9 +26,9 @@ void MenuToolbarButton::paint(BitmapBuffer * dc) { if (checked()) { dc->drawPlainFilledRectangle(MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, FOCUS_BGCOLOR); - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); } else { - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); } } \ No newline at end of file diff --git a/src/modal_window.cpp b/src/modal_window.cpp index 35aa644..1b80c92 100644 --- a/src/modal_window.cpp +++ b/src/modal_window.cpp @@ -49,6 +49,6 @@ void ModalWindow::paint(BitmapBuffer * dc) void ModalWindowContent::paint(BitmapBuffer * dc) { dc->drawPlainFilledRectangle(0, 0, width(), POPUP_HEADER_HEIGHT, FOCUS_BGCOLOR); - dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(STD))) / 2, title.c_str(), FOCUS_COLOR); + dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(M))) / 2, title.c_str(), FOCUS_COLOR); dc->drawPlainFilledRectangle(0, POPUP_HEADER_HEIGHT, width(), height() - POPUP_HEADER_HEIGHT, DEFAULT_BGCOLOR); } diff --git a/src/roller.h b/src/roller.h index afd8cea..c8a44b2 100644 --- a/src/roller.h +++ b/src/roller.h @@ -93,7 +93,7 @@ class Roller: public Choice auto fgColor = DISABLE_COLOR; if (value == displayedValue) { - fgColor = FOCUS_COLOR | FONT(STD); + fgColor = FOCUS_COLOR | FONT(M); } unsigned valueIndex = displayedValue - vmin; diff --git a/src/static.h b/src/static.h index 561a819..931b1cd 100644 --- a/src/static.h +++ b/src/static.h @@ -111,7 +111,7 @@ class Subtitle: public StaticText { public: Subtitle(Window * parent, const rect_t & rect, const char * text): - StaticText(parent, rect, text, 0, FONT(STD_BOLD)) + StaticText(parent, rect, text, 0, FONT(M_BOLD)) { } diff --git a/src/textedit.cpp b/src/textedit.cpp index c648691..51ad5ab 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -279,7 +279,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) TextKeyboard::show(this); #endif - auto font = getFont(FONT(STD)); + auto font = getFont(FONT(M)); coord_t rest = x; for (cursorPos = 0; cursorPos < length; cursorPos++) { diff --git a/src/textedit.h b/src/textedit.h index dde0642..a92a6cc 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -100,9 +100,9 @@ class TextEdit: public FormField static uint32_t getNextAvailableChar(uint32_t index) { - auto end = stdFont.end(); + auto end = mFont.end(); while (++index < uint32_t(end)) { - if (stdFont.getGlyph(index).width > 0) { + if (mFont.getGlyph(index).width > 0) { return index; } } @@ -111,9 +111,9 @@ class TextEdit: public FormField static uint32_t getPreviousAvailableChar(uint32_t index) { - auto begin = stdFont.begin(); + auto begin = mFont.begin(); while (--index >= uint32_t(begin)) { - if (stdFont.getGlyph(index).width > 0) { + if (mFont.getGlyph(index).width > 0) { return index; } } From 612dfba7d5b5749131938e2eb3a69efb1d759673 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 29 Apr 2025 15:12:42 +0200 Subject: [PATCH 13/54] Fonts fixes --- src/font.cpp | 5 +++++ src/font.h | 35 +++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/font.cpp b/src/font.cpp index ae7be8d..b360788 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -158,6 +158,11 @@ bool Font::loadFile(const char * path) ranges.push_back({rangeHeader.begin, rangeHeader.end, bitmapData, specs}); } + // TRACE("Ranges..."); + // for (auto & range: ranges) { + // TRACE(" %d-%d", range.begin, range.end); + // } + f_close(file); free(file); diff --git a/src/font.h b/src/font.h index 9ac6f27..044f9b3 100644 --- a/src/font.h +++ b/src/font.h @@ -65,8 +65,8 @@ class Font strlcpy(this->name, name, sizeof(this->name)); } - Font(const char * name, const BitmapData * data, const uint16_t * specs): - ranges({{0x21, 0x7F, data, specs}}) + Font(const char * name, std::list ranges): + ranges(std::move(ranges)) { strlcpy(this->name, name, sizeof(this->name)); } @@ -76,10 +76,15 @@ class Font void addGlyphs(const Font * other) { for (auto range: other->ranges) { - ranges.push_back(range); + addRange(range); } } + void addRange(const GlyphRange & range) + { + ranges.push_back(range); + } + coord_t getHeight() const { return ranges.empty() ? 0 : ranges.front().data->height(); @@ -90,13 +95,16 @@ class Font return name; } - Glyph getGlyph(wchar_t index) const + Glyph getGlyph(wchar_t c) const { for (auto & range: ranges) { - if (range.begin <= uint32_t(index) && uint32_t(index) < range.end) { - index -= range.begin; - auto offset = range.specs[index]; - return {range.data, offset, uint8_t(range.specs[index + 1] - offset)}; + if (range.begin <= uint32_t(c) && uint32_t(c) < range.end) { + auto index = c - range.begin; + unsigned offset = range.specs[index]; + uint8_t width = range.specs[index + 1] - offset; + if (width > 0) { + return {range.data, offset, width}; + } } } return {}; @@ -193,8 +201,19 @@ class Font return result; } + uint8_t isSubsetLoaded(uint8_t index) const + { + return subsetsMask & (1 << index); + } + + void setSubsetLoaded(uint8_t index) + { + subsetsMask |= 1 << index; + } + protected: char name[LEN_FONT_NAME + 1]; + uint8_t subsetsMask = 0; uint8_t spacing = 1; uint8_t spaceWidth = 4; std::list ranges; From 92b86e379088c7af99589c0635bb8fb41e923f31 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 7 May 2025 19:52:20 +0200 Subject: [PATCH 14/54] Refactoring --- src/bitmapbuffer.cpp | 2 +- src/bitmapbuffer.h | 70 +++++++++++++++++++++++++++--------------- src/filechoice.cpp | 2 +- src/menu.cpp | 2 +- src/menu.h | 8 ++--- src/static.h | 8 ++--- tools/encode_bitmap.py | 27 +++++++++++++--- 7 files changed, 79 insertions(+), 40 deletions(-) diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 7627adb..4fa289a 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -542,7 +542,7 @@ void BitmapBuffer::drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coo fillRectangle(x, y, w, h, RGB565_TO_ARGB4444(color, 0xFF)); } -void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color) +void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const StaticMask * mask, Color565 color) { coord_t maskHeight = mask->height(); while (h > 0) { diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 5473551..80ac9a3 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "bitmapdata.h" #include "libopenui_types.h" #include "libopenui_defines.h" @@ -63,13 +64,13 @@ class BitmapBufferBase { } - BitmapBufferBase(uint8_t format, T * data): - format(format), - _width(*((uint16_t*)data)), - _height(*(((uint16_t*)data) + 1)), + BitmapBufferBase(const uint8_t * data): + format(*(data)), + _width(*((uint16_t *)(data + 1))), + _height(*((uint16_t *)(data + 3))), xmax(_width), ymax(_height), - data((T *)(((uint16_t *)data) + 2)), + data((T *)(uint16_t *)(data + 5)), dataEnd((T *)(((uint16_t *)data) + 2) + (_width * _height)) { } @@ -256,6 +257,24 @@ class BitmapBufferBase return &data[y * _width + x]; } + template + C * invert() const + { + auto result = C::allocate(format, width(), height()); + if (result) { + auto * srcData = data; + auto * destData = result->getData(); + for (auto y = 0; y < height(); y++) { + for (auto x = 0; x < width(); x++) { + destData[x] = 0xFF - srcData[x]; + } + srcData += width(); + destData += width(); + } + } + return result; + } + template C * horizontalFlip() const { @@ -274,7 +293,7 @@ class BitmapBufferBase } #else auto * srcData = data + width(); - auto * destData = result->data; + auto * destData = result->getData(); for (uint8_t y = 0; y < height(); y++) { for (uint8_t x = 0; x < width(); x++) { *(destData++) = *(--srcData); @@ -478,6 +497,16 @@ class RLEBitmap: public BitmapBufferBase class BitmapMask: public BitmapBufferBase { public: + mutable std::optional> _constView; + + const BitmapBufferBase* asConstView() const + { + if (!_constView.has_value()) { + _constView.emplace(format, width(), height(), data); + } + return &*_constView; + } + static BitmapMask * allocate(uint8_t format, uint16_t width, uint16_t height) { auto result = new BitmapMask(format, width, height); @@ -500,23 +529,6 @@ class BitmapMask: public BitmapBufferBase free(data); } - [[nodiscard]] BitmapMask * invert() const - { - auto result = BitmapMask::allocate(format, width(), height()); - if (result) { - auto * srcData = data; - auto * destData = result->data; - for (auto y = 0; y < height(); y++) { - for (auto x = 0; x < width(); x++) { - destData[x] = 0xFF - srcData[x]; - } - srcData += width(); - destData += width(); - } - } - return result; - } - static BitmapMask * load(const char * filename, int maxSize = -1); }; @@ -533,6 +545,16 @@ class BitmapBuffer: public BitmapBufferBase return result; } + mutable std::optional> _constView; + + const BitmapBufferBase* asConstView() const + { + if (!_constView.has_value()) { + _constView.emplace(format, width(), height(), data); + } + return &*_constView; + } + protected: BitmapBuffer(uint8_t format, uint16_t width, uint16_t height); @@ -623,7 +645,7 @@ class BitmapBuffer: public BitmapBufferBase void drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color); - void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color); + void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const StaticMask * mask, Color565 color); void drawCircle(coord_t x, coord_t y, coord_t radius, LcdColor color); diff --git a/src/filechoice.cpp b/src/filechoice.cpp index c1a5c42..8b18c13 100644 --- a/src/filechoice.cpp +++ b/src/filechoice.cpp @@ -117,7 +117,7 @@ bool FileChoice::openMenu() } } - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, exclamationIcon); + new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, &maskIconExclamation); return false; } diff --git a/src/menu.cpp b/src/menu.cpp index 44e8c6a..2104d6c 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -235,7 +235,7 @@ void Menu::setTitle(std::string text) updatePosition(); } -void Menu::addLine(const std::string & text, const BitmapMask * mask, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addLine(const std::string & text, const StaticMask * mask, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addLine(text, mask, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (content->width() < MAX_MENUS_WIDTH) { diff --git a/src/menu.h b/src/menu.h index 690a28e..8f4f2a2 100644 --- a/src/menu.h +++ b/src/menu.h @@ -42,7 +42,7 @@ class MenuBody: public Window friend class MenuBody; public: - MenuLine(std::string text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::string text, const StaticMask * icon, std::function onPress, std::function onSelect, std::function isChecked): text(std::move(text)), icon(icon), onPress(std::move(onPress)), @@ -65,7 +65,7 @@ class MenuBody: public Window protected: std::string text; - const BitmapMask * icon; + const StaticMask * icon; std::function drawLine; std::function onPress; std::function onSelect; @@ -106,7 +106,7 @@ class MenuBody: public Window bool onTouchEnd(coord_t x, coord_t y) override; #endif - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked) + void addLine(const std::string & text, const StaticMask * icon, std::function onPress, std::function onSelect, std::function isChecked) { lines.emplace_back(text, icon, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (icon) @@ -211,7 +211,7 @@ class Menu: public ModalWindow void setTitle(std::string text); - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addLine(const std::string & text, const StaticMask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void addLine(const std::string & text, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); diff --git a/src/static.h b/src/static.h index 931b1cd..db90514 100644 --- a/src/static.h +++ b/src/static.h @@ -146,7 +146,7 @@ class StaticBitmap: public Window { } - StaticBitmap(Window * parent, const rect_t & rect, const BitmapMask * mask, LcdFlags color, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const StaticMask * mask, LcdFlags color, bool scale = false): Window(parent, rect), mask(mask), color(color), @@ -171,9 +171,9 @@ class StaticBitmap: public Window invalidate(); } - void setMask(const BitmapMask * newMask) + void setMask(const StaticMask * newMask) { - delete mask; + // delete mask; TODO ??? mask = newMask; invalidate(); } @@ -199,7 +199,7 @@ class StaticBitmap: public Window } protected: - const BitmapMask * mask = nullptr; + const StaticMask * mask = nullptr; const BitmapBuffer * bitmap = nullptr; LcdFlags color = 0; bool scale = false; diff --git a/tools/encode_bitmap.py b/tools/encode_bitmap.py index 5321e8c..067fbaa 100755 --- a/tools/encode_bitmap.py +++ b/tools/encode_bitmap.py @@ -70,6 +70,16 @@ def append_size(self, width, height): else: self.extend(width, height) + @staticmethod + def guess_format(image): + if image.mode == "P": + print("Indexed bitmap, will use RGBA") + return "4/4/4/4" + elif image.mode == "RGBA": + return "4/4/4/4" + else: + return "5/6/5" + def encode_1bit(self, image, rows): image = image.convert(mode='1') width, height = image.size @@ -113,6 +123,7 @@ def encode_4bits(self, image): def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size + self.append(0) self.append_size(width, height) if self.orientation == 270: for x in range(width): @@ -129,10 +140,12 @@ def encode_8bits(self, image): def encode_5_6_5(self, image): width, height = image.size + self.append(0) self.append_size(width, height) for y in range(height): for x in range(width): pixel = self.get_pixel(image, x, y) + # print(pixel) val = ((pixel[0] >> 3) << 11) + ((pixel[1] >> 2) << 5) + ((pixel[2] >> 3) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) @@ -141,6 +154,7 @@ def encode_5_6_5(self, image): def encode_4_4_4_4(self, image): width, height = image.size + self.append(1) self.append_size(width, height) for y in range(height): for x in range(width): @@ -181,15 +195,18 @@ def main(): image = Image.open(args.input) encoder = ImageEncoder.create(args.size_format, args.orientation, RleMixin if args.rle else RawMixin) - if args.format == "1bit": + format = args.format + if format == "auto": + format = encoder.guess_format(image) + if format == "1bit": bytes = encoder.encode_1bit(image, args.rows) - elif args.format == "4bits": + elif format == "4bits": bytes = encoder.encode_4bits(image) - elif args.format == "8bits": + elif format == "8bits": bytes = encoder.encode_8bits(image) - elif args.format == "4/4/4/4": + elif format == "4/4/4/4": bytes = encoder.encode_4_4_4_4(image) - elif args.format == "5/6/5": + elif format == "5/6/5": bytes = encoder.encode_5_6_5(image) with open(args.output, "w") as f: From 57e16deb8e35e8bc72ab76cf91adc23242a88173 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 8 May 2025 19:56:46 +0200 Subject: [PATCH 15/54] Fixes --- src/bitmapbuffer.cpp | 2 +- src/bitmapbuffer.h | 18 ++++-------- src/bitmapdata.h | 66 ++++++++++++++++++++++++++++++------------ src/bufferedwindow.h | 4 +-- src/font.cpp | 27 ++--------------- src/keyboard_text.cpp | 12 ++++---- tools/encode_bitmap.py | 34 +++++++++++++++------- 7 files changed, 87 insertions(+), 76 deletions(-) diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 4fa289a..3abedaa 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -742,7 +742,7 @@ void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * im auto bitmap = (BitmapData *)img; coord_t width = bitmap->width(); coord_t height = bitmap->height(); - const uint8_t * q = bitmap->data; + const uint8_t * q = bitmap->getData(); int w2 = width / 2; int h2 = height / 2; diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 80ac9a3..c186c6d 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -65,13 +65,7 @@ class BitmapBufferBase } BitmapBufferBase(const uint8_t * data): - format(*(data)), - _width(*((uint16_t *)(data + 1))), - _height(*((uint16_t *)(data + 3))), - xmax(_width), - ymax(_height), - data((T *)(uint16_t *)(data + 5)), - dataEnd((T *)(((uint16_t *)data) + 2) + (_width * _height)) + BitmapBufferBase(*((uint32_t *)data), *((uint16_t *)(data + 4)), *((uint16_t *)(data + 6)), (T *)(data + 8)) { } @@ -322,7 +316,7 @@ class BitmapBufferBase } #else auto * srcData = getDataEnd() - width(); - auto * destData = result->data; + auto * destData = result->getData(); for (uint8_t y = 0; y < height(); y++) { for (uint8_t x = 0; x < width(); x++) { *(destData++) = *(srcData++); @@ -393,7 +387,7 @@ class BitmapBufferBase } } #else - auto * destData = result->data + height(); + auto * destData = result->getData() + height(); for (uint8_t y = 0; y < width(); y++) { auto * srcData = data + y; for (uint8_t x = 0; x < height(); x++) { @@ -499,7 +493,7 @@ class BitmapMask: public BitmapBufferBase public: mutable std::optional> _constView; - const BitmapBufferBase* asConstView() const + const BitmapBufferBase * asConstView() const { if (!_constView.has_value()) { _constView.emplace(format, width(), height(), data); @@ -547,10 +541,10 @@ class BitmapBuffer: public BitmapBufferBase mutable std::optional> _constView; - const BitmapBufferBase* asConstView() const + const BitmapBufferBase * asConstView() const { if (!_constView.has_value()) { - _constView.emplace(format, width(), height(), data); + _constView.emplace(format, width(), height(), data); } return &*_constView; } diff --git a/src/bitmapdata.h b/src/bitmapdata.h index c02ce84..43f4114 100644 --- a/src/bitmapdata.h +++ b/src/bitmapdata.h @@ -21,24 +21,52 @@ #include -struct BitmapData +class BitmapData { - uint16_t _width; - uint16_t _height; - uint8_t data[]; - - uint16_t width() const - { - return _width; - } - - uint16_t height() const - { - return _height; - } - - const uint8_t * getData() const - { - return data; - } + public: + uint16_t width() const + { + return _width; + } + + uint16_t height() const + { + return _height; + } + + const uint8_t * getData() const + { + return data; + } + + // TODO duplicated code + void flip() + { + auto ptr1 = &data[0]; + auto ptr2 = &data[height() * width() - 1]; + while (ptr2 > ptr1) { + std::swap(*ptr1++, *ptr2--); + } + } + + // TODO duplicated code + void rotate() + { + auto dataSize = width() * height(); + auto * srcData = (uint8_t *)malloc(dataSize); + memcpy(srcData, data, dataSize); + auto * destData = &data[0]; + for (uint16_t y = 0; y < height(); y++) { + for (uint16_t x = 0; x < width(); x++) { + destData[x * height() + y] = srcData[y * width() + x]; + } + } + free(srcData); + } + + protected: + uint32_t format; // alignment + uint16_t _width; + uint16_t _height; + uint8_t data[]; }; diff --git a/src/bufferedwindow.h b/src/bufferedwindow.h index 4603c58..60b2833 100644 --- a/src/bufferedwindow.h +++ b/src/bufferedwindow.h @@ -80,7 +80,7 @@ class TransparentBufferedWindow: public BufferedWindow class TransparentBitmapBackground: public TransparentBufferedWindow { public: - TransparentBitmapBackground(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap): + TransparentBitmapBackground(Window * parent, const rect_t & rect, const Bitmap * bitmap): TransparentBufferedWindow(parent, rect, OPAQUE), background(bitmap) { @@ -94,7 +94,7 @@ class TransparentBitmapBackground: public TransparentBufferedWindow #endif protected: - const BitmapBuffer * background; + const Bitmap * background; void paintUpdate(BitmapBuffer * dc) override { diff --git a/src/font.cpp b/src/font.cpp index b360788..7b82453 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -36,29 +36,6 @@ BitmapData * decodeBitmapData(const uint8_t * data) return (BitmapData *)buffer; } -void flipBitmapData(BitmapData * bitmap) -{ - auto ptr1 = &bitmap->data[0]; - auto ptr2 = &bitmap->data[bitmap->height() * bitmap->width() - 1]; - while (ptr2 > ptr1) { - std::swap(*ptr1++, *ptr2--); - } -} - -void rotateBitmapData(BitmapData * bitmap) -{ - auto dataSize = bitmap->width() * bitmap->height(); - auto * srcData = (uint8_t *)malloc(dataSize); - memcpy(srcData, bitmap->data, dataSize); - auto * destData = &bitmap->data[0]; - for (coord_t y = 0; y < bitmap->height(); y++) { - for (coord_t x = 0; x < bitmap->width(); x++) { - destData[x * bitmap->height() + y] = srcData[y * bitmap->width() + x]; - } - } - free(srcData); -} - /* Font format * 'F', 'N', 'T', '1' * begin: 4bytes (glyphs index start) @@ -151,9 +128,9 @@ bool Font::loadFile(const char * path) auto bitmapData = decodeBitmapData(data); #if LCD_ORIENTATION == 180 - flipBitmapData(bitmapData); + bitmapData.flip(); #elif LCD_ORIENTATION == 270 - rotateBitmapData(bitmapData); + bitmapData.rotate(); #endif ranges.push_back({rangeHeader.begin, rangeHeader.end, bitmapData, specs}); } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index 234c6f0..4d9f7e4 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -25,27 +25,27 @@ using namespace ui; TextKeyboard * TextKeyboard::_instance = nullptr; const uint8_t LBM_KEY_UPPERCASE[] = { -#include "mask_key_uppercase.lbm" +#include "bitmaps/mask_key_uppercase.lbm" }; const uint8_t LBM_KEY_LOWERCASE[] = { -#include "mask_key_lowercase.lbm" +#include "bitmaps/mask_key_lowercase.lbm" }; const uint8_t LBM_KEY_BACKSPACE[] = { -#include "mask_key_backspace.lbm" +#include "bitmaps/mask_key_backspace.lbm" }; const uint8_t LBM_KEY_LETTERS[] = { -#include "mask_key_letters.lbm" +#include "bitmaps/mask_key_letters.lbm" }; const uint8_t LBM_KEY_NUMBERS[] = { -#include "mask_key_numbers.lbm" +#include "bitmaps/mask_key_numbers.lbm" }; const uint8_t LBM_KEY_SPACEBAR[] = { -#include "mask_key_spacebar.lbm" +#include "bitmaps/mask_key_spacebar.lbm" }; const uint8_t * const LBM_SPECIAL_KEYS[] = { diff --git a/tools/encode_bitmap.py b/tools/encode_bitmap.py index 067fbaa..c7c7fb3 100755 --- a/tools/encode_bitmap.py +++ b/tools/encode_bitmap.py @@ -6,7 +6,7 @@ class RawMixin: def encode_byte(self, byte): - self.append(byte) + self.append_byte(byte) def encode_end(self): pass @@ -28,7 +28,7 @@ def eq_prev_byte(self, byte): def encode_byte(self, byte): if self.state == self.RLE_BYTE: - self.append(byte) + self.append_byte(byte) if self.eq_prev_byte(byte): self.state = self.RLE_SEQ self.count = 0 @@ -38,18 +38,18 @@ def encode_byte(self, byte): if self.eq_prev_byte(byte): self.count += 1 if self.count == 255: - self.append(self.count) + self.append_byte(self.count) self.prev_byte = None self.state = self.RLE_BYTE else: - self.append(self.count) - self.append(byte) + self.append_byte(self.count) + self.append_byte(byte) self.prev_byte = byte self.state = self.RLE_BYTE def encode_end(self): if self.state == self.RLE_SEQ: - self.append(self.count) + self.append_byte(self.count) class ImageEncoder: @@ -58,15 +58,27 @@ def __init__(self, size_format, orientation=0): self.orientation = orientation self.bytes = [] - def append(self, value): + def append_byte(self, value): self.bytes.append(value) def extend(self, values): self.bytes.extend(values) + def append_short(self, value): + self.append_byte(value % 256) + self.append_byte(value // 256) + + def append_word(self, value): + self.append_short(value % 65536) + self.append_short(value // 65536) + + def append_format(self, value): + self.append_word(value) + def append_size(self, width, height): if self.size_format == 2: - self.extend([width % 256, width // 256, height % 256, height // 256]) + self.append_short(width) + self.append_short(height) else: self.extend(width, height) @@ -123,7 +135,7 @@ def encode_4bits(self, image): def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size - self.append(0) + self.append_format(0) self.append_size(width, height) if self.orientation == 270: for x in range(width): @@ -140,7 +152,7 @@ def encode_8bits(self, image): def encode_5_6_5(self, image): width, height = image.size - self.append(0) + self.append_format(0) self.append_size(width, height) for y in range(height): for x in range(width): @@ -154,7 +166,7 @@ def encode_5_6_5(self, image): def encode_4_4_4_4(self, image): width, height = image.size - self.append(1) + self.append_format(1) self.append_size(width, height) for y in range(height): for x in range(width): From 6fc176db8b4760e77836a8b370ce6b24344c8586 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 10 May 2025 09:57:22 +0200 Subject: [PATCH 16/54] Refactoring continued --- src/bitmapbuffer.cpp | 155 +++++---- src/bitmapbuffer.h | 763 ++++++++++++++++++++--------------------- src/bitmapdata.h | 72 ---- src/font.cpp | 20 +- src/font.h | 22 +- src/keyboard_text.cpp | 47 +-- src/keyboard_text.h | 2 - src/menu.cpp | 2 +- src/menu.h | 8 +- src/static.h | 62 ++-- src/textedit.h | 1 + src/theme.h | 2 +- tools/encode_bitmap.py | 1 - 13 files changed, 530 insertions(+), 627 deletions(-) delete mode 100644 src/bitmapdata.h diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 3abedaa..75af818 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -26,31 +26,9 @@ #include "file_reader.h" #include "intconversions.h" -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, nullptr), - dataAllocated(true) +void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) { - data = (uint16_t *) malloc(align32(width * height * sizeof(uint16_t))); - dataEnd = data + (width * height); -} - -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data): - BitmapBufferBase(format, width, height, data), - dataAllocated(false) -{ -} - -BitmapBuffer::~BitmapBuffer() -{ - if (dataAllocated) { - free(data); - } -} - -template -void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) -{ - if (!data || !bmp) + if (!data || !bitmap) return; APPLY_OFFSET(); @@ -58,8 +36,8 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (x >= xmax || y >= ymax) return; - coord_t bmpw = bmp->width(); - coord_t bmph = bmp->height(); + coord_t bmpw = bitmap->width(); + coord_t bmph = bitmap->height(); if (srcw == 0) srcw = bmpw; @@ -92,10 +70,10 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, return; } - if (bmp->getFormat() == BMP_ARGB4444 || format == BMP_ARGB4444) - DMACopyAlphaBitmap(data, format == BMP_ARGB4444, _width, _height, x, y, bmp->getData(), bmp->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); + if (bitmap->getFormat() == BMP_ARGB4444 || _format == BMP_ARGB4444) + DMACopyAlphaBitmap(data, _format == BMP_ARGB4444, _width, _height, x, y, bitmap->getData(), bitmap->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); else - DMACopyBitmap(data, _width, _height, x, y, bmp->getData(), bmpw, bmph, srcx, srcy, srcw, srch); + DMACopyBitmap(data, _width, _height, x, y, bitmap->getData(), bmpw, bmph, srcx, srcy, srcw, srch); } else { if (x < xmin) { @@ -127,14 +105,14 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (y + scaledh > _height) scaledh = _height - y; - if (format == BMP_ARGB4444) { + if (getFormat() == BMP_ARGB4444) { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_RGB565) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_RGB565) { RGB_SPLIT(*q, r, g, b); drawPixel(p, ARGB_JOIN(0xF, r>>1, g>>2, b>>1)); } @@ -148,11 +126,11 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, else { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_ARGB4444) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_ARGB4444) { ARGB_SPLIT(*q, a, r, g, b); drawAlphaPixel(p, a, RGB_JOIN(r<<1, g<<2, b<<1)); } @@ -166,12 +144,7 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, } } -template void BitmapBuffer::drawBitmap(coord_t, coord_t, BitmapBufferBase const *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const RLEBitmap *, coord_t, coord_t, coord_t, coord_t, float); - -template -void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) +void BitmapBuffer::drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) { if (bitmap) { auto scale = bitmap->getScale(w, h); @@ -181,11 +154,9 @@ void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coor } } -template void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t); - void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) { - if (format == BMP_RGB565) { + if (_format == BMP_RGB565) { if (alpha == ALPHA_MAX) { drawPixel(p, color); } @@ -199,7 +170,7 @@ void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) drawPixel(p, RGB_JOIN(r, g, b)); } } - else if (format == BMP_ARGB4444) { + else if (_format == BMP_ARGB4444) { if (alpha == ALPHA_MAX) { drawPixel(p, RGB565_TO_ARGB4444(color, 0xFF)); } @@ -536,13 +507,13 @@ void BitmapBuffer::fillRectangle(coord_t x, coord_t y, coord_t w, coord_t h, pix void BitmapBuffer::drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color) { - if (format == BMP_RGB565) + if (_format == BMP_RGB565) fillRectangle(x, y, w, h, color); else fillRectangle(x, y, w, h, RGB565_TO_ARGB4444(color, 0xFF)); } -void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const StaticMask * mask, Color565 color) +void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color) { coord_t maskHeight = mask->height(); while (h > 0) { @@ -728,7 +699,7 @@ class Slope int value; }; -void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * img, LcdColor color, int startAngle, int endAngle) +void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const Mask * mask, LcdColor color, int startAngle, int endAngle) { if (endAngle == startAngle) { endAngle += 1; @@ -739,10 +710,9 @@ void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * im auto rgb565 = COLOR_TO_RGB565(color); - auto bitmap = (BitmapData *)img; - coord_t width = bitmap->width(); - coord_t height = bitmap->height(); - const uint8_t * q = bitmap->getData(); + coord_t width = mask->width(); + coord_t height = mask->height(); + const uint8_t * q = mask->getData(); int w2 = width / 2; int h2 = height / 2; @@ -799,8 +769,7 @@ void BitmapBuffer::drawAnnulusSector(coord_t x, coord_t y, coord_t internalRadiu } } -template -void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) { if (!mask) return; @@ -848,14 +817,10 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color } auto rgb565 = COLOR_TO_RGB565(color); - DMACopyAlphaMask(data, format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); + DMACopyAlphaMask(data, _format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); } -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapData *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapMask *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const StaticMask *, Color565, coord_t, coord_t, coord_t, coord_t); - -void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) { if (!mask || !srcBitmap) return; @@ -899,6 +864,11 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const } } +void BitmapBuffer::drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color) +{ + drawMask(x, y, glyph.data, color, glyph.offset, 0, glyph.width); +} + #define INCREMENT_POS(delta) do { if (flags & VERTICAL) y -= delta; else x += delta; } while(0) coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags) @@ -1108,20 +1078,20 @@ coord_t BitmapBuffer::drawNumber(coord_t x, coord_t y, int32_t val, LcdColor col //} // -BitmapBuffer * BitmapBuffer::load(const char * filename, int maxSize) +Bitmap * Bitmap::load(const char * path, int maxSize) { - auto ext = getFileExtension(filename); + auto ext = getFileExtension(path); if (ext && !strcmp(ext, ".bmp")) - return load_bmp(filename, maxSize); + return load_bmp(path, maxSize); else - return load_stb(filename, maxSize); + return load_stb(path, maxSize); } -BitmapMask * BitmapMask::load(const char * filename, int maxSize) +Mask * Mask::load(const char * path, int maxSize) { - BitmapBuffer * bitmap = BitmapBuffer::load(filename, maxSize); + auto bitmap = Bitmap::load(path, maxSize); if (bitmap) { - BitmapMask * result = BitmapMask::allocate(BMP_RGB565, bitmap->width(), bitmap->height()); + Mask * result = Mask::allocate(bitmap->width(), bitmap->height()); if (result) { auto * q = result->getData(); for (const auto * p = bitmap->getData(); p < bitmap->getDataEnd(); p++) { @@ -1134,10 +1104,49 @@ BitmapMask * BitmapMask::load(const char * filename, int maxSize) return nullptr; } +Mask * Mask::decodeRle(const uint8_t * data) +{ + auto width = *((uint16_t *)data); + auto height = *((uint16_t *)(data + 2)); + + auto result = Mask::allocate(width, height); + + uint8_t prevByte = 0; + bool prevByteValid = false; + + auto d = result->getData(); + auto destDataEnd = result->getDataEnd(); + + while (d < destDataEnd) { + uint8_t byte = *data++; + *d++ = byte; + + if (prevByteValid && byte == prevByte) { + uint8_t count = *data++; + + if (d + count > destDataEnd) { + TRACE("RLE decode error: destination overflow!"); + delete result; + return nullptr; + } + + memset(d, byte, count); + d += count; + prevByteValid = false; + } + else { + prevByte = byte; + prevByteValid = true; + } + } + + return result; +} + BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize) { BitmapBuffer * result = nullptr; - const auto * mask = BitmapMask::load(filename, maxSize); + const auto * mask = Mask::load(filename, maxSize); if (mask) { result = BitmapBuffer::allocate(BMP_RGB565, mask->width(), mask->height()); if (result) { @@ -1149,7 +1158,7 @@ BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color56 return result; } -BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) +Bitmap * Bitmap::load_bmp(const char * filename, int maxSize) { uint8_t palette[16]; @@ -1232,7 +1241,7 @@ BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) return nullptr; } - auto bmp = BitmapBuffer::allocate(BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) failed: malloc error", filename); return nullptr; @@ -1374,7 +1383,7 @@ void * stb_realloc(void *ptr, unsigned int oldsz, unsigned int newsz) #define STB_IMAGE_IMPLEMENTATION #include "thirdparty/stb/stb_image.h" -BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) +Bitmap * Bitmap::load_stb(const char * filename, int maxSize) { int w, h, n; unsigned char * img; @@ -1412,7 +1421,7 @@ BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) } // convert to RGB565 or ARGB4444 format - auto bmp = BitmapBuffer::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) malloc failed", filename); stbi_image_free(img); diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index c186c6d..00d01a0 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -22,15 +22,14 @@ #include #include #include -#include -#include "bitmapdata.h" #include "libopenui_types.h" #include "libopenui_defines.h" #include "libopenui_depends.h" #include "libopenui_helpers.h" -#include "font.h" #include "debug.h" +class FontGlyph; + constexpr uint8_t SOLID = 0xFF; constexpr uint8_t DOTTED = 0x55; constexpr uint8_t DASHED = 0x33; @@ -50,132 +49,108 @@ enum BitmapFormat }; template -class BitmapBufferBase +class Raster { public: - BitmapBufferBase(uint8_t format, uint16_t width, uint16_t height, T * data): - format(format), + using DataType = T; + + Raster(uint16_t width, uint16_t height): _width(width), _height(height), - xmax(width), - ymax(height), - data(data), - dataEnd(data + (width * height)) - { - } - - BitmapBufferBase(const uint8_t * data): - BitmapBufferBase(*((uint32_t *)data), *((uint16_t *)(data + 4)), *((uint16_t *)(data + 6)), (T *)(data + 8)) - { - } - - [[nodiscard]] inline bool isValid() const - { - return data != nullptr; - } - - inline void clearClippingRect() - { - xmin = 0; - xmax = _width; - ymin = 0; - ymax = _height; - } - - inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) - { - this->xmin = max(0, xmin); - this->xmax = min(_width, xmax); - this->ymin = max(0, ymin); - this->ymax = min(_height, ymax); - } - - inline void setClippingRect(const rect_t & rect) + data((T *)malloc(align32(width * height * sizeof(T)))), + dataAllocated(true) { - setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); } - inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const - { - xmin = this->xmin; - xmax = this->xmax; - ymin = this->ymin; - ymax = this->ymax; - } - - inline rect_t getClippingRect() const - { - return {xmin, ymin, xmax - xmin, ymax - ymin }; - } - - inline void setOffsetX(coord_t offsetX) - { - this->offsetX = offsetX; - } - - inline void setOffsetY(coord_t offsetY) + Raster(uint16_t width, uint16_t height, T * data): + _width(width), + _height(height), + data(data), + dataAllocated(false) { - this->offsetY = offsetY; } - inline void setOffset(coord_t offsetX, coord_t offsetY) + ~Raster() { - setOffsetX(offsetX); - setOffsetY(offsetY); - } - - inline void clearOffset() - { - setOffset(0, 0); + if (dataAllocated) { + free(data); + } } - inline void reset() + [[nodiscard]] inline uint16_t width() const { - clearOffset(); - clearClippingRect(); + return _width; } - [[nodiscard]] coord_t getOffsetX() const + [[nodiscard]] inline uint16_t height() const { - return offsetX; + return _height; } - [[nodiscard]] coord_t getOffsetY() const + [[nodiscard]] float getScale(coord_t w, coord_t h) const { - return offsetY; + auto vscale = float(h) / height(); + auto hscale = float(w) / width(); + return vscale < hscale ? vscale : hscale; } - [[nodiscard]] inline uint8_t getFormat() const + [[nodiscard]] inline T * getData() const { - return format; + return data; } - [[nodiscard]] inline uint16_t width() const + [[nodiscard]] inline bool isValid() const { - return _width; + return data != nullptr; } - [[nodiscard]] inline uint16_t height() const + [[nodiscard]] uint32_t getDataSize() const { - return _height; + return _width * _height * sizeof(T); } - [[nodiscard]] inline T * getData() const + [[nodiscard]] inline T * getDataEnd() const { - return data; + return data + (_width * _height); } - [[nodiscard]] inline T * getDataEnd() const + [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const { - return dataEnd; +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - [[nodiscard]] uint32_t getDataSize() const + inline T * getPixelPtrAbs(coord_t x, coord_t y) { - return _width * _height * sizeof(T); +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - inline T * getNextPixel(T * pixel, coord_t count = 1) + inline const T * getNextPixel(const T * pixel, coord_t count = 1) const { #if LCD_ORIENTATION == 180 return pixel - count; @@ -195,7 +170,7 @@ class BitmapBufferBase #endif } - inline const T * getNextPixel(const T * pixel, coord_t count = 1) const + inline T * getNextPixel(T * pixel, coord_t count = 1) { #if LCD_ORIENTATION == 180 return pixel - count; @@ -215,320 +190,360 @@ class BitmapBufferBase #endif } - inline T * getPixelPtrAbs(coord_t x, coord_t y) - { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; - } + protected: + uint16_t _width; + uint16_t _height; + T * data; + bool dataAllocated = false; +}; - [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const +class Mask: public Raster +{ + protected: + using Raster::Raster; + + public: + Mask(const uint8_t * data): + Raster(*((uint16_t *)(data + 0)), *((uint16_t *)(data + 2)), (uint8_t *)(data + 4)) { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; } - template - C * invert() const + static Mask * allocate(uint16_t width, uint16_t height) { - auto result = C::allocate(format, width(), height()); - if (result) { - auto * srcData = data; - auto * destData = result->getData(); - for (auto y = 0; y < height(); y++) { - for (auto x = 0; x < width(); x++) { - destData[x] = 0xFF - srcData[x]; - } - srcData += width(); - destData += width(); - } + auto result = new Mask(width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; } return result; } - template - C * horizontalFlip() const + static Mask * allocate(const Mask * from, uint16_t width, uint16_t height) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return allocate(width, height); + } -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[(width() - 1 - x) * height() + y]; - *destData = *srcData; - } - } -#else - auto * srcData = data + width(); - auto * destData = result->getData(); - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(--srcData); - } - srcData += 2 * width(); - } -#endif - return result; + static Mask * load(const char * path, int maxSize = -1); + + static Mask * decodeRle(const uint8_t * data); +}; + +class Bitmap: public Raster +{ + public: + Bitmap(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Raster(width, height, data), + _format(format) + { } - template - C * verticalFlip() const + Bitmap(const uint8_t * data): + Bitmap(*((uint32_t *)data), *((uint16_t *)(data + 4)), *((uint16_t *)(data + 6)), (pixel_t *)(data + 8)) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } - -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[x * height() + height() - 1 - y]; - *destData = *srcData; - } - } -#else - auto * srcData = getDataEnd() - width(); - auto * destData = result->getData(); - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(srcData++); - } - srcData -= 2 * width(); + } + + static Bitmap * allocate(uint8_t format, uint16_t width, uint16_t height) + { + auto result = new Bitmap(format, width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; } -#endif return result; } - template - C * rotate(float radians) const + static Bitmap * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - float sine = sinf(radians); - float cosine = cosf(radians); + return allocate(from->getFormat(), width, height); + } - auto w = width(); - auto h = height(); + static Bitmap * load(const char * path, int maxSize = -1); - auto x0 = w / 2; - auto y0 = h / 2; + [[nodiscard]] uint8_t getFormat() const + { + return _format; + } - auto * result = C::allocate(format, w, h); - if (!result) { - return nullptr; - } + void setFormat(uint8_t format) + { + _format = format; + } - auto * srcData = data; - auto * destData = result->data; - - for (unsigned rX = 0; rX < w; rX++) { - coord_t dX = rX - x0; - for (unsigned rY = 0; rY < h; rY++) { - coord_t dY = rY - y0; - coord_t x = round(dX * cosine + dY * sine + x0); - if (x >= 0 && x < w) { - coord_t y = round(dY * cosine - dX * sine + y0); - if (y >= 0 && y < h) { - destData[rY * w + rX] = srcData[y * w + x]; - } - else { - destData[rY * w + rX] = 0; - } - } - else { - destData[rY * w + rX] = 0; - } - } - } + protected: + uint8_t _format; - return result; + Bitmap(uint8_t format, uint16_t width, uint16_t height): + Raster(width, height), + _format(format) + { } - template - C * rotate90() const - { - auto * result = C::allocate(format, height(), width()); - if (!result) { - return nullptr; + static Bitmap * load_bmp(const char * filename, int maxSize = -1); + static Bitmap * load_stb(const char * filename, int maxSize = -1); +}; + +template +C * invert(const C * source) +{ + auto result = C::allocate(source, source->width(), source->height()); + if (result) { + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (auto y = 0; y < source->height(); y++) { + for (auto x = 0; x < source->width(); x++) { + destData[x] = 0xFF - srcData[x]; } + srcData += source->width(); + destData += source->width(); + } + } + return result; +} - #if LCD_ORIENTATION == 270 - auto * srcData = data; - auto * destData = result->data; - for (uint8_t y = 0; y < width(); y++) { - for (uint8_t x = 0; x < height(); x++) { - destData[x * width() + y] = srcData[y * height() + x]; +template +C * verticalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } + +#if LCD_ORIENTATION == 270 + for (uint8_t y = 0; y < source->height(); y++) { + for (uint8_t x = 0; x < source->width(); x++) { + auto * destData = &result->data[x * height() + y]; + auto * srcData = &source->getData()[x * height() + height() - 1 - y]; + *destData = *srcData; + } + } +#else + auto * srcData = source->getData() + source->width() * (source->height() - 1); + auto * destData = result->getData(); + for (uint8_t y = 0; y < source->height(); y++) { + for (uint8_t x = 0; x < source->width(); x++) { + *(destData++) = *(srcData++); + } + srcData -= 2 * source->width(); + } +#endif + return result; +} + +template +C * rotate(const C * source, float radians) +{ + float sine = sinf(radians); + float cosine = cosf(radians); + + auto w = source->width(); + auto h = source->height(); + + auto x0 = w / 2; + auto y0 = h / 2; + + auto * result = C::allocate(source, w, h); + if (!result) { + return nullptr; + } + + auto * srcData = source->getData(); + auto * destData = result->getData(); + + for (unsigned rX = 0; rX < w; rX++) { + coord_t dX = rX - x0; + for (unsigned rY = 0; rY < h; rY++) { + coord_t dY = rY - y0; + coord_t x = round(dX * cosine + dY * sine + x0); + if (x >= 0 && x < w) { + coord_t y = round(dY * cosine - dX * sine + y0); + if (y >= 0 && y < h) { + destData[rY * w + rX] = srcData[y * w + x]; } - } - #else - auto * destData = result->getData() + height(); - for (uint8_t y = 0; y < width(); y++) { - auto * srcData = data + y; - for (uint8_t x = 0; x < height(); x++) { - destData--; - *destData = *srcData; - srcData += width(); + else { + destData[rY * w + rX] = 0; } - destData += 2 * height(); } - #endif - return result; + else { + destData[rY * w + rX] = 0; + } } + } - template - C * rotate180() const - { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return result; +} - auto * srcData = data; - auto * destData = result->dataEnd; - while (destData > result->data) { - *(--destData) = *srcData++; - } +template +static C * horizontalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } - return result; +#if LCD_ORIENTATION == 270 + for (uint8_t y = 0; y < _height; y++) { + for (uint8_t x = 0; x < _width; x++) { + auto * destData = &result->data[x * _height + y]; + auto * srcData = &data[(_width - 1 - x) * _height + y]; + *destData = *srcData; } + } +#else + auto * srcData = source->getData() + source->width(); + auto * destData = result->getData(); + for (uint8_t y = 0; y < source->height(); y++) { + for (uint8_t x = 0; x < source->width(); x++) { + *(destData++) = *(--srcData); + } + srcData += 2 * source->width(); + } +#endif + return result; +} - protected: - uint8_t format; - coord_t _width; - coord_t _height; - coord_t xmin = 0; - coord_t xmax; - coord_t ymin = 0; - coord_t ymax; - coord_t offsetX = 0; - coord_t offsetY = 0; - T * data; - T * dataEnd; -}; +template +C * rotate90(const C * source) +{ + auto * result = C::allocate(source, source->height(), source->width()); + if (!result) { + return nullptr; + } -typedef BitmapBufferBase Bitmap; -typedef BitmapBufferBase StaticMask; +#if LCD_ORIENTATION == 270 + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (uint8_t y = 0; y < source->width(); y++) { + for (uint8_t x = 0; x < source->height(); x++) { + destData[x * width() + y] = srcData[y * height() + x]; + } + } +#else + auto * destData = result->getData() + source->height(); + for (uint8_t y = 0; y < source->width(); y++) { + auto * srcData = source->getData() + y; + for (uint8_t x = 0; x < source->height(); x++) { + destData--; + *destData = *srcData; + srcData += source->width(); + } + destData += 2 * source->height(); + } +#endif + return result; +} -class RLEBitmap: public BitmapBufferBase +template +C * rotate180(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } + + auto * srcData = source->getData(); + auto * destData = result->getDataEnd(); + while (destData > result->getData()) { + *(--destData) = *srcData++; + } + + return result; +} + +class BitmapBuffer: public Bitmap { public: - RLEBitmap(uint8_t format, const uint8_t * rleData) : - BitmapBufferBase(format, 0, 0, nullptr) + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Bitmap(format, width, height, data), + dataEnd(data + (width * height)), + xmax(width), + ymax(height) { - _width = *((uint16_t *)rleData); - _height = *(((uint16_t *)rleData) + 1); - uint32_t pixels = _width * _height; - data = (uint16_t*)malloc(align32(pixels * sizeof(uint16_t))); - decode((uint8_t *)data, pixels * sizeof(uint16_t), rleData + 4); - dataEnd = data + pixels; } - ~RLEBitmap() + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): + Bitmap(format, width, height), + dataEnd(Bitmap::data + (width * height)), + xmax(width), + ymax(height) { - free(data); } - - static int decode(uint8_t * dest, unsigned int destSize, const uint8_t * src) + + inline void clearClippingRect() { - uint8_t prevByte = 0; - bool prevByteValid = false; - - const uint8_t * destEnd = dest + destSize; - uint8_t * d = dest; - - while (d < destEnd) { - uint8_t byte = *src++; - *d++ = byte; - - if (prevByteValid && byte == prevByte) { - uint8_t count = *src++; - - if (d + count > destEnd) { - TRACE("rle_decode_8bit: destination overflow!\n"); - return -1; - } - - memset(d, byte, count); - d += count; - prevByteValid = false; - } - else { - prevByte = byte; - prevByteValid = true; - } - } - - return d - dest; + xmin = 0; + xmax = this->_width; + ymin = 0; + ymax = this->_height; + } + + inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) + { + this->xmin = max(0, xmin); + this->xmax = min(this->_width, xmax); + this->ymin = max(0, ymin); + this->ymax = min(this->_height, ymax); + } + + inline void setClippingRect(const rect_t & rect) + { + setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); + } + + inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const + { + xmin = this->xmin; + xmax = this->xmax; + ymin = this->ymin; + ymax = this->ymax; + } + + inline rect_t getClippingRect() const + { + return {xmin, ymin, xmax - xmin, ymax - ymin }; } -}; -class BitmapMask: public BitmapBufferBase -{ - public: - mutable std::optional> _constView; + inline void setOffsetX(coord_t offsetX) + { + this->offsetX = offsetX; + } + + inline void setOffsetY(coord_t offsetY) + { + this->offsetY = offsetY; + } - const BitmapBufferBase * asConstView() const + inline void setOffset(coord_t offsetX, coord_t offsetY) { - if (!_constView.has_value()) { - _constView.emplace(format, width(), height(), data); - } - return &*_constView; + setOffsetX(offsetX); + setOffsetY(offsetY); } - static BitmapMask * allocate(uint8_t format, uint16_t width, uint16_t height) + inline void clearOffset() { - auto result = new BitmapMask(format, width, height); - if (result && !result->isValid()) { - delete result; - result = nullptr; - } - return result; + setOffset(0, 0); } - protected: - BitmapMask(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, (uint8_t *)malloc(align32(width * height))) + inline void reset() { + clearOffset(); + clearClippingRect(); } - public: - ~BitmapMask() + [[nodiscard]] coord_t getOffsetX() const { - free(data); + return offsetX; } - static BitmapMask * load(const char * filename, int maxSize = -1); -}; + [[nodiscard]] coord_t getOffsetY() const + { + return offsetY; + } + + [[nodiscard]] inline DataType * getDataEnd() const + { + return dataEnd; + } -class BitmapBuffer: public BitmapBufferBase -{ - public: static BitmapBuffer * allocate(uint8_t format, uint16_t width, uint16_t height) { auto result = new BitmapBuffer(format, width, height); @@ -539,34 +554,9 @@ class BitmapBuffer: public BitmapBufferBase return result; } - mutable std::optional> _constView; - - const BitmapBufferBase * asConstView() const + static BitmapBuffer * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - if (!_constView.has_value()) { - _constView.emplace(format, width(), height(), data); - } - return &*_constView; - } - - protected: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height); - - public: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data); - - ~BitmapBuffer(); - - [[nodiscard]] float getScale(coord_t w, coord_t h) const - { - auto vscale = float(h) / height(); - auto hscale = float(w) / width(); - return vscale < hscale ? vscale : hscale; - } - - inline void setFormat(uint8_t format) - { - this->format = format; + return allocate(from->getFormat(), width, height); } inline void clear(Color565 color = 0 /*black*/) @@ -581,7 +571,7 @@ class BitmapBuffer: public BitmapBufferBase if (!applyPixelClippingRect(x, y)) return nullptr; - return BitmapBufferBase::getPixelPtrAbs(x, y); + return getPixelPtrAbs(x, y); } inline void drawPixel(coord_t x, coord_t y, pixel_t value) @@ -639,7 +629,7 @@ class BitmapBuffer: public BitmapBufferBase void drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color); - void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const StaticMask * mask, Color565 color); + void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color); void drawCircle(coord_t x, coord_t y, coord_t radius, LcdColor color); @@ -651,16 +641,12 @@ class BitmapBuffer: public BitmapBufferBase void drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); - void drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdColor color, int startAngle, int endAngle); - - static BitmapBuffer * load(const char * filename, int maxSize = -1); + void drawBitmapPatternPie(coord_t x0, coord_t y0, const Mask * mask, LcdColor color, int startAngle, int endAngle); static BitmapBuffer * loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize = -1); - template - void drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); - - void drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); coord_t drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags = 0); coord_t drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags = 0); @@ -683,20 +669,23 @@ class BitmapBuffer: public BitmapBufferBase coord_t drawNumber(coord_t x, coord_t y, int32_t val, LcdColor color, LcdFlags flags = 0, uint8_t len = 0, const char * prefix = nullptr, const char * suffix = nullptr); - template - void drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); - void copyFrom(const BitmapBuffer * other) { DMACopyBitmap(getData(), _width, _height, 0, 0, other->getData(), _width, _height, 0, 0, _width, _height); } - template - void drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); + void drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); + + void drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); protected: - static BitmapBuffer * load_bmp(const char * filename, int maxSize = -1); - static BitmapBuffer * load_stb(const char * filename, int maxSize = -1); + DataType * dataEnd; + coord_t xmin = 0; + coord_t xmax; + coord_t ymin = 0; + coord_t ymax; + coord_t offsetX = 0; + coord_t offsetY = 0; inline bool applyClippingRect(coord_t & x, coord_t & y, coord_t & w, coord_t & h) const { @@ -738,10 +727,7 @@ class BitmapBuffer: public BitmapBufferBase return applyClippingRect(x, y, w, h); } - void drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color) - { - drawMask(x, y, glyph.data, color, glyph.offset, 0, glyph.width); - } + void drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color); inline void drawPixel(pixel_t * p, pixel_t value) { @@ -785,7 +771,6 @@ class BitmapBuffer: public BitmapBufferBase void fillTopFlatTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t x2, coord_t y2, LcdColor color); private: - bool dataAllocated; #if defined(DEBUG) bool leakReported = false; #endif diff --git a/src/bitmapdata.h b/src/bitmapdata.h deleted file mode 100644 index 43f4114..0000000 --- a/src/bitmapdata.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) OpenTX - * - * Source: - * https://github.com/opentx/libopenui - * - * This file is a part of libopenui library. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ - -#pragma once - -#include - -class BitmapData -{ - public: - uint16_t width() const - { - return _width; - } - - uint16_t height() const - { - return _height; - } - - const uint8_t * getData() const - { - return data; - } - - // TODO duplicated code - void flip() - { - auto ptr1 = &data[0]; - auto ptr2 = &data[height() * width() - 1]; - while (ptr2 > ptr1) { - std::swap(*ptr1++, *ptr2--); - } - } - - // TODO duplicated code - void rotate() - { - auto dataSize = width() * height(); - auto * srcData = (uint8_t *)malloc(dataSize); - memcpy(srcData, data, dataSize); - auto * destData = &data[0]; - for (uint16_t y = 0; y < height(); y++) { - for (uint16_t x = 0; x < width(); x++) { - destData[x * height() + y] = srcData[y * width() + x]; - } - } - free(srcData); - } - - protected: - uint32_t format; // alignment - uint16_t _width; - uint16_t _height; - uint8_t data[]; -}; diff --git a/src/font.cpp b/src/font.cpp index 7b82453..a16bd9d 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -22,8 +22,8 @@ #include "bitmapbuffer.h" #include "font.h" #include "debug.h" - -BitmapData * decodeBitmapData(const uint8_t * data) +/* +Mask * decodeBitmapData(const uint8_t * data) { auto bitmap = (const BitmapData *)data; size_t fontSize = bitmap->width() * bitmap->height(); @@ -34,7 +34,7 @@ BitmapData * decodeBitmapData(const uint8_t * data) RLEBitmap::decode(buffer + 4, fontSize, bitmap->getData()); return (BitmapData *)buffer; -} +}*/ /* Font format * 'F', 'N', 'T', '1' @@ -126,13 +126,19 @@ bool Font::loadFile(const char * path) return false; } - auto bitmapData = decodeBitmapData(data); #if LCD_ORIENTATION == 180 - bitmapData.flip(); + auto source = Mask::decodeRle(data); + auto mask = Mask::flip(source); + delete source; #elif LCD_ORIENTATION == 270 - bitmapData.rotate(); + auto source = Mask::decodeRle(data); + auto mask = Mask::rotate(source); + delete source; +#else + auto mask = Mask::decodeRle(data); #endif - ranges.push_back({rangeHeader.begin, rangeHeader.end, bitmapData, specs}); + ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); + free(data); } // TRACE("Ranges..."); diff --git a/src/font.h b/src/font.h index 044f9b3..3a6d9fb 100644 --- a/src/font.h +++ b/src/font.h @@ -22,7 +22,7 @@ #include #include "libopenui_types.h" #include "libopenui_config.h" -#include "bitmapdata.h" +#include "bitmapbuffer.h" #include "unicode.h" constexpr uint8_t LEN_FONT_NAME = 8; @@ -39,25 +39,25 @@ inline const char * findNextLine(const char * str) } } -class Font +class FontGlyph { public: + const Mask * data; + unsigned offset; + uint8_t width; +}; +class Font +{ + public: struct GlyphRange { uint32_t begin; uint32_t end; - const BitmapData * data; + const Mask * data; const uint16_t * specs; }; - struct Glyph - { - const BitmapData * data; - unsigned offset; - uint8_t width; - }; - Font() = default; Font(const char * name) @@ -95,7 +95,7 @@ class Font return name; } - Glyph getGlyph(wchar_t c) const + FontGlyph getGlyph(wchar_t c) const { for (auto & range: ranges) { if (range.begin <= uint32_t(c) && uint32_t(c) < range.end) { diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index 4d9f7e4..9cb7b2f 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -24,36 +24,19 @@ using namespace ui; TextKeyboard * TextKeyboard::_instance = nullptr; -const uint8_t LBM_KEY_UPPERCASE[] = { -#include "bitmaps/mask_key_uppercase.lbm" -}; - -const uint8_t LBM_KEY_LOWERCASE[] = { -#include "bitmaps/mask_key_lowercase.lbm" -}; - -const uint8_t LBM_KEY_BACKSPACE[] = { -#include "bitmaps/mask_key_backspace.lbm" -}; - -const uint8_t LBM_KEY_LETTERS[] = { -#include "bitmaps/mask_key_letters.lbm" -}; - -const uint8_t LBM_KEY_NUMBERS[] = { -#include "bitmaps/mask_key_numbers.lbm" -}; - -const uint8_t LBM_KEY_SPACEBAR[] = { -#include "bitmaps/mask_key_spacebar.lbm" -}; - -const uint8_t * const LBM_SPECIAL_KEYS[] = { - LBM_KEY_BACKSPACE, - LBM_KEY_UPPERCASE, - LBM_KEY_LOWERCASE, - LBM_KEY_LETTERS, - LBM_KEY_NUMBERS, +extern const Mask maskKeyUppercase; +extern const Mask maskKeyLowercase; +extern const Mask maskKeyBackspace; +extern const Mask maskKeyLetters; +extern const Mask maskKeyNumbers; +extern const Mask maskKeySpacebar; + +constexpr const Mask * const MASKS_SPECIAL_KEYS[] = { + &maskKeyBackspace, + &maskKeyUppercase, + &maskKeyLowercase, + &maskKeyLetters, + &maskKeyNumbers, }; const char * const KEYBOARD_QWERTY_LOWERCASE[] = { @@ -141,12 +124,12 @@ void TextKeyboard::paint(BitmapBuffer * dc) } else if (uint8_t(*c) <= SPECIAL_KEY_WITH_BITMAP_LAST) { // special keys drawn with a bitmap - dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); + dc->drawMask(x, y, MASKS_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); x += 45; } else if (*c == SPECIAL_KEY_SPACEBAR) { // spacebar - dc->drawMask(x, y, (const BitmapData *)LBM_KEY_SPACEBAR, DEFAULT_COLOR); + dc->drawMask(x, y, &maskKeySpacebar, DEFAULT_COLOR); x += 135; } else if (*c == SPECIAL_KEY_ENTER) { diff --git a/src/keyboard_text.h b/src/keyboard_text.h index 03d527f..967ef37 100644 --- a/src/keyboard_text.h +++ b/src/keyboard_text.h @@ -22,8 +22,6 @@ #include "keyboard_base.h" #include "textedit.h" -extern const uint8_t LBM_KEY_SPACEBAR[]; -extern const uint8_t * const LBM_SPECIAL_KEYS[]; extern const char * const * KEYBOARDS[]; constexpr uint8_t LOWERCASE_OPTION = 1; diff --git a/src/menu.cpp b/src/menu.cpp index 2104d6c..5089c63 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -235,7 +235,7 @@ void Menu::setTitle(std::string text) updatePosition(); } -void Menu::addLine(const std::string & text, const StaticMask * mask, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addLine(const std::string & text, const Mask * mask, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addLine(text, mask, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (content->width() < MAX_MENUS_WIDTH) { diff --git a/src/menu.h b/src/menu.h index 8f4f2a2..7af8796 100644 --- a/src/menu.h +++ b/src/menu.h @@ -42,7 +42,7 @@ class MenuBody: public Window friend class MenuBody; public: - MenuLine(std::string text, const StaticMask * icon, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::string text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked): text(std::move(text)), icon(icon), onPress(std::move(onPress)), @@ -65,7 +65,7 @@ class MenuBody: public Window protected: std::string text; - const StaticMask * icon; + const Mask * icon; std::function drawLine; std::function onPress; std::function onSelect; @@ -106,7 +106,7 @@ class MenuBody: public Window bool onTouchEnd(coord_t x, coord_t y) override; #endif - void addLine(const std::string & text, const StaticMask * icon, std::function onPress, std::function onSelect, std::function isChecked) + void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked) { lines.emplace_back(text, icon, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (icon) @@ -211,7 +211,7 @@ class Menu: public ModalWindow void setTitle(std::string text); - void addLine(const std::string & text, const StaticMask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void addLine(const std::string & text, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); diff --git a/src/static.h b/src/static.h index db90514..6c72641 100644 --- a/src/static.h +++ b/src/static.h @@ -21,6 +21,7 @@ #include "window.h" #include "button.h" // TODO just for BUTTON_BACKGROUND +#include "font.h" constexpr coord_t STATIC_TEXT_INTERLINE_HEIGHT = 2; @@ -132,21 +133,14 @@ class StaticBitmap: public Window { } - StaticBitmap(Window * parent, const rect_t & rect, const char * filename, bool scale = false): - Window(parent, rect), - bitmap(BitmapBuffer::load(filename)), - scale(scale) - { - } - - StaticBitmap(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Bitmap * bitmap, bool scale = false): Window(parent, rect), bitmap(bitmap), scale(scale) { } - StaticBitmap(Window * parent, const rect_t & rect, const StaticMask * mask, LcdFlags color, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Mask * mask, LcdFlags color, bool scale = false): Window(parent, rect), mask(mask), color(color), @@ -154,29 +148,29 @@ class StaticBitmap: public Window { } - void setBitmap(const char * filename) - { - setBitmap(BitmapBuffer::load(filename)); - } - - void setMaskColor(LcdFlags value) - { - color = value; - } - - void setBitmap(const BitmapBuffer * newBitmap) - { - delete bitmap; - bitmap = newBitmap; - invalidate(); - } - - void setMask(const StaticMask * newMask) - { - // delete mask; TODO ??? - mask = newMask; - invalidate(); - } + // void setBitmap(const char * filename) + // { + // setBitmap(BitmapBuffer::load(filename)); + // } + + // void setMaskColor(LcdFlags value) + // { + // color = value; + // } + + // void setBitmap(const BitmapBuffer * newBitmap) + // { + // delete bitmap; + // bitmap = newBitmap; + // invalidate(); + // } + + // void setMask(const Mask * newMask) + // { + // // delete mask; TODO ??? + // mask = newMask; + // invalidate(); + // } #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override @@ -199,8 +193,8 @@ class StaticBitmap: public Window } protected: - const StaticMask * mask = nullptr; - const BitmapBuffer * bitmap = nullptr; + const Mask * mask = nullptr; + const Bitmap * bitmap = nullptr; LcdFlags color = 0; bool scale = false; }; diff --git a/src/textedit.h b/src/textedit.h index a92a6cc..7c905a7 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -19,6 +19,7 @@ #pragma once +#include #include "form.h" namespace ui { diff --git a/src/theme.h b/src/theme.h index 5fe6b6c..d03ab72 100644 --- a/src/theme.h +++ b/src/theme.h @@ -54,7 +54,7 @@ class Theme virtual void drawChoice(BitmapBuffer * dc, ChoiceBase * choice, const char * str) const = 0; virtual void drawSlider(BitmapBuffer * dc, int vmin, int vmax, int value, const rect_t & rect, bool edit, bool focus) const = 0; virtual const BitmapBuffer * getIcon(uint8_t index, IconState state) const = 0; - virtual const BitmapMask * getIconMask(uint8_t index) const = 0; + virtual const Mask * getIconMask(uint8_t index) const = 0; virtual TextButton * createTextButton(FormGroup * parent, const rect_t & rect, std::string text, std::function pressHandler = nullptr, WindowFlags windowFlags = OPAQUE | BUTTON_BACKGROUND) const { diff --git a/tools/encode_bitmap.py b/tools/encode_bitmap.py index c7c7fb3..f701152 100755 --- a/tools/encode_bitmap.py +++ b/tools/encode_bitmap.py @@ -135,7 +135,6 @@ def encode_4bits(self, image): def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size - self.append_format(0) self.append_size(width, height) if self.orientation == 270: for x in range(width): From 0dc38d4221c7167557f46be2eb030ab3490bb79b Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 12 May 2025 10:32:32 +0200 Subject: [PATCH 17/54] Fixes --- src/font.cpp | 13 ------------- src/font.h | 3 ++- src/static.h | 24 ------------------------ 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/src/font.cpp b/src/font.cpp index a16bd9d..29f0517 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -22,19 +22,6 @@ #include "bitmapbuffer.h" #include "font.h" #include "debug.h" -/* -Mask * decodeBitmapData(const uint8_t * data) -{ - auto bitmap = (const BitmapData *)data; - size_t fontSize = bitmap->width() * bitmap->height(); - auto * buffer = (uint8_t *)malloc(fontSize + 4); - - // copy width / height - memcpy(buffer, data, 4); - - RLEBitmap::decode(buffer + 4, fontSize, bitmap->getData()); - return (BitmapData *)buffer; -}*/ /* Font format * 'F', 'N', 'T', '1' diff --git a/src/font.h b/src/font.h index 3a6d9fb..cc67c75 100644 --- a/src/font.h +++ b/src/font.h @@ -65,7 +65,8 @@ class Font strlcpy(this->name, name, sizeof(this->name)); } - Font(const char * name, std::list ranges): + Font(const char * name, uint8_t spacing, std::list ranges): + spacing(spacing), ranges(std::move(ranges)) { strlcpy(this->name, name, sizeof(this->name)); diff --git a/src/static.h b/src/static.h index 6c72641..f594421 100644 --- a/src/static.h +++ b/src/static.h @@ -148,30 +148,6 @@ class StaticBitmap: public Window { } - // void setBitmap(const char * filename) - // { - // setBitmap(BitmapBuffer::load(filename)); - // } - - // void setMaskColor(LcdFlags value) - // { - // color = value; - // } - - // void setBitmap(const BitmapBuffer * newBitmap) - // { - // delete bitmap; - // bitmap = newBitmap; - // invalidate(); - // } - - // void setMask(const Mask * newMask) - // { - // // delete mask; TODO ??? - // mask = newMask; - // invalidate(); - // } - #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override { From 8c4afe182d48d6f44722ae14d75e0af2a3e43417 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 12 May 2025 12:20:15 +0200 Subject: [PATCH 18/54] Fixes --- src/bitmapbuffer.cpp | 2 ++ src/font.cpp | 4 +++- src/font.h | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 75af818..6af09e2 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -1117,6 +1117,8 @@ Mask * Mask::decodeRle(const uint8_t * data) auto d = result->getData(); auto destDataEnd = result->getDataEnd(); + data += 4; + while (d < destDataEnd) { uint8_t byte = *data++; *d++ = byte; diff --git a/src/font.cpp b/src/font.cpp index 29f0517..078110a 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -124,8 +124,10 @@ bool Font::loadFile(const char * path) #else auto mask = Mask::decodeRle(data); #endif - ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); free(data); + if (mask) { + ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); + } } // TRACE("Ranges..."); diff --git a/src/font.h b/src/font.h index cc67c75..897f0dc 100644 --- a/src/font.h +++ b/src/font.h @@ -65,8 +65,9 @@ class Font strlcpy(this->name, name, sizeof(this->name)); } - Font(const char * name, uint8_t spacing, std::list ranges): + Font(const char * name, uint8_t spacing, uint8_t spaceWidth, std::list ranges): spacing(spacing), + spaceWidth(spaceWidth), ranges(std::move(ranges)) { strlcpy(this->name, name, sizeof(this->name)); From ab051f8fe7c4730861033b7f08549254eb02c871 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 12 May 2025 16:32:08 +0200 Subject: [PATCH 19/54] Fixes --- src/bitmapbuffer.h | 39 +++++++++++++++++++++++++-------------- src/font.cpp | 2 +- tools/encode_bitmap.py | 41 +++++++++++++++++++++++++---------------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 00d01a0..b098c9f 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -162,6 +162,7 @@ class Raster } #endif auto result = pixel + (count * lineHeight); + auto dataEnd = getDataEnd(); while (result > dataEnd) result -= lineHeight - 1; return result; @@ -182,6 +183,7 @@ class Raster } #endif auto result = pixel + (count * lineHeight); + auto dataEnd = getDataEnd(); while (result > dataEnd) result -= lineHeight - 1; return result; @@ -309,11 +311,15 @@ C * verticalFlip(const C * source) } #if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < source->height(); y++) { - for (uint8_t x = 0; x < source->width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &source->getData()[x * height() + height() - 1 - y]; - *destData = *srcData; + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (uint8_t y = 0; y < sourceHeight; y++) { + for (uint8_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[x * sourceHeight + sourceHeight - 1 - y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; } } #else @@ -381,21 +387,26 @@ static C * horizontalFlip(const C * source) } #if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < _height; y++) { - for (uint8_t x = 0; x < _width; x++) { - auto * destData = &result->data[x * _height + y]; - auto * srcData = &data[(_width - 1 - x) * _height + y]; - *destData = *srcData; + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (uint8_t y = 0; y < sourceHeight; y++) { + for (uint8_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[(sourceWidth - 1 - x) * sourceHeight + y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; } } #else - auto * srcData = source->getData() + source->width(); + auto sourceWidth = source->width(); + auto * srcData = source->getData() + sourceWidth; auto * destData = result->getData(); for (uint8_t y = 0; y < source->height(); y++) { - for (uint8_t x = 0; x < source->width(); x++) { + for (uint8_t x = 0; x < sourceWidth; x++) { *(destData++) = *(--srcData); } - srcData += 2 * source->width(); + srcData += 2 * sourceWidth; } #endif return result; @@ -414,7 +425,7 @@ C * rotate90(const C * source) auto * destData = result->getData(); for (uint8_t y = 0; y < source->width(); y++) { for (uint8_t x = 0; x < source->height(); x++) { - destData[x * width() + y] = srcData[y * height() + x]; + destData[x * source->width() + y] = srcData[y * source->height() + x]; } } #else diff --git a/src/font.cpp b/src/font.cpp index 078110a..7cf09c0 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -119,7 +119,7 @@ bool Font::loadFile(const char * path) delete source; #elif LCD_ORIENTATION == 270 auto source = Mask::decodeRle(data); - auto mask = Mask::rotate(source); + auto mask = rotate90(source); delete source; #else auto mask = Mask::decodeRle(data); diff --git a/tools/encode_bitmap.py b/tools/encode_bitmap.py index f701152..28d4cce 100755 --- a/tools/encode_bitmap.py +++ b/tools/encode_bitmap.py @@ -2,6 +2,7 @@ import argparse from PIL import Image +import sys class RawMixin: @@ -136,16 +137,16 @@ def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size self.append_size(width, height) - if self.orientation == 270: + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size + for y in range(height): for x in range(width): - for y in range(height): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) - else: - for y in range(height): - for x in range(width): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) + value = 0xFF - image.getpixel((x, y)) + self.encode_byte(value) self.encode_end() return self.bytes @@ -153,10 +154,15 @@ def encode_5_6_5(self, image): width, height = image.size self.append_format(0) self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) - # print(pixel) + pixel = image.getpixel((x, y)) val = ((pixel[0] >> 3) << 11) + ((pixel[1] >> 2) << 5) + ((pixel[2] >> 3) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) @@ -167,9 +173,15 @@ def encode_4_4_4_4(self, image): width, height = image.size self.append_format(1) self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) + pixel = image.getpixel((x, y)) val = ((pixel[3] // 16) << 12) + ((pixel[0] // 16) << 8) + ((pixel[1] // 16) << 4) + ((pixel[2] // 16) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) @@ -177,10 +189,7 @@ def encode_4_4_4_4(self, image): return self.bytes def get_pixel(self, image, x, y): - if self.orientation == 180: - return image.getpixel((image.width - x - 1, image.height - y - 1)) - else: - return image.getpixel((x, y)) + return image.getpixel((x, y)) @staticmethod def create(size_format=1, orientation=0, encode_mixin=RawMixin): From 673b6fdfb4f1f96527a4884b0ff6cfbc77735788 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 12 May 2025 17:04:06 +0200 Subject: [PATCH 20/54] Fixes --- src/bitmapbuffer.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index b098c9f..d1285e9 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -291,8 +291,8 @@ C * invert(const C * source) if (result) { auto * srcData = source->getData(); auto * destData = result->getData(); - for (auto y = 0; y < source->height(); y++) { - for (auto x = 0; x < source->width(); x++) { + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { destData[x] = 0xFF - srcData[x]; } srcData += source->width(); @@ -315,8 +315,8 @@ C * verticalFlip(const C * source) auto sourceWidth = source->width(); auto sourceHeight = source->height(); auto resultData = result->getData(); - for (uint8_t y = 0; y < sourceHeight; y++) { - for (uint8_t x = 0; x < sourceWidth; x++) { + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { auto * p = &sourceData[x * sourceHeight + sourceHeight - 1 - y]; auto * q = &resultData[x * sourceHeight + y]; *q = *p; @@ -325,8 +325,8 @@ C * verticalFlip(const C * source) #else auto * srcData = source->getData() + source->width() * (source->height() - 1); auto * destData = result->getData(); - for (uint8_t y = 0; y < source->height(); y++) { - for (uint8_t x = 0; x < source->width(); x++) { + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { *(destData++) = *(srcData++); } srcData -= 2 * source->width(); @@ -391,8 +391,8 @@ static C * horizontalFlip(const C * source) auto sourceWidth = source->width(); auto sourceHeight = source->height(); auto resultData = result->getData(); - for (uint8_t y = 0; y < sourceHeight; y++) { - for (uint8_t x = 0; x < sourceWidth; x++) { + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { auto * p = &sourceData[(sourceWidth - 1 - x) * sourceHeight + y]; auto * q = &resultData[x * sourceHeight + y]; *q = *p; @@ -402,8 +402,8 @@ static C * horizontalFlip(const C * source) auto sourceWidth = source->width(); auto * srcData = source->getData() + sourceWidth; auto * destData = result->getData(); - for (uint8_t y = 0; y < source->height(); y++) { - for (uint8_t x = 0; x < sourceWidth; x++) { + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < sourceWidth; x++) { *(destData++) = *(--srcData); } srcData += 2 * sourceWidth; @@ -423,16 +423,16 @@ C * rotate90(const C * source) #if LCD_ORIENTATION == 270 auto * srcData = source->getData(); auto * destData = result->getData(); - for (uint8_t y = 0; y < source->width(); y++) { - for (uint8_t x = 0; x < source->height(); x++) { + for (coord_t y = 0; y < source->width(); y++) { + for (coord_t x = 0; x < source->height(); x++) { destData[x * source->width() + y] = srcData[y * source->height() + x]; } } #else auto * destData = result->getData() + source->height(); - for (uint8_t y = 0; y < source->width(); y++) { + for (coord_t y = 0; y < source->width(); y++) { auto * srcData = source->getData() + y; - for (uint8_t x = 0; x < source->height(); x++) { + for (coord_t x = 0; x < source->height(); x++) { destData--; *destData = *srcData; srcData += source->width(); From 921c0c9db55aa09aaef1123bab122651d1b2b3ec Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 12 May 2025 18:06:28 +0200 Subject: [PATCH 21/54] Fixes --- src/font.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/font.cpp b/src/font.cpp index 7cf09c0..2c7822b 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -113,17 +113,7 @@ bool Font::loadFile(const char * path) return false; } -#if LCD_ORIENTATION == 180 - auto source = Mask::decodeRle(data); - auto mask = Mask::flip(source); - delete source; -#elif LCD_ORIENTATION == 270 - auto source = Mask::decodeRle(data); - auto mask = rotate90(source); - delete source; -#else auto mask = Mask::decodeRle(data); -#endif free(data); if (mask) { ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); From b04a06df216291eb6b3cf42d084d1e1b185765af Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 13 May 2025 09:26:40 +0200 Subject: [PATCH 22/54] Fixes --- src/bitmapbuffer.h | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index d1285e9..14c0627 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -58,6 +58,7 @@ class Raster _width(width), _height(height), data((T *)malloc(align32(width * height * sizeof(T)))), + dataEnd(data + (width * height)), dataAllocated(true) { } @@ -66,6 +67,7 @@ class Raster _width(width), _height(height), data(data), + dataEnd(data + (width * height)), dataAllocated(false) { } @@ -99,19 +101,19 @@ class Raster return data; } - [[nodiscard]] inline bool isValid() const + [[nodiscard]] inline T * getDataEnd() const { - return data != nullptr; + return dataEnd; } - [[nodiscard]] uint32_t getDataSize() const + [[nodiscard]] inline bool isValid() const { - return _width * _height * sizeof(T); + return data != nullptr; } - [[nodiscard]] inline T * getDataEnd() const + [[nodiscard]] uint32_t getDataSize() const { - return data + (_width * _height); + return (const uint8_t *)dataEnd - (const uint8_t *)data; } [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const @@ -162,7 +164,6 @@ class Raster } #endif auto result = pixel + (count * lineHeight); - auto dataEnd = getDataEnd(); while (result > dataEnd) result -= lineHeight - 1; return result; @@ -183,7 +184,6 @@ class Raster } #endif auto result = pixel + (count * lineHeight); - auto dataEnd = getDataEnd(); while (result > dataEnd) result -= lineHeight - 1; return result; @@ -196,6 +196,7 @@ class Raster uint16_t _width; uint16_t _height; T * data; + T * dataEnd; bool dataAllocated = false; }; @@ -465,7 +466,6 @@ class BitmapBuffer: public Bitmap public: BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): Bitmap(format, width, height, data), - dataEnd(data + (width * height)), xmax(width), ymax(height) { @@ -473,7 +473,6 @@ class BitmapBuffer: public Bitmap BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): Bitmap(format, width, height), - dataEnd(Bitmap::data + (width * height)), xmax(width), ymax(height) { @@ -690,7 +689,6 @@ class BitmapBuffer: public Bitmap void drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); protected: - DataType * dataEnd; coord_t xmin = 0; coord_t xmax; coord_t ymin = 0; From 328ea3aa5df1f9c1c1b8685b3b1dfb3d45a22e9c Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 14 May 2025 11:34:49 +0200 Subject: [PATCH 23/54] Fixes --- src/filechoice.cpp | 2 +- src/keyboard_text.cpp | 44 ++++++++++++++++---------------------- src/keyboard_text.h | 49 ++++++++++++++++++++++++------------------- src/textedit.cpp | 8 +++---- 4 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/filechoice.cpp b/src/filechoice.cpp index 8b18c13..b55ebe4 100644 --- a/src/filechoice.cpp +++ b/src/filechoice.cpp @@ -117,7 +117,7 @@ bool FileChoice::openMenu() } } - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, &maskIconExclamation); + new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, maskIconExclamation); return false; } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index 9cb7b2f..b2bd5c1 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -22,22 +22,7 @@ using namespace ui; -TextKeyboard * TextKeyboard::_instance = nullptr; - -extern const Mask maskKeyUppercase; -extern const Mask maskKeyLowercase; -extern const Mask maskKeyBackspace; -extern const Mask maskKeyLetters; -extern const Mask maskKeyNumbers; -extern const Mask maskKeySpacebar; - -constexpr const Mask * const MASKS_SPECIAL_KEYS[] = { - &maskKeyBackspace, - &maskKeyUppercase, - &maskKeyLowercase, - &maskKeyLetters, - &maskKeyNumbers, -}; +TextKeyboardBase * TextKeyboardBase::_instance = nullptr; const char * const KEYBOARD_QWERTY_LOWERCASE[] = { "qwertyuiop", @@ -98,15 +83,21 @@ const char * const * KEYBOARDS[] = { KEYBOARD_NUMBERS, }; -TextKeyboard::TextKeyboard(): - Keyboard(TEXT_KEYBOARD_HEIGHT) -{ -} - -TextKeyboard::~TextKeyboard() -{ - _instance = nullptr; -} +#if defined(LIBOPENUI_USE_DEFAULT_TEXT_KEYBOARD) +extern const Mask * maskKeyUppercase; +extern const Mask * maskKeyLowercase; +extern const Mask * maskKeyBackspace; +extern const Mask * maskKeyLetters; +extern const Mask * maskKeyNumbers; +extern const Mask * maskKeySpacebar; + +const Mask * const MASKS_SPECIAL_KEYS[] = { + maskKeyBackspace, + maskKeyUppercase, + maskKeyLowercase, + maskKeyLetters, + maskKeyNumbers, +}; void TextKeyboard::paint(BitmapBuffer * dc) { @@ -129,7 +120,7 @@ void TextKeyboard::paint(BitmapBuffer * dc) } else if (*c == SPECIAL_KEY_SPACEBAR) { // spacebar - dc->drawMask(x, y, &maskKeySpacebar, DEFAULT_COLOR); + dc->drawMask(x, y, maskKeySpacebar, DEFAULT_COLOR); x += 135; } else if (*c == SPECIAL_KEY_ENTER) { @@ -214,3 +205,4 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) return true; } +#endif diff --git a/src/keyboard_text.h b/src/keyboard_text.h index 967ef37..c1cb9cc 100644 --- a/src/keyboard_text.h +++ b/src/keyboard_text.h @@ -46,50 +46,57 @@ enum KeyboardLayout namespace ui { -class TextKeyboard: public Keyboard +class TextKeyboardBase: public Keyboard { public: - TextKeyboard(); + TextKeyboardBase(): + Keyboard(TEXT_KEYBOARD_HEIGHT) + { + } - ~TextKeyboard() override; + ~TextKeyboardBase() override + { + _instance = nullptr; + } -#if defined(DEBUG_WINDOWS) - [[nodiscard]] std::string getName() const override + static void show(FormField * field) { - return "TextKeyboard"; + if (activeKeyboard != _instance) { + _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; + } + _instance->setField(field); } -#endif - static void setInstance(TextKeyboard * keyboard) + static void setInstance(TextKeyboardBase * keyboard) { _instance = keyboard; } - static TextKeyboard * instance() + static TextKeyboardBase * instance() { return _instance; } - static void show(FormField * field) +#if defined(DEBUG_WINDOWS) + [[nodiscard]] std::string getName() const override { - if (!_instance) { - _instance = new TextKeyboard(); - } - if (activeKeyboard != _instance) { - _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; - } - _instance->setField(field); + return "TextKeyboard"; } +#endif + + protected: + static TextKeyboardBase * _instance; + unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; +}; +class TextKeyboard: public TextKeyboardBase +{ + protected: void paint(BitmapBuffer * dc) override; #if defined(HARDWARE_TOUCH) bool onTouchEnd(coord_t x, coord_t y) override; #endif - - protected: - static TextKeyboard * _instance; - unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; }; } diff --git a/src/textedit.cpp b/src/textedit.cpp index 51ad5ab..1488e37 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -48,7 +48,7 @@ void TextEdit::setEditMode(bool newEditMode) #if defined(SOFTWARE_KEYBOARD) if (editMode) { - TextKeyboard::show(this); + TextKeyboardBase::show(this); } #endif @@ -198,7 +198,7 @@ void TextEdit::onEvent(event_t event) changeEnd(); FormField::onEvent(event); #if defined(HARDWARE_TOUCH) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif break; @@ -276,7 +276,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) } #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::show(this); + TextKeyboardBase::show(this); #endif auto font = getFont(FONT(M)); @@ -300,7 +300,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) void TextEdit::onFocusLost() { #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif changeEnd(); From 0a8f1082480c101c285feb9d42b01e93a5ba6333 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 14 May 2025 16:02:40 +0200 Subject: [PATCH 24/54] Fix --- src/bitmapbuffer.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 14c0627..c9f45c4 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -54,6 +54,15 @@ class Raster public: using DataType = T; + Raster(const Raster & other): + _width(other._width), + _height(other._height), + data(other.data), + dataEnd(other.dataEnd), + dataAllocated(false) + { + } + Raster(uint16_t width, uint16_t height): _width(width), _height(height), From 1dd7017f365003a17f4c17a51d3714589b32e8ed Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 14 May 2025 18:53:33 +0200 Subject: [PATCH 25/54] Fix --- src/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/window.cpp b/src/window.cpp index 3965a27..345a61a 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -544,7 +544,7 @@ coord_t Window::adjustHeight() void Window::moveWindowsTop(coord_t y, coord_t delta) // NOLINT(misc-no-recursion) { if (delta != 0) { - if (getWindowFlags() & FORWARD_SCROLL) { + if (parent && (getWindowFlags() & FORWARD_SCROLL)) { parent->moveWindowsTop(bottom(), delta); } From bea3870b4ed635ed513b33a931d08057ea92a0a1 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Fri, 16 May 2025 22:24:53 +0200 Subject: [PATCH 26/54] Fix --- src/font.h | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/font.h b/src/font.h index 897f0dc..6126665 100644 --- a/src/font.h +++ b/src/font.h @@ -20,6 +20,7 @@ #pragma once #include +#include #include "libopenui_types.h" #include "libopenui_config.h" #include "bitmapbuffer.h" @@ -112,6 +113,23 @@ class Font return {}; } + bool hasGlyphs(const char * s) const + { + auto curr = s; + while (true) { + auto c = getNextUnicodeChar(curr); + if (!c) { + return true; + } + if (c != '\n' && c != ' ') { + auto glyph = getGlyph(c); + if (glyph.width == 0) { + return false; + } + } + } + } + uint8_t getGlyphWidth(wchar_t c) const { if (c == ' ') { @@ -203,21 +221,23 @@ class Font return result; } - uint8_t isSubsetLoaded(uint8_t index) const + uint8_t isSubsetLoaded(const char * value) const { - return subsetsMask & (1 << index); + return std::find_if(subsets.begin(), subsets.end(), [value](const char * s) { + return std::strcmp(s, value) == 0; + }) != subsets.end(); } - void setSubsetLoaded(uint8_t index) + void setSubsetLoaded(const char * value) { - subsetsMask |= 1 << index; + subsets.push_back(value); } protected: char name[LEN_FONT_NAME + 1]; - uint8_t subsetsMask = 0; uint8_t spacing = 1; uint8_t spaceWidth = 4; + std::list subsets; std::list ranges; }; From a06f24992c3d9bf59be0fb05be23e4d40b9dce1a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 19 May 2025 08:45:52 +0200 Subject: [PATCH 27/54] Add Choice::setGetValueHandler --- src/choice.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/choice.h b/src/choice.h index 798a227..65552fb 100644 --- a/src/choice.h +++ b/src/choice.h @@ -112,6 +112,11 @@ class Choice: public ChoiceBase bool onTouchEnd(coord_t x, coord_t y) override; #endif + void setGetValueHandler(std::function handler) + { + getValue = std::move(handler); + } + void setSetValueHandler(std::function handler) { setValue = std::move(handler); From 32ef281189df13f03cbfdc3bd6c6fea083ea80c8 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 27 May 2025 10:11:55 +0200 Subject: [PATCH 28/54] [drawLine] Ethos issue #5391 fixed (flickering when clipping) --- src/bitmapbuffer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 6af09e2..8874c70 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -306,9 +306,9 @@ bool BitmapBuffer::clipLine(coord_t & x1, coord_t & y1, coord_t & x2, coord_t & float p4 = -p3; float q1 = x1 - xmin; - float q2 = xmax - 1 - x1; + float q2 = xmax - x1; float q3 = y1 - ymin; - float q4 = ymax - 1 - y1; + float q4 = ymax - y1; float posarr[5], negarr[5]; int posind = 1, negind = 1; From 8f83c632ec4e00c17ad6a31616e798fd78cdef0d Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 4 Jun 2025 11:27:04 +0200 Subject: [PATCH 29/54] Add a parameter "relative" to display a step value in NumberEdit --- src/numberedit.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/numberedit.h b/src/numberedit.h index 0bbe5ce..c857851 100644 --- a/src/numberedit.h +++ b/src/numberedit.h @@ -56,7 +56,7 @@ class NumberEdit: public BaseNumberEdit ContextAll = 0xFF, }; - void setGetStringValueHandler(std::function handler, ValueContext context = ContextAll) + void setGetStringValueHandler(std::function handler, ValueContext context = ContextAll) { _getStringValueContext = context; _getStringValue = std::move(handler); @@ -111,7 +111,7 @@ class NumberEdit: public BaseNumberEdit if (value == 0 && !zeroText.empty()) return zeroText; else if (_getStringValue && (context & _getStringValueContext)) - return _getStringValue(value); + return _getStringValue(value, context == ContextKeyboard); else return getDefaultStringValue(value); } @@ -130,7 +130,7 @@ class NumberEdit: public BaseNumberEdit std::string suffix; std::string zeroText; std::function isValueAvailable; - std::function _getStringValue; + std::function _getStringValue; #if defined(SOFTWARE_KEYBOARD) bool keyboardEnabled = true; #endif From 575f96aae8fabbe9b469868fcf1ba71da30548cf Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 10 Jun 2025 16:43:47 +0200 Subject: [PATCH 30/54] Add Window::clearCloseHandler() --- src/window.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/window.h b/src/window.h index 0b1ba0d..398dbbc 100644 --- a/src/window.h +++ b/src/window.h @@ -151,6 +151,11 @@ class Window textFlags = flags; } + void clearCloseHandler() + { + closeHandler = nullptr; + } + void setCloseHandler(std::function handler) { closeHandler = std::move(handler); From 2b3b5c66f56c69de9c973c4d1e2fca8357fb7be7 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 12 Jun 2025 09:32:52 +0200 Subject: [PATCH 31/54] [DynamicNumber] Fix when value is displayed RIGHT --- src/static.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static.h b/src/static.h index f594421..11884bf 100644 --- a/src/static.h +++ b/src/static.h @@ -227,7 +227,7 @@ class DynamicNumber: public Window void paint(BitmapBuffer * dc) override { - dc->drawNumber(0, FIELD_PADDING_TOP, value, DEFAULT_COLOR, textFlags, 0, prefix, suffix); + dc->drawNumber((textFlags & RIGHT) ? width() : 0, FIELD_PADDING_TOP, value, DEFAULT_COLOR, textFlags, 0, prefix, suffix); } void checkEvents() override From b6289e92f53aefc69b8a54f6c02156cc24a508a9 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Tue, 24 Jun 2025 16:43:34 +0200 Subject: [PATCH 32/54] Proposal from luftruepel to improve BitmapBuffer::drawLine(). Thanks! --- src/bitmapbuffer.cpp | 566 ++++++++++++++++++++++++++++++++++--------- src/bitmapbuffer.h | 2 - 2 files changed, 456 insertions(+), 112 deletions(-) diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 8874c70..bdc1b65 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -277,146 +277,492 @@ void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h, LcdColor co } } +void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, LcdColor color, uint8_t pat) +{ +// ---------------------------------------------------------------------------- +// Bresenham Line Drawing with Built-In Clipping // -// Liang-barsky clipping algo +// This C++ implementation is based on the algorithm described in the paper: +// "Bresenham's Line Generation Algorithm with Built-in Clipping" +// by Yevgeny P. Kuzmin // -static float lb_max(float arr[], int n) -{ - float m = 0; - for (int i = 0; i < n; ++i) - if (m < arr[i]) m = arr[i]; - return m; -} +// It also derives from the Python implementation by Campbell Barton (ideasman42): +// https://gitlab.com/ideasman42/bresenham-line-plot-clip-py +// +// The original Python version is licensed under the Apache License 2.0, +// which is compatible with the LGPLv3 license used by the libopenui project. +// +// This C++ translation and adaptation for use in libopenui was developed by: +// luftruepel, 2025 +// +// ---------------------------------------------------------------------------- +// Apache License, Version 2.0: +// ---------------------------------------------------------------------------- +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------- -// this function gives the minimum -static float lb_min(float arr[], int n) -{ - float m = 1; - for (int i = 0; i < n; ++i) - if (m > arr[i]) m = arr[i]; - return m; -} -bool BitmapBuffer::clipLine(coord_t & x1, coord_t & y1, coord_t & x2, coord_t & y2) -{ - // defining variables - float p1 = -(x2 - x1); - float p2 = -p1; - float p3 = -(y2 - y1); - float p4 = -p3; + // Offsets + x1 += offsetX; + y1 += offsetY; + x2 += offsetX; + y2 += offsetY; - float q1 = x1 - xmin; - float q2 = xmax - x1; - float q3 = y1 - ymin; - float q4 = ymax - y1; + auto rgb565 = COLOR_TO_RGB565(color); + uint8_t alpha = GET_COLOR_ALPHA(color); + + // ------------------ + // Case: single point + // ------------------ + if (x1 == x2 && y1 == y2) { + drawAlphaPixelAbs(x1, y1, alpha, rgb565); + return; + } + + // ------------------- + // Case: vertical line + // ------------------- + if (x1 == x2) { + // Case: vertical line completely outside of the clipping area (left or right) + if (x1 < xmin || x1 > xmax) { + return; + } + + // Sorting the coordinates to avoid further case differentiation + if (y1 > y2) { + std::swap(y1, y2); + } + + // Case: vertical line completely outside of the clipping area (top or bottom) + if (y2 < ymin || y1 > ymax) { + return; + } - float posarr[5], negarr[5]; - int posind = 1, negind = 1; - posarr[0] = 1; - negarr[0] = 0; + // If necessary, adjust the y-coordinates to fit within the clipping area + y1 = std::max(y1, ymin); + y2 = std::min(y2, ymax); - if ((p1 == 0 && q1 < 0) || (p2 == 0 && q2 < 0) || (p3 == 0 && q3 < 0) || - (p4 == 0 && q4 < 0)) { - return false; + // Loop through the y-coordinates and draw pixels + for (int y = y1; y <= y2; ++y) { + if ((1 << (y % 8)) & pat) { + drawAlphaPixelAbs(x1, y, alpha, rgb565); + } + } + + return; } + + // --------------------- + // Case: horizontal line + // --------------------- + if (y1 == y2) { + // Case: horizontal line completely outside of the clipping area (top or bottom) + if (y1 < ymin || y1 > ymax) { + return; + } - if (p1 != 0) { - float r1 = q1 / p1; - float r2 = q2 / p2; - if (p1 < 0) { - negarr[negind++] = r1; // for negative p1, add it to negative array - posarr[posind++] = r2; // and add p2 to positive array - } else { - negarr[negind++] = r2; - posarr[posind++] = r1; + // Sorting the coordinates to avoid further case differentiation + if (x1 > x2) { + std::swap(x1, x2); } + + // Case: horizontal line completely outside of the clipping area (left or right) + if (x2 < xmin || x1 > xmax) { + return; + } + + // If necessary, adjust the x-coordinates to fit within the clipping area + x1 = std::max(x1, xmin); + x2 = std::min(x2, xmax); + + // Loop through the x-coordinates and draw pixels + for (int x = x1; x <= x2; ++x) { + if ((1 << (x % 8)) & pat) { + drawAlphaPixelAbs(x, y1, alpha, rgb565); + } + } + + return; } - if (p3 != 0) { - float r3 = q3 / p3; - float r4 = q4 / p4; - if (p3 < 0) { - negarr[negind++] = r3; - posarr[posind++] = r4; - } else { - negarr[negind++] = r4; - posarr[posind++] = r3; + + // ------------------ + // Case: general line + // Check whether the line lies outside the clipping area, or if not, transform the coordinates if necessary to simplify the clipping logic + // ------------------ + int sign_x, sign_y; + + // Use local copies of the clipping bounds to avoid modifying the original global values during coordinate transformations + int clip_xmin = xmin; + int clip_xmax = xmax; + int clip_ymin = ymin; + int clip_ymax = ymax; + + if (x1 < x2) { + // Case: line completely outside the clipping area (left or right) + if (x1 > clip_xmax || x2 < clip_xmin) { + return; } + + sign_x = 1; + } + else { + // Case: line completely outside the clipping area (left or right) + if (x2 > clip_xmax || x1 < clip_xmin) { + return; + } + + sign_x = -1; + + // Transformation of the cooordinates. Due to the transformation delta_x and delta_y are allways positive, so the same logic can be used for both cases. + x1 = -x1; + x2 = -x2; + clip_xmin = -clip_xmin; + clip_xmax = -clip_xmax; + std::swap(clip_xmin, clip_xmax); } - float xn1, yn1, xn2, yn2; - float rn1, rn2; - rn1 = lb_max(negarr, negind); // maximum of negative array - rn2 = lb_min(posarr, posind); // minimum of positive array + if (y1 < y2) { + // Case: line completely outside of the clipping area (top or bottom) + if (y1 > clip_ymax || y2 < clip_ymin) { + return; + } - if (rn1 > rn2) { // reject - return false; + sign_y = 1; + } + else { + // Case: line completely outside of the clipping area (top or bottom) + if (y2 > clip_ymax || y1 < clip_ymin) { + return; + } + + sign_y = -1; + + // Transformation of the cooordinates. Due to the transformation delta_x and delta_y are allways positive, so the same logic can be used for both cases. + y1 = -y1; + y2 = -y2; + clip_ymin = -clip_ymin; + clip_ymax = -clip_ymax; + std::swap(clip_ymin, clip_ymax); } - xn1 = x1 + p2 * rn1; - yn1 = y1 + p4 * rn1; // computing new points + // Calculation of parameters for Bresenham's line algorithm + int delta_x = x2 - x1; + int delta_y = y2 - y1; + int delta_x_step = 2*delta_x; + int delta_y_step = 2*delta_y; - xn2 = x1 + p2 * rn2; - yn2 = y1 + p4 * rn2; + int x_pos = x1; + int y_pos = y1; + + // -------------- + // Case: 45° line + // -------------- + if (delta_x == delta_y) { + + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin + x_pos += clip_ymin - y1; + + // Case: The computed intersection at y=clip_ymin lies beyond the right clipping boundary at x=clip_xmax, so the line is completely outside the clipping area + if (x_pos > clip_xmax) { + return; + } - x1 = coord_t(xn1); - y1 = coord_t(yn1); - x2 = coord_t(xn2); - y2 = coord_t(yn2); + // Case: The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== clip_xmin) { + y_pos = clip_ymin; - return true; -} + // If the line intersects the top edge of the clipping area, then the line cannot intersect the left edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } + } + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (!set_exit && x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin + y_pos += clip_xmin - x1; -void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, LcdColor color, uint8_t pat) -{ - // Offsets - x1 += offsetX; - y1 += offsetY; - x2 += offsetX; - y2 += offsetY; + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the bottom boundary at y=clip_ymax, but with the residual error in the Bresenham algorithm is large enough to cause a downward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (y_pos > clip_ymax) { + return; + } - if (!clipLine(x1, y1, x2, y2)) - return; + x_pos = clip_xmin; + } - auto rgb565 = COLOR_TO_RGB565(color); - uint8_t alpha = GET_COLOR_ALPHA(color); + // Step 2: Apply clipping to the line end point (x2, y2) + int x_pos_end = x2; + + // Case: The line end point lies below the bottom clipping boundary at y=clip_ymax + if (y2 > clip_ymax) { + // Determine the y-coordinate where the line intersects y=clip_ymax + x_pos_end = x1 + clip_ymax - y1; + } - int dx = x2 - x1; /* the horizontal distance of the line */ - int dy = y2 - y1; /* the vertical distance of the line */ - int dxabs = abs(dx); - int dyabs = abs(dy); - int sdx = sgn(dx); - int sdy = sgn(dy); - int x = dyabs >> 1; - int y = dxabs >> 1; - int px = x1; - int py = y1; - - if (dxabs >= dyabs) { - /* the line is more horizontal than vertical */ - for (int i = 0; i <= dxabs; i++) { - if ((1 << (px % 8)) & pat) { - drawAlphaPixelAbs(px, py, alpha, rgb565); + // Ensure the end point does not exceed the clipping area. The +1 is needed because the line drawing loop below uses an exclusive condition (x_pos != x_pos_end), so we must go one step beyond the actual last drawable x position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_x == -1. + x_pos_end = std::min(x_pos_end, clip_xmax) + 1; + + // Back transformation of the line coordinates + if (sign_y == -1) { + y_pos = -y_pos; + } + + if (sign_x == -1) { + x_pos = -x_pos; + x_pos_end = -x_pos_end; + } + + // Line drawing + while (x_pos != x_pos_end) { + if ((1 << (x_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); } - y += dyabs; - if (y >= dxabs) { - y -= dxabs; - py += sdy; + + y_pos += sign_y; + x_pos += sign_x; + } + + // ----------------------------------------------- + // Case: the line is more horizontal than vertical + // ----------------------------------------------- + } + else if (delta_x > delta_y) { + int temp; + int msd; + int rem; + + int error = delta_y_step - delta_x; + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin, using Bresenham-style error handling + temp = (2*(clip_ymin - y1) - 1)*delta_x; + msd = temp/delta_y_step; + x_pos += msd; + + // Case: The computed intersection at y=clip_ymin lies beyond the right clipping boundary at x=clip_xmax, so the line is completely outside the clipping area + if (x_pos > clip_xmax) { + return; + } + + // Case: The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== clip_xmin) { + rem = temp - msd*delta_y_step; + + y_pos = clip_ymin; + error -= rem + delta_x; + + if (rem > 0) { + x_pos += 1; + error += delta_y_step; + } + + // If the line intersects the top edge of the clipping area, then the line cannot intersect the left edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } + } + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (!set_exit && x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin, using Bresenham-style error handling + temp = delta_y_step*(clip_xmin - x1); + msd = temp/delta_x_step; + y_pos += msd; + rem = temp % delta_x_step; + + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the bottom boundary at y=clip_ymax, but with the residual error in the Bresenham algorithm is large enough to cause a downward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (y_pos > clip_ymax || (y_pos == clip_ymax && rem >= delta_x)) { + return; + } + + // The computed intersection at x=clip_xmin lies on the left clipping boundary (clip_ymin== delta_x) { + y_pos += 1; + error -= delta_x_step; } - px += sdx; } - } + + // Step 2: Apply clipping to the line end point (x2, y2) + int x_pos_end = x2; + + // Case: The line end point lies below the bottom clipping boundary at y=clip_ymax + if (y2 > clip_ymax) { + // Determine the y-coordinate where the line intersects y=clip_ymax, using Bresenham-style error handling + temp = delta_x_step*(clip_ymax - y1) + delta_x; + msd = temp/delta_y_step; + x_pos_end = x1 + msd; + + // Case: The computed intersection at y=clip_ymax lies exactly on the pixel grid (with no residual error remains). + if ((temp - msd*delta_y_step) == 0) { + x_pos_end -= 1; + } + } + + // Ensure the end point does not exceed the clipping area. The +1 is needed because the Bresenham loop below uses an exclusive condition (x_pos != x_pos_end), so we must go one step beyond the actual last drawable x position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_x == -1. + x_pos_end = std::min(x_pos_end, clip_xmax) + 1; + + // Back transformation of the line coordinates + if (sign_y == -1) { + y_pos = -y_pos; + } + + if (sign_x == -1) { + x_pos = -x_pos; + x_pos_end = -x_pos_end; + } + + // Bresenham's line algorithm + delta_x_step -= delta_y_step; + + while (x_pos != x_pos_end) { + if ((1 << (x_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); + } + + if (error >= 0) { + y_pos += sign_y; + error -= delta_x_step; + } + else { + error += delta_y_step; + } + + x_pos += sign_x; + } + + // -------------------------------------------------------------------------------------------------------------- + // Case: the line is more vertical than horizontal (same as previous case, but with swapped x and y coordinates) + // -------------------------------------------------------------------------------------------------------------- + } else { - /* the line is more vertical than horizontal */ - for (int i = 0; i <= dyabs; i++) { - if ((1 << (py % 8)) & pat) { - drawAlphaPixelAbs(px, py, alpha, rgb565); + int temp; + int msd; + int rem; + + int error = delta_x_step - delta_y; + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin, using Bresenham-style error handling + temp = (2*(clip_xmin - x1) - 1)*delta_y; + msd = temp/delta_x_step; + y_pos += msd; + + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area + if (y_pos > clip_ymax) { + return; + } + + // Case: The computed intersection at x=clip_xmin lies on the left clipping boundary (clip_ymin== clip_ymin) { + rem = temp - msd*delta_x_step; + + x_pos = clip_xmin; + error -= rem + delta_y; + + if (rem > 0) { + y_pos += 1; + error += delta_x_step; + } + + // If the line intersects the left edge of the clipping area, then the line cannot intersect the top edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } + } + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (!set_exit && y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin, using Bresenham-style error handling + temp = delta_x_step*(clip_ymin - y1); + msd = temp/delta_y_step; + x_pos += msd; + rem = temp % delta_y_step; + + // Case: The computed intersection at y=clip_ymin lies on the right of the clipping boundary at x=clip_xmax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the right boundary at x=clip_xmax, but with the residual error in the Bresenham algorithm is large enough to cause a rightward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (x_pos > clip_xmax || (x_pos == clip_xmax && rem >= delta_y)) { + return; + } + + // The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== delta_y) { + x_pos += 1; + error -= delta_y_step; } - x += dxabs; - if (x >= dyabs) { - x -= dyabs; - px += sdx; + } + + // Step 2: Apply clipping to the line end point (x2, y2) + int y_pos_end = y2; + + // Case: The line end point lies beyond the right clipping boundary at x=clip_xmax + if (x2 > clip_xmax) { + // Determine the y-coordinate where the line intersects x=clip_xmax, using Bresenham-style error handling + temp = delta_y_step*(clip_xmax - x1) + delta_y; + msd = temp/delta_x_step; + y_pos_end = y1 + msd; + + // Case: The computed intersection at x=clip_xmax lies exactly on the pixel grid (with no residual error remains). + if ((temp - msd*delta_x_step) == 0) { + y_pos_end -= 1; } - py += sdy; + } + + // Ensure the end point does not exceed the clipping area. The +1 is needed because the Bresenham loop below uses an exclusive condition (y_pos != y_pos_end), so we must go one step beyond the actual last drawable y position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_y == -1. + y_pos_end = std::min(y_pos_end, clip_ymax) + 1; + + // Back transformation of the line coordinates + if (sign_x == -1) { + x_pos = -x_pos; + } + + if (sign_y == -1) { + y_pos = -y_pos; + y_pos_end = -y_pos_end; + } + + // Bresenham's line algorithm + delta_y_step -= delta_x_step; + + while (y_pos != y_pos_end) { + if ((1 << (y_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); + } + + if (error >= 0) { + x_pos += sign_x; + error -= delta_y_step; + } + else { + error += delta_x_step; + } + + y_pos += sign_y; } } } diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index c9f45c4..1cbaa3f 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -781,8 +781,6 @@ class BitmapBuffer: public Bitmap void drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w, LcdColor color, uint8_t pat = SOLID); - bool clipLine(coord_t& x1, coord_t& y1, coord_t& x2, coord_t& y2); - void fillRectangle(coord_t x, coord_t y, coord_t w, coord_t h, pixel_t color); void fillBottomFlatTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, LcdColor color); From eccf44daed84868242db0f13e52ef1475bc84b32 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 2 Jul 2025 10:01:47 +0200 Subject: [PATCH 33/54] Allow reuse of MenuWindowContent in MultiMenu --- src/choice.cpp | 1 - src/choice.h | 9 ++++++ src/menu.cpp | 79 +++++++++++++++++++++++++----------------------- src/menu.h | 81 +++++++++++++++++++++++++++++++++++++------------- src/theme.h | 2 +- 5 files changed, 113 insertions(+), 59 deletions(-) diff --git a/src/choice.cpp b/src/choice.cpp index 58da394..31fe1d7 100644 --- a/src/choice.cpp +++ b/src/choice.cpp @@ -158,7 +158,6 @@ void Choice::openMenu() }); setEditMode(true); - invalidate(); } #if defined(HARDWARE_TOUCH) diff --git a/src/choice.h b/src/choice.h index 65552fb..9fab7ee 100644 --- a/src/choice.h +++ b/src/choice.h @@ -87,6 +87,15 @@ class Choice: public ChoiceBase Choice(FormGroup * parent, const rect_t & rect, const char * values, int vmin, int vmax, std::function getValue, std::function setValue = nullptr, WindowFlags windowFlags = 0); + Choice(FormGroup * parent, const rect_t & rect, std::function getValue, std::function setValue = nullptr, WindowFlags windowFlags = 0): + ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags), + vmin(0), + vmax(-1), + getValue(std::move(getValue)), + setValue(std::move(setValue)) + { + } + void addValue(const char * value); void addValues(const char * const values[], uint8_t count); diff --git a/src/menu.cpp b/src/menu.cpp index 5089c63..3b8b93f 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -27,17 +27,20 @@ using namespace ui; void MenuBody::select(int index) { selectedIndex = index; - if (innerHeight > height()) { - if (scrollPositionY + height() < MENUS_LINE_HEIGHT * (index + 1)) { - setScrollPositionY(MENUS_LINE_HEIGHT * (index + 1) - height()); - } - else if (scrollPositionY > MENUS_LINE_HEIGHT * index) { - setScrollPositionY(MENUS_LINE_HEIGHT * index); + + if (index >= 0) { + if (innerHeight > height()) { + if (scrollPositionY + height() < MENUS_LINE_HEIGHT * (index + 1)) { + setScrollPositionY(MENUS_LINE_HEIGHT * (index + 1) - height()); + } + else if (scrollPositionY > MENUS_LINE_HEIGHT * index) { + setScrollPositionY(MENUS_LINE_HEIGHT * index); + } } - } - if (lines[index].onSelect) { - lines[index].onSelect(); + if (lines[index].onSelect) { + lines[index].onSelect(); + } } invalidate(); @@ -50,13 +53,13 @@ void MenuBody::onEvent(event_t event) if (event == EVT_ROTARY_RIGHT) { if (!lines.empty()) { - select(int((selectedIndex + 1) % lines.size())); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex >= lines.size() - 1 ? 0 : selectedIndex + 1))); onKeyPress(); } } else if (event == EVT_ROTARY_LEFT) { if (!lines.empty()) { - select(int(selectedIndex <= 0 ? lines.size() - 1 : selectedIndex - 1)); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex == 0 ? lines.size() - 1 : selectedIndex - 1))); onKeyPress(); } } @@ -64,28 +67,28 @@ void MenuBody::onEvent(event_t event) if (!lines.empty()) { onKeyPress(); if (selectedIndex < 0) { - select(0); + select(defaultSelection); } - else { + else if (multiple) { + lines[selectedIndex].onPress(); + getParentMenu()->invalidate(); + } + else if (autoClose) { auto menu = getParentMenu(); - if (menu->multiple) { - lines[selectedIndex].onPress(); - menu->invalidate(); - } - else { - Layer::pop(menu); - lines[selectedIndex].onPress(); - menu->deleteLater(); // called at the end in case onPress changes the closeHandler - } + Layer::pop(menu); + lines[selectedIndex].onPress(); + menu->deleteLater(); // called at the end in case onPress changes the closeHandler + } + else { + lines[selectedIndex].onPress(); } } } else if (event == EVT_KEY_BREAK(KEY_EXIT)) { onKeyPress(); - if (onCancel) { - onCancel(); + if (!onCancel || onCancel()) { + Window::onEvent(event); } - Window::onEvent(event); } else { Window::onEvent(event); @@ -96,22 +99,26 @@ void MenuBody::onEvent(event_t event) #if defined(HARDWARE_TOUCH) bool MenuBody::onTouchEnd(coord_t /*x*/, coord_t y) { - Menu * menu = getParentMenu(); + auto * menu = getParentMenu(); int index = y / MENUS_LINE_HEIGHT; if (index < (int)lines.size()) { onKeyPress(); - if (menu->multiple) { + if (multiple) { if (selectedIndex == index && lines[index].onPress) lines[index].onPress(); else select(index); menu->invalidate(); } - else { + else if (autoClose) { Layer::pop(menu); lines[index].onPress(); menu->deleteLater(); // called at the end in case onPress changes the closeHandler } + else { + selectedIndex = index; + lines[index].onPress(); + } } return true; } @@ -155,8 +162,7 @@ void MenuBody::paint(BitmapBuffer * dc) } } - Menu * menu = getParentMenu(); - if (menu->multiple && line.isChecked) { + if (multiple && line.isChecked) { theme->drawCheckBox(dc, line.isChecked(), IS_TRANSLATION_RIGHT_TO_LEFT() ? MENUS_HORIZONTAL_PADDING : width() - MENUS_HORIZONTAL_PADDING - CHECKBOX_WIDTH, i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - CHECKBOX_WIDTH) / 2, 0); } @@ -166,9 +172,9 @@ void MenuBody::paint(BitmapBuffer * dc) } } -MenuWindowContent::MenuWindowContent(Menu * parent, bool footer): - ModalWindowContent(parent, {(LCD_W - MIN_MENUS_WIDTH) / 2, (LCD_H - MIN_MENUS_WIDTH) / 2, MIN_MENUS_WIDTH, 0}), - body(this, {0, 0, MIN_MENUS_WIDTH, 0}) +MenuWindowContent::MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer): + ModalWindowContent(parent, rect), + body(this, {0, 0, MIN_MENUS_WIDTH, 0}, multiple) { body.setFocus(SET_FOCUS_DEFAULT); if (footer) { @@ -209,8 +215,7 @@ void MenuWindowContent::paint(BitmapBuffer * dc) Menu::Menu(Window * parent, bool multiple, bool footer): ModalWindow(parent, true), - content(createMenuWindow(this, footer)), - multiple(multiple) + content(createMenuWindow(this, multiple, footer)) { } @@ -248,7 +253,7 @@ void Menu::addLine(const std::string & text, const Mask * mask, std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addCustomLine(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); updatePosition(); @@ -266,7 +271,7 @@ void Menu::onEvent(event_t event) if (event == EVT_KEY_BREAK(KEY_EXIT)) { deleteLater(); } - else if (event == EVT_KEY_BREAK(KEY_ENTER) && !multiple) { + else if (event == EVT_KEY_BREAK(KEY_ENTER) && content->body.autoClose && !content->body.multiple) { deleteLater(); } } diff --git a/src/menu.h b/src/menu.h index 7af8796..28e4dbb 100644 --- a/src/menu.h +++ b/src/menu.h @@ -34,7 +34,6 @@ class MenuWindowContent; class MenuBody: public Window { - friend class MenuWindowContent; friend class Menu; class MenuLine @@ -51,7 +50,7 @@ class MenuBody: public Window { } - MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): drawLine(std::move(drawLine)), onPress(std::move(onPress)), onSelect(std::move(onSelect)), @@ -66,15 +65,16 @@ class MenuBody: public Window protected: std::string text; const Mask * icon; - std::function drawLine; + std::function drawLine; std::function onPress; std::function onSelect; std::function isChecked; }; public: - MenuBody(Window * parent, const rect_t & rect): - Window(parent, rect, OPAQUE) + MenuBody(Window * parent, const rect_t & rect, bool multiple): + Window(parent, rect, OPAQUE), + multiple(multiple) { setPageHeight(MENUS_LINE_HEIGHT); } @@ -106,7 +106,7 @@ class MenuBody: public Window bool onTouchEnd(coord_t x, coord_t y) override; #endif - void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked) + void addLine(const std::string & text, const Mask * icon = nullptr, std::function onPress = nullptr, std::function onSelect = nullptr, std::function isChecked = nullptr) { lines.emplace_back(text, icon, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (icon) @@ -114,7 +114,7 @@ class MenuBody: public Window invalidate(); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { lines.emplace_back(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); invalidate(); @@ -126,24 +126,46 @@ class MenuBody: public Window invalidate(); } - void setCancelHandler(std::function handler) + void setCancelHandler(std::function handler) { onCancel = std::move(handler); } + void setDefaultSelection(int index) + { + defaultSelection = index; + } + + int getDefaultSelection() + { + return defaultSelection; + } + + void setAutoClose(bool value) + { + autoClose = value; + } + void paint(BitmapBuffer * dc) override; - protected: std::vector lines; + + protected: #if defined(HARDWARE_TOUCH) int selectedIndex = -1; #else int selectedIndex = 0; #endif - std::function onCancel; + int defaultSelection = 0; + std::function onCancel; bool displayIcons = false; + bool autoClose = true; + bool multiple = false; - inline Menu * getParentMenu(); + inline Window * getParentMenu() + { + return getParent()->getParent(); + } }; class MenuWindowContent: public ModalWindowContent @@ -151,7 +173,7 @@ class MenuWindowContent: public ModalWindowContent friend class Menu; public: - explicit MenuWindowContent(Menu * parent, bool footer = false); + explicit MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer); void deleteLater(bool detach = true, bool trash = true) override { @@ -175,6 +197,11 @@ class MenuWindowContent: public ModalWindowContent void paint(BitmapBuffer * dc) override; + MenuBody * getBody() + { + return &body; + } + FormGroup * getFooter() const { return footer; @@ -187,8 +214,6 @@ class MenuWindowContent: public ModalWindowContent class Menu: public ModalWindow { - friend class MenuBody; - public: explicit Menu(Window * parent, bool multiple = false, bool footer = false); @@ -199,7 +224,7 @@ class Menu: public ModalWindow } #endif - void setCancelHandler(std::function handler) + void setCancelHandler(std::function handler) { content->body.setCancelHandler(std::move(handler)); } @@ -217,7 +242,7 @@ class Menu: public ModalWindow addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void removeLines(); @@ -245,14 +270,30 @@ class Menu: public ModalWindow protected: MenuWindowContent * content; - bool multiple; std::function waitHandler; void updatePosition(); }; -Menu * MenuBody::getParentMenu() +class MultiMenu: public ModalWindow { - return static_cast(getParent()->getParent()); -} + public: + explicit MultiMenu(Window * parent): + ModalWindow(parent, true) + { + } + + void addColumn(MenuWindowContent * column) + { + columns.emplace_back(column); + } + + MenuWindowContent * getColumn(uint8_t index) + { + return columns[index]; + } + + protected: + std::vector columns; +}; } diff --git a/src/theme.h b/src/theme.h index d03ab72..3e3073a 100644 --- a/src/theme.h +++ b/src/theme.h @@ -64,7 +64,7 @@ class Theme extern Theme * theme; -MenuWindowContent * createMenuWindow(Menu * menu, bool footer = false); +MenuWindowContent * createMenuWindow(Menu * menu, bool multiple = false, bool footer = false); DialogWindowContent * createDialogWindow(Dialog * dialog, const rect_t & rect); } From 94fc32f345bf8c3e832c2c5d4b2073b277ed2889 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 10 Jul 2025 11:15:17 +0200 Subject: [PATCH 34/54] Warning removed --- src/menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/menu.cpp b/src/menu.cpp index 3b8b93f..44f3cfa 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -53,13 +53,13 @@ void MenuBody::onEvent(event_t event) if (event == EVT_ROTARY_RIGHT) { if (!lines.empty()) { - select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex >= lines.size() - 1 ? 0 : selectedIndex + 1))); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex >= int(lines.size() - 1) ? 0 : selectedIndex + 1))); onKeyPress(); } } else if (event == EVT_ROTARY_LEFT) { if (!lines.empty()) { - select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex == 0 ? lines.size() - 1 : selectedIndex - 1))); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex == 0 ? int(lines.size() - 1) : selectedIndex - 1))); onKeyPress(); } } From a1c5140d4c2dd82d4160b529290c050360e17375 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 23 Jul 2025 05:47:28 +0200 Subject: [PATCH 35/54] Problem with table focus when followed by a button --- src/table.cpp | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/table.cpp b/src/table.cpp index 6da6a15..d8518d4 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -118,45 +118,37 @@ void Table::Body::onEvent(event_t event) if (event == EVT_ROTARY_RIGHT) { onKeyPress(); auto table = static_cast(parent); - if (table->getWindowFlags() & FORWARD_SCROLL) { - auto lineIndex = selection + 1; - if (lineIndex < int(lines.size())) { - select(lineIndex, true); - } - else { - auto next = table->getNextField(); - if (next) { - next->setFocus(SET_FOCUS_FORWARD, this); - if (!hasFocus()) { - select(-1, false); - } - } - } + auto lineIndex = selection + 1; + if (lineIndex < int(lines.size())) { + select(lineIndex, true); } else { - if (!lines.empty()) { - select((selection + 1) % lines.size(), true); + auto next = table->getNextField(); + if (next) { + next->setFocus(SET_FOCUS_FORWARD, this); + if (!hasFocus()) { + select(-1, false); + } + } + else if (!table->getPreviousField() && !lines.empty()) { + select(selection >= lines.size() - 1 ? 0 : selection + 1, true); } } } else if (event == EVT_ROTARY_LEFT) { onKeyPress(); auto table = static_cast
(parent); - if (table->getWindowFlags() & FORWARD_SCROLL) { - auto lineIndex = selection - 1; - if (lineIndex >= 0) { - select(lineIndex, true); - } - else { - auto previous = table->getPreviousField(); - if (previous) { - select(-1, false); - previous->setFocus(SET_FOCUS_BACKWARD); - } - } + auto lineIndex = selection - 1; + if (lineIndex >= 0) { + select(lineIndex, true); } else { - if (!lines.empty()) { + auto previous = table->getPreviousField(); + if (previous) { + select(-1, false); + previous->setFocus(SET_FOCUS_BACKWARD); + } + else if (!table->getNextField() && !lines.empty()) { select(selection <= 0 ? lines.size() - 1 : selection - 1, true); } } From f10dc960d7438a0bba6adb8abe67c72bcfd6de0a Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 23 Jul 2025 06:37:36 +0200 Subject: [PATCH 36/54] Warning removed --- src/table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.cpp b/src/table.cpp index d8518d4..64246f4 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -131,7 +131,7 @@ void Table::Body::onEvent(event_t event) } } else if (!table->getPreviousField() && !lines.empty()) { - select(selection >= lines.size() - 1 ? 0 : selection + 1, true); + select(selection >= int(lines.size() - 1) ? 0 : selection + 1, true); } } } From 1a38fa2994c9406f5e1d8ec9393162d3eb6c6c49 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 28 Jul 2025 11:48:14 +0200 Subject: [PATCH 37/54] Return something from writeUnicodeChar to allow easy append --- src/unicode.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/unicode.h b/src/unicode.h index d3f8d84..2e1137c 100644 --- a/src/unicode.h +++ b/src/unicode.h @@ -120,26 +120,27 @@ inline uint8_t getUnicodeCharLength(uint32_t c) return 4; } -inline void writeUnicodeChar(char * s, uint32_t c) +inline char * writeUnicodeChar(char * s, uint32_t c) { if (c < 0x80) { - *s = c; + *s++ = c; } else if (c <= 0b11111111111) { *s++ = 0xC0 | ((c >> 6) & 0b11111); - *s = 0b10000000 | (c & 0b111111); + *s++ = 0b10000000 | (c & 0b111111); } else if (c <= 0b1111111111111111) { *s++ = 0xE0 | ((c >> 12) & 0b1111); *s++ = 0b10000000 | ((c >> 6) & 0b111111); - *s = 0b10000000 | (c & 0b111111); + *s++ = 0b10000000 | (c & 0b111111); } else { *s++ = 0xF0 | ((c >> 18) & 0b111); *s++ = 0b10000000 | ((c >> 12) & 0b111111); *s++ = 0b10000000 | ((c >> 6) & 0b111111); - *s = 0b10000000 | (c & 0b111111); + *s++ = 0b10000000 | (c & 0b111111); } + return s; } inline void insertUnicodeChar(char * s, uint8_t position, uint32_t c, uint8_t maxLength) From c12a7f3c8ae8247e0a79d1cd2a435c3edee175d0 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 18 Aug 2025 12:14:52 +0200 Subject: [PATCH 38/54] Warning removed --- src/bitmapbuffer.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 1cbaa3f..70bf460 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -232,6 +232,7 @@ class Mask: public Raster static Mask * allocate(const Mask * from, uint16_t width, uint16_t height) { + UNUSED(from); return allocate(width, height); } From 9ce0ff1618fd173a41fc4481c54b27eae32aef7e Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 27 Aug 2025 22:05:40 +0200 Subject: [PATCH 39/54] Simplify the touch interface --- src/touch.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/touch.h b/src/touch.h index f10ed6a..bb33d38 100644 --- a/src/touch.h +++ b/src/touch.h @@ -68,11 +68,6 @@ struct TouchState } } - void killEvents() - { - event = state = TE_NONE; - } - unsigned char popEvent() { auto result = event; From 85623e6e3137c69b38b8c4fb987ab8290ade6a2f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 3 Sep 2025 17:13:41 +0200 Subject: [PATCH 40/54] DynamicText was one loop late --- src/static.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static.h b/src/static.h index 11884bf..017cf06 100644 --- a/src/static.h +++ b/src/static.h @@ -179,7 +179,7 @@ class DynamicText: public StaticText { public: DynamicText(Window * parent, const rect_t & rect, std::function textHandler, LcdFlags textFlags = 0): - StaticText(parent, rect, "", 0, textFlags), + StaticText(parent, rect, textHandler(), 0, textFlags), textHandler(std::move(textHandler)) { } From 97fb6116404f4926d64ec5ae4ee4cacc90c221a5 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 3 Sep 2025 17:33:30 +0200 Subject: [PATCH 41/54] DynamicStringCell was one loop late --- src/table.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/table.h b/src/table.h index e3dca9c..d2e8ec2 100644 --- a/src/table.h +++ b/src/table.h @@ -82,6 +82,7 @@ class Table: public FormField explicit DynamicStringCell(std::function getText): getText(std::move(getText)) { + value = this->getText(); } [[nodiscard]] bool needsInvalidate() override From 15e614dcf594d9d30f9294735db03cd903d1f24e Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Wed, 3 Sep 2025 18:33:43 +0200 Subject: [PATCH 42/54] Segfault in previous commit --- src/static.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static.h b/src/static.h index 017cf06..3d0d0c2 100644 --- a/src/static.h +++ b/src/static.h @@ -179,7 +179,7 @@ class DynamicText: public StaticText { public: DynamicText(Window * parent, const rect_t & rect, std::function textHandler, LcdFlags textFlags = 0): - StaticText(parent, rect, textHandler(), 0, textFlags), + StaticText(parent, rect, textHandler ? textHandler() : "", 0, textFlags), textHandler(std::move(textHandler)) { } From 58a77f987a11254e46d907fc656fb2f7e0012e3f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 18 Sep 2025 22:06:39 +0200 Subject: [PATCH 43/54] Add the support for radio buttons --- src/menu.cpp | 23 ++++++++++++++--------- src/menu.h | 13 ++++++++----- src/theme.h | 3 ++- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/menu.cpp b/src/menu.cpp index 44f3cfa..4ee6063 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -69,7 +69,7 @@ void MenuBody::onEvent(event_t event) if (selectedIndex < 0) { select(defaultSelection); } - else if (multiple) { + else if (flags & MENU_MULTIPLE) { lines[selectedIndex].onPress(); getParentMenu()->invalidate(); } @@ -103,7 +103,7 @@ bool MenuBody::onTouchEnd(coord_t /*x*/, coord_t y) int index = y / MENUS_LINE_HEIGHT; if (index < (int)lines.size()) { onKeyPress(); - if (multiple) { + if (flags & MENU_MULTIPLE) { if (selectedIndex == index && lines[index].onPress) lines[index].onPress(); else @@ -162,8 +162,13 @@ void MenuBody::paint(BitmapBuffer * dc) } } - if (multiple && line.isChecked) { - theme->drawCheckBox(dc, line.isChecked(), IS_TRANSLATION_RIGHT_TO_LEFT() ? MENUS_HORIZONTAL_PADDING : width() - MENUS_HORIZONTAL_PADDING - CHECKBOX_WIDTH, i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - CHECKBOX_WIDTH) / 2, 0); + if ((flags & MENU_MULTIPLE) && line.isChecked) { + if (flags & MENU_RADIO_BUTTONS) { + theme->drawRadioButton(dc, line.isChecked(), IS_TRANSLATION_RIGHT_TO_LEFT() ? MENUS_HORIZONTAL_PADDING : width() - MENUS_HORIZONTAL_PADDING - CHECKBOX_WIDTH, i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - CHECKBOX_WIDTH) / 2, 0); + } + else { + theme->drawCheckBox(dc, line.isChecked(), IS_TRANSLATION_RIGHT_TO_LEFT() ? MENUS_HORIZONTAL_PADDING : width() - MENUS_HORIZONTAL_PADDING - CHECKBOX_WIDTH, i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - CHECKBOX_WIDTH) / 2, 0); + } } if (i > 0) { @@ -172,9 +177,9 @@ void MenuBody::paint(BitmapBuffer * dc) } } -MenuWindowContent::MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer): +MenuWindowContent::MenuWindowContent(ModalWindow * parent, const rect_t & rect, uint8_t flags, bool footer): ModalWindowContent(parent, rect), - body(this, {0, 0, MIN_MENUS_WIDTH, 0}, multiple) + body(this, {0, 0, MIN_MENUS_WIDTH, 0}, flags) { body.setFocus(SET_FOCUS_DEFAULT); if (footer) { @@ -213,9 +218,9 @@ void MenuWindowContent::paint(BitmapBuffer * dc) } } -Menu::Menu(Window * parent, bool multiple, bool footer): +Menu::Menu(Window * parent, uint8_t flags, bool footer): ModalWindow(parent, true), - content(createMenuWindow(this, multiple, footer)) + content(createMenuWindow(this, flags, footer)) { } @@ -271,7 +276,7 @@ void Menu::onEvent(event_t event) if (event == EVT_KEY_BREAK(KEY_EXIT)) { deleteLater(); } - else if (event == EVT_KEY_BREAK(KEY_ENTER) && content->body.autoClose && !content->body.multiple) { + else if (event == EVT_KEY_BREAK(KEY_ENTER) && content->body.autoClose && !(content->body.flags & MENU_MULTIPLE)) { deleteLater(); } } diff --git a/src/menu.h b/src/menu.h index 28e4dbb..3816992 100644 --- a/src/menu.h +++ b/src/menu.h @@ -27,6 +27,9 @@ constexpr coord_t MENUS_HORIZONTAL_PADDING = 10; +constexpr uint8_t MENU_MULTIPLE = (1 << 0); +constexpr uint8_t MENU_RADIO_BUTTONS = (1 << 1); + namespace ui { class Menu; @@ -72,9 +75,9 @@ class MenuBody: public Window }; public: - MenuBody(Window * parent, const rect_t & rect, bool multiple): + MenuBody(Window * parent, const rect_t & rect, uint8_t flags): Window(parent, rect, OPAQUE), - multiple(multiple) + flags(flags) { setPageHeight(MENUS_LINE_HEIGHT); } @@ -160,7 +163,7 @@ class MenuBody: public Window std::function onCancel; bool displayIcons = false; bool autoClose = true; - bool multiple = false; + uint8_t flags = 0; inline Window * getParentMenu() { @@ -173,7 +176,7 @@ class MenuWindowContent: public ModalWindowContent friend class Menu; public: - explicit MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer); + explicit MenuWindowContent(ModalWindow * parent, const rect_t & rect, uint8_t flags, bool footer); void deleteLater(bool detach = true, bool trash = true) override { @@ -215,7 +218,7 @@ class MenuWindowContent: public ModalWindowContent class Menu: public ModalWindow { public: - explicit Menu(Window * parent, bool multiple = false, bool footer = false); + explicit Menu(Window * parent, uint8_t flags = 0, bool footer = false); #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override diff --git a/src/theme.h b/src/theme.h index 3e3073a..1cab0ce 100644 --- a/src/theme.h +++ b/src/theme.h @@ -51,6 +51,7 @@ class Theme dc->drawText(22, FIELD_PADDING_TOP, label, DEFAULT_COLOR); } virtual void drawCheckBox(BitmapBuffer * dc, bool checked, coord_t x, coord_t y, bool focus = false) const = 0; + virtual void drawRadioButton(BitmapBuffer * dc, bool checked, coord_t x, coord_t y, bool focus = false) const = 0; virtual void drawChoice(BitmapBuffer * dc, ChoiceBase * choice, const char * str) const = 0; virtual void drawSlider(BitmapBuffer * dc, int vmin, int vmax, int value, const rect_t & rect, bool edit, bool focus) const = 0; virtual const BitmapBuffer * getIcon(uint8_t index, IconState state) const = 0; @@ -64,7 +65,7 @@ class Theme extern Theme * theme; -MenuWindowContent * createMenuWindow(Menu * menu, bool multiple = false, bool footer = false); +MenuWindowContent * createMenuWindow(Menu * menu, uint8_t flags = 0, bool footer = false); DialogWindowContent * createDialogWindow(Dialog * dialog, const rect_t & rect); } From 0f3b1432793161c890ab6f84283177026f4bdc49 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 9 Oct 2025 22:38:40 +0200 Subject: [PATCH 44/54] [Table] editMode was set when pressing [Enter] --- src/table.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/table.cpp b/src/table.cpp index 64246f4..ed994ca 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -115,7 +115,7 @@ void Table::Body::onEvent(event_t event) onPress(); } } - if (event == EVT_ROTARY_RIGHT) { + else if (event == EVT_ROTARY_RIGHT) { onKeyPress(); auto table = static_cast
(parent); auto lineIndex = selection + 1; From 144a9b68fb97a0386008ae3c65221956576a9035 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 23 Oct 2025 13:26:12 +0200 Subject: [PATCH 45/54] Expansion panel simplification --- src/expansion_panel.cpp | 58 ----------------------------------- src/expansion_panel.h | 68 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/expansion_panel.cpp b/src/expansion_panel.cpp index 7ab796a..9c6a561 100644 --- a/src/expansion_panel.cpp +++ b/src/expansion_panel.cpp @@ -49,61 +49,3 @@ bool ExpansionPanel::setFocus(uint8_t flag, Window * from) // NOLINT(google-defa return header->setFocus(flag, from); } } - -ExpansionPanelHeader::ExpansionPanelHeader(ExpansionPanel * parent): - FormGroup(parent, {0, 0, parent->width(), parent->height()}, FORWARD_SCROLL) -{ -} - -void ExpansionPanelHeader::onEvent(event_t event) -{ - auto panel = static_cast(parent); - - if (event == EVT_KEY_BREAK(KEY_ENTER)) { - panel->toggle(); - } - else if (event == EVT_ROTARY_RIGHT && !panel->isOpen()) { - auto next = panel->getNextField(); - if (next) - next->setFocus(SET_FOCUS_FORWARD, this); - } - else if (event == EVT_ROTARY_LEFT) { - auto previous = panel->getPreviousField(); - if (previous) { - previous->setFocus(SET_FOCUS_BACKWARD, this); - } - } - else { - FormGroup::onEvent(event); - } -} - -bool ExpansionPanelHeader::setFocus(uint8_t flag, Window * from) // NOLINT(google-default-arguments) -{ - auto panel = static_cast(parent); - - if (enabled || panel->isOpen()) { - return FormGroup::setFocus(flag, from); - } - else { - if (flag == SET_FOCUS_BACKWARD) { - auto previous = panel->getPreviousField(); - return previous ? previous->setFocus(SET_FOCUS_BACKWARD, this) : false; - } - else { - auto next = panel->getNextField(); - return next ? next->setFocus(SET_FOCUS_FORWARD, this) : false; - } - } -} - -#if defined(HARDWARE_TOUCH) -bool ExpansionPanelHeader::onTouchEnd(coord_t, coord_t) -{ - if (enabled) { - static_cast(parent)->toggle(); - setFocus(SET_FOCUS_DEFAULT); - } - return true; -} -#endif diff --git a/src/expansion_panel.h b/src/expansion_panel.h index 1a7c8ad..0522d20 100644 --- a/src/expansion_panel.h +++ b/src/expansion_panel.h @@ -25,10 +25,11 @@ namespace ui { class ExpansionPanel; -class ExpansionPanelHeader: virtual public FormGroup +template +class ExpansionPanelHeader: public T { public: - explicit ExpansionPanelHeader(ExpansionPanel * parent); + using T::T; #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override @@ -48,7 +49,7 @@ class ExpansionPanelHeader: virtual public FormGroup class ExpansionPanel: public FormGroup { - friend class ExpansionPanelHeader; + // friend class ExpansionPanelHeader; public: ExpansionPanel(Window * parent, const rect_t & rect): @@ -122,7 +123,7 @@ class ExpansionPanel: public FormGroup bool setFocus(uint8_t flag = SET_FOCUS_DEFAULT, Window * from = nullptr) override; // NOLINT(google-default-arguments) - ExpansionPanelHeader * getHeader() + FormGroup * getHeader() { return header; } @@ -134,9 +135,66 @@ class ExpansionPanel: public FormGroup protected: bool _isOpen = false; - ExpansionPanelHeader * header = nullptr; + FormGroup * header = nullptr; FormGroup * body = nullptr; std::function openHandler; }; + +template +bool ExpansionPanelHeader::setFocus(uint8_t flag, Window * from) // NOLINT(google-default-arguments) +{ + auto panel = static_cast(T::parent); + + if (T::enabled || panel->isOpen()) { + return FormGroup::setFocus(flag, from); + } + else { + if (flag == SET_FOCUS_BACKWARD) { + auto previous = panel->getPreviousField(); + return previous ? previous->setFocus(SET_FOCUS_BACKWARD, this) : false; + } + else { + auto next = panel->getNextField(); + return next ? next->setFocus(SET_FOCUS_FORWARD, this) : false; + } + } +} + +template +void ExpansionPanelHeader::onEvent(event_t event) +{ + auto panel = static_cast(T::parent); + + if (event == EVT_KEY_BREAK(KEY_ENTER)) { + panel->toggle(); + } + else if (event == EVT_ROTARY_RIGHT && !panel->isOpen()) { + auto next = panel->getNextField(); + if (next) + next->setFocus(SET_FOCUS_FORWARD, this); + } + else if (event == EVT_ROTARY_LEFT) { + auto previous = panel->getPreviousField(); + if (previous) { + previous->setFocus(SET_FOCUS_BACKWARD, this); + } + } + else { + FormGroup::onEvent(event); + } +} + +#if defined(HARDWARE_TOUCH) +template +bool ExpansionPanelHeader::onTouchEnd(coord_t, coord_t) +{ + if (T::enabled) { + static_cast(T::parent)->toggle(); + setFocus(SET_FOCUS_DEFAULT); + } + return true; +} +#endif + } From c8383c000c578f8ea505b2bf3447b7a818f97775 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 13 Nov 2025 15:53:18 +0100 Subject: [PATCH 46/54] Table paint optimization --- src/table.cpp | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/table.cpp b/src/table.cpp index ed994ca..5759819 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -54,11 +54,11 @@ void Table::Body::checkEvents() coord_t x = TABLE_HORIZONTAL_PADDING; for (unsigned i = 0; i < line->cells.size(); i++) { auto cell = line->cells[i]; - auto width = static_cast
(parent)->columnsWidth[i]; + auto columnWidth = static_cast
(parent)->columnsWidth[i]; if (cell && cell->needsInvalidate()) { - invalidate({x, y - scrollPositionY, width ? width : line->width() - x, line->height() - TABLE_LINE_BORDER}); + invalidate({x, y - scrollPositionY, columnWidth ? columnWidth : line->width() - x, line->height() - TABLE_LINE_BORDER}); } - x += width; + x += columnWidth; } } y += line->lineHeight; @@ -69,19 +69,26 @@ void Table::Body::paint(BitmapBuffer * dc) { int lineIndex = 0; dc->clear(DEFAULT_BGCOLOR); + coord_t y = 0; for (auto line: lines) { - bool highlight = (lineIndex == selection); - dc->drawPlainFilledRectangle(0, line->top(), line->width(), line->height() - TABLE_LINE_BORDER, highlight ? FOCUS_COLOR : TABLE_BGCOLOR); - coord_t x = TABLE_HORIZONTAL_PADDING; - for (unsigned i = 0; i < line->cells.size(); i++) { - auto cell = line->cells[i]; - auto columnWidth = static_cast
(parent)->columnsWidth[i]; - if (cell) { - cell->paint(dc, rect_t{x, line->top(), columnWidth, line->height()}, highlight ? EDIT_COLOR : line->color, line->font); + if (y > scrollPositionY - line->height()) { + if (y >= scrollPositionY + height()) { + break; } - x += columnWidth; + bool highlight = (lineIndex == selection); + dc->drawPlainFilledRectangle(0, line->top(), line->width(), line->height() - TABLE_LINE_BORDER, highlight ? FOCUS_COLOR : TABLE_BGCOLOR); + coord_t x = TABLE_HORIZONTAL_PADDING; + for (unsigned i = 0; i < line->cells.size(); i++) { + auto cell = line->cells[i]; + auto columnWidth = static_cast
(parent)->columnsWidth[i]; + if (cell) { + cell->paint(dc, rect_t{x, line->top(), columnWidth, line->height()}, highlight ? EDIT_COLOR : line->color, line->font); + } + x += columnWidth; + } + y += line->lineHeight; + lineIndex += 1; } - lineIndex += 1; } } From 78d0eee2bd17a84e796c706a82cda4d2eb2ddb15 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 13 Nov 2025 15:53:56 +0100 Subject: [PATCH 47/54] NumberEdit better step when decimals displayed --- src/basenumberedit.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basenumberedit.h b/src/basenumberedit.h index ebbba0f..06d3ccf 100644 --- a/src/basenumberedit.h +++ b/src/basenumberedit.h @@ -109,6 +109,9 @@ class BaseNumberEdit: public FormField void setEditMode(bool newEditMode) override { if (editMode != newEditMode) { + if (newEditMode) { + setStep(pow(10, FLAGS_TO_DECIMALS(textFlags))); + } FormField::setEditMode(newEditMode); if (!instantChange) { if (newEditMode) { From 49b59ccc4e0c4f943a371be1d86e172dea4f9c94 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Thu, 13 Nov 2025 19:33:53 +0100 Subject: [PATCH 48/54] Table paint optimization --- src/table.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/table.cpp b/src/table.cpp index 5759819..e844ef8 100644 --- a/src/table.cpp +++ b/src/table.cpp @@ -86,9 +86,9 @@ void Table::Body::paint(BitmapBuffer * dc) } x += columnWidth; } - y += line->lineHeight; - lineIndex += 1; } + lineIndex += 1; + y += line->lineHeight; } } From 37613716ec813f7579cfe62eaa461a50069db40c Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 17 Nov 2025 14:50:58 +0100 Subject: [PATCH 49/54] Window::enableScroll() / disableScroll() not needed anymore --- src/keyboard_base.h | 4 ++-- src/window.h | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/keyboard_base.h b/src/keyboard_base.h index 9b07554..78f3bde 100644 --- a/src/keyboard_base.h +++ b/src/keyboard_base.h @@ -67,7 +67,7 @@ class Keyboard: public KeyboardBase fieldContainerOriginalHeight = fieldContainer->height(); fieldContainer->setHeight(fieldContainer->height() - height()); fieldContainer->scrollTo(newField); - fieldContainer->disableScroll(); + // fieldContainer->disableScroll(); invalidate(); field = newField; } @@ -78,7 +78,7 @@ class Keyboard: public KeyboardBase detach(); if (fieldContainer) { fieldContainer->setHeight(fieldContainerOriginalHeight); - fieldContainer->enableScroll(); + // fieldContainer->enableScroll(); fieldContainer = nullptr; } if (field) { diff --git a/src/window.h b/src/window.h index 398dbbc..dfb6c7c 100644 --- a/src/window.h +++ b/src/window.h @@ -371,15 +371,15 @@ class Window return scrollPositionY; } - void enableScroll(bool value = true) - { - scrollEnabled = value; - } + // void enableScroll(bool value = true) + // { + // scrollEnabled = value; + // } - void disableScroll() - { - enableScroll(false); - } + // void disableScroll() + // { + // enableScroll(false); + // } virtual void setScrollPositionX(coord_t value); @@ -472,7 +472,7 @@ class Window bool _deleted = false; uint8_t refCount = 0; - bool scrollEnabled = true; + // bool scrollEnabled = true; static Window * focusWindow; static Window * slidingWindow; From a8044dfc1287ebad56aea72809bddb2717b6eb53 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 17 Nov 2025 14:51:07 +0100 Subject: [PATCH 50/54] Window::enableScroll() / disableScroll() not needed anymore --- src/window.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/window.cpp b/src/window.cpp index 345a61a..263bb2f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -482,9 +482,9 @@ bool Window::onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, } } - if (!scrollEnabled) { - return false; - } + // if (!scrollEnabled) { + // return false; + // } if (slidingWindow && slidingWindow != this) { return false; From 49373a63589844f7ed5d0afff62b9003de81a6b8 Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 17 Nov 2025 14:51:26 +0100 Subject: [PATCH 51/54] FormField::isEditMode() needs to be virtual --- src/form.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/form.h b/src/form.h index 74210e9..2278e89 100644 --- a/src/form.h +++ b/src/form.h @@ -54,7 +54,7 @@ class FormField: public Window return next; } - inline bool isEditMode() const + virtual bool isEditMode() const { return editMode; } From 06fafc8acc4fcdbd22839223b2ae324aaf58949f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 17 Nov 2025 19:28:09 +0100 Subject: [PATCH 52/54] FormField::isEditMode() needs to be virtual --- src/bufferedwindow.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bufferedwindow.h b/src/bufferedwindow.h index 60b2833..405022f 100644 --- a/src/bufferedwindow.h +++ b/src/bufferedwindow.h @@ -28,6 +28,7 @@ class BufferedWindow: public T { public: using T::T; + using T::invalidate; ~BufferedWindow() override { From 859c7493382801f8c67f8fa0995e051e0463655d Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Sat, 22 Nov 2025 01:20:12 +0100 Subject: [PATCH 53/54] Menu customLine width given in the paint handler --- src/menu.cpp | 4 ++-- src/menu.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/menu.cpp b/src/menu.cpp index 4ee6063..b2f566a 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -144,7 +144,7 @@ void MenuBody::paint(BitmapBuffer * dc) } } if (line.drawLine) { - line.drawLine(dc, 0, i * MENUS_LINE_HEIGHT, color); + line.drawLine(dc, 0, i * MENUS_LINE_HEIGHT, width(), color); } else { const char * text = line.text.data(); @@ -258,7 +258,7 @@ void Menu::addLine(const std::string & text, const Mask * mask, std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addCustomLine(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); updatePosition(); diff --git a/src/menu.h b/src/menu.h index 3816992..1b2d0cb 100644 --- a/src/menu.h +++ b/src/menu.h @@ -53,7 +53,7 @@ class MenuBody: public Window { } - MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): drawLine(std::move(drawLine)), onPress(std::move(onPress)), onSelect(std::move(onSelect)), @@ -68,7 +68,7 @@ class MenuBody: public Window protected: std::string text; const Mask * icon; - std::function drawLine; + std::function drawLine; std::function onPress; std::function onSelect; std::function isChecked; @@ -117,7 +117,7 @@ class MenuBody: public Window invalidate(); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { lines.emplace_back(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); invalidate(); @@ -245,7 +245,7 @@ class Menu: public ModalWindow addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void removeLines(); From 976e8e1a020aecf13f1f41665099304b2ed01d4f Mon Sep 17 00:00:00 2001 From: Bertrand Songis Date: Mon, 24 Nov 2025 09:18:51 +0100 Subject: [PATCH 54/54] [Slider] Add a default value --- src/slider.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/slider.h b/src/slider.h index 297775e..1cc5888 100644 --- a/src/slider.h +++ b/src/slider.h @@ -60,6 +60,26 @@ class Slider: public FormField invalidate(); } + void setDefault(int value) + { + vdefault = value; + } + + int getMin() const + { + return vmin; + } + + int getMax() const + { + return vmax; + } + + int getDefault() const + { + return vdefault; + } + void paint(BitmapBuffer * dc) override; #if defined(HARDWARE_KEYS) @@ -78,6 +98,7 @@ class Slider: public FormField int value(coord_t x) const; int vmin; int vmax; + int vdefault; bool sliding = false; std::function getValue; std::function _setValue;