"""Execute actual color boundaries, indexed palettes and night emoji rendering.""" from pathlib import Path import os import subprocess import tempfile import unittest from cpp_source import body ROOT = Path(__file__).resolve().parents[1] UI = ROOT / 'src/helpers/ui' PREFIX = r''' #include #include #include #include #include static bool night = false; namespace mesh { namespace ui { bool displayNightActive() { return night; } } } ColorVal UIColor::window_bkg=0x0885, UIColor::title_bkg=0x1299, UIColor::title_txt=0xffff; ColorVal UIColor::primary_txt=0xe73c, UIColor::secondary_txt=0x8d59, UIColor::warning_txt=0xfd20; ColorVal UIColor::popup_bkg=0x1299, UIColor::popup_txt=0xffff, UIColor::corp_blue=0x5dbf; struct Canvas { uint32_t fill=0, text=0; std::vector marks; void fillScreen(uint32_t c) { fill=c; } void setTextColor(uint32_t c) { text=c; } void setBaseColor(uint32_t) {} void setTextSize(int) {} void setTextWrap(bool) {} void setFreeFont() {} void cp437(bool) {} void fillRect(int,int,int,int,uint32_t c) { marks.push_back(c); } }; ''' class DisplayNightRenderingTests(unittest.TestCase): def run_cpp(self, source, *defines): with tempfile.TemporaryDirectory(prefix='meshcore-night-render-') as directory: executable = Path(directory) / 'render' compiled = subprocess.run([ os.environ.get('CXX', 'c++'), '-std=c++11', '-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter', '-Wno-unused-function', '-Wno-unused-variable', '-DDISPLAY_CLASS=LGFXDisplay', *('-D' + value for value in defines), '-I', str(ROOT / 'src'), '-x', 'c++', '-', '-o', str(executable), ], input=source, text=True, capture_output=True) self.assertEqual(compiled.returncode, 0, compiled.stdout + compiled.stderr) ran = subprocess.run([str(executable)], text=True, capture_output=True) self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr) def test_all_rgb565_values_and_variant_semantic_backgrounds(self): self.run_cpp(PREFIX + r''' int main() { using namespace mesh::ui::color_theme; for (unsigned c=0;c<65536;++c) assert(render(c)==c && background(c)==c); night=true; for (unsigned c=0;c<65536;++c) { const bool b=c==0 || c==0x0885 || c==0x1299 || c==WINDOW_BACKGROUND || c==TITLE_BACKGROUND || c==POPUP_BACKGROUND; assert(render(c)==(b ? NIGHT_BACKGROUND : NIGHT_FOREGROUND)); assert(background(c)==NIGHT_BACKGROUND); } assert(render(TOUCH_OUTLINE)==0xf800 && render(TOUCH_PRESSED)==0xf800); assert(UIColor::window_bkg==0x0885 && UIColor::primary_txt==0xe73c); night=false; assert(render(UIColor::window_bkg)==0x0885 && render(UIColor::primary_txt)==0xe73c); } ''') def test_actual_direct_and_buffered_color_boundaries(self): for driver in ('ST7735Display', 'ST7789LCDDisplay', 'NV3001BDisplay'): with self.subTest(driver=driver): source = (UI / (driver + '.cpp')).read_text() if driver == 'ST7735Display': harness = r''' static Canvas canvas; static Canvas* sprite=&canvas; static ColorVal curr_color; bool spriteReady() { return true; } struct ST7735Display { void startFrame(ColorVal); void setColor(ColorVal); void fillRect(int,int,int,int); }; ''' inspect = 'canvas' elif driver == 'ST7789LCDDisplay': harness = r''' struct ST7789LCDDisplay { Canvas display, canvas; Canvas* _canvas=&canvas; bool buffered=false, _isOn=true, _frame_active=false; ColorVal _color=0; bool isBufferedRendering() const { return buffered; } int width() const { return 8; } int height() const { return 8; } void serviceRender(bool) {} void finishCanvasPrimitive(int,int,int,int) {} void startFrame(ColorVal); void setColor(ColorVal); void fillRect(int,int,int,int); }; ''' inspect = '(d.buffered ? d.canvas : d.display)' else: harness = r''' static const int NV3001B_SCREEN_WIDTH=8, NV3001B_SCREEN_HEIGHT=8; struct NV3001BDisplay { Canvas canvas; ColorVal color=0; int text_size=1,cursor_x=0,cursor_y=0; void fillPhysicalRect(int x,int y,int w,int h) { canvas.fillRect(x,y,w,h,color); if (w==8 && h==8) canvas.fill=color; } void startFrame(ColorVal); void setColor(ColorVal); void fillRect(int,int,int,int); }; ''' inspect = 'd.canvas' harness += '\n'.join(body(source, f'void {driver}::{name}(') for name in ('startFrame', 'setColor', 'fillRect')) harness += '\nint main() { ' + driver + ' d;\n' if driver == 'ST7789LCDDisplay': harness += 'for (bool buffered : {false,true}) { d.buffered=buffered;\n' harness += 'auto& c=' + inspect + ';\n' harness += r''' night=false; d.startFrame(0x3456); assert(c.fill==0x3456); d.setColor(0x07e0); d.fillRect(1,1,1,1); assert(c.marks.back()==0x07e0); night=true; d.startFrame(0x3456); assert(c.fill==0); d.setColor(UIColor::primary_txt); d.fillRect(1,1,1,1); assert(c.marks.back()==0xf800); d.setColor(0xffe0); d.fillRect(1,1,1,1); assert(c.marks.back()==0xf800); d.setColor(UIColor::popup_bkg); d.fillRect(1,1,1,1); assert(c.marks.back()==0); night=false; d.startFrame(0x3456); assert(c.fill==0x3456); ''' if driver == 'ST7789LCDDisplay': harness += '}\n' harness += '}\n' self.run_cpp(PREFIX + harness, 'LILYGO_TDECK=1') def test_actual_lgfx_indexed_and_truecolor_emoji_paths(self): source = (UI / 'LGFXDisplay.cpp').read_text() palette = body(source, 'static const uint32_t UI_PALETTE[16]') + ';\n' harness = r''' namespace lgfx { namespace v1 { struct LGFXBase {}; } } struct Sprite : Canvas, lgfx::v1::LGFXBase { uint32_t palette[16]={}; void setPaletteColor(unsigned i,uint32_t c) { palette[i]=c; } template void fillRect(int,int,int,int,T c) { // LovyanGFX treats signed32 as RGB565, unsigned32 as RGB888. #if UI_BUFFER_COLOR_DEPTH < 8 marks.push_back(c); #else marks.push_back(std::is_signed::value ? mesh::ui::color_theme::rgb888(static_cast(c)) : c); #endif } }; struct Atlas { const uint8_t* glyph(uint16_t) const { static uint8_t p[]={255,0xe0,0x1c,3}; return p; } int width() const { return 2; } int height() const { return 2; } uint8_t transparent() const { return 255; } }; struct LGFXDisplay { Canvas panel; Canvas* display=&panel; Sprite buffer; Atlas _emojiAtlas; struct EmojiOverlay { uint16_t codepoint; int16_t x,y; uint16_t size; } _emojiOverlays[2]; static const unsigned MAX_EMOJI_OVERLAYS=2; unsigned _emojiOverlayCount=0,_presentedEmojiOverlayCount=0; bool _hasTransparentEmojiPixels=false,_nightFrame=false,_hasLastFrame=true; uint32_t _color=0; bool wasEmojiPresented(const EmojiOverlay&) { return false; } void drawEmojiMask(int,int,int,const uint8_t*) { assert(false); } void drawEmojiUnderlay(int,int,int,const uint8_t*) {} void startFrame(ColorVal); void setColor(ColorVal); void applyNightPalette(); uint32_t renderColor(ColorVal) const; int32_t queueEmoji(lgfx::v1::LGFXBase*,int32_t,int32_t,uint32_t,int32_t); }; ''' harness += '\n'.join(body(source, signature) for signature in ( 'void LGFXDisplay::startFrame(', 'void LGFXDisplay::setColor(', 'void LGFXDisplay::applyNightPalette(', 'uint32_t LGFXDisplay::renderColor(', 'int32_t LGFXDisplay::queueEmoji(')) harness += r''' int main() { LGFXDisplay d; night=true; d.startFrame(0xffff); assert(!d._hasLastFrame && d.panel.fill==0 && d.buffer.fill==0); #if UI_BUFFER_COLOR_DEPTH < 8 assert(d.buffer.palette[0]==0 && d.buffer.palette[1]==0xff0000); for (unsigned i=1;i<16;++i) assert(d.buffer.palette[i]==0xff0000); const uint32_t red=1; const uint32_t physical_red=1; #else const uint32_t red=0xf800; const uint32_t physical_red=0xff0000; #endif assert(d.buffer.text==red && d.renderColor(0x07e0)==red); assert(d.queueEmoji(&d.buffer,0,0,0x1234,4)==4); assert(d._emojiOverlayCount==0 && !d._hasTransparentEmojiPixels); assert(d.buffer.marks.size()==3); for (uint32_t color : d.buffer.marks) assert(color==physical_red); night=false; d.startFrame(0x3456); assert(!d._hasLastFrame && d.panel.fill==UIColor::window_bkg); #if UI_BUFFER_COLOR_DEPTH < 8 assert(d.buffer.palette[0]==UI_PALETTE[0] && d.buffer.palette[1]==UI_PALETTE[1]); for (unsigned i=0;i<16;++i) assert(d.buffer.palette[i]==UI_PALETTE[i]); #else assert(d.buffer.fill==0x3456); #endif assert(d.queueEmoji(&d.buffer,0,0,0x1234,4)==4 && d._emojiOverlayCount==1); } ''' for depth in (4, 8, 16): with self.subTest(depth=depth): self.run_cpp(PREFIX + palette + harness, f'UI_BUFFER_COLOR_DEPTH={depth}') def test_color_capability_excludes_monochrome_eink_and_null(self): colors = ('ST7735Display', 'ST7789Display', 'ST7789LCDDisplay', 'NV3001BDisplay', 'LGFXDisplay', 'SCIndicatorDisplay', 'WioTrackerL2Display') others = ('SSD1306Display', 'SH1106Display', 'SH1107Display', 'U8g2Display', 'GxEPDDisplay', 'E213Display', 'E290Display', 'NullDisplayDriver') for driver in colors + others: with self.subTest(driver=driver): code = '#define DISPLAY_CLASS ' + driver + '\n#include \n' code += '#ifdef MESHCORE_HAS_COLOR_DISPLAY\nconstexpr bool color=true;\n#else\nconstexpr bool color=false;\n#endif\n' code += 'static_assert(color == ' + ('true' if driver in colors else 'false') + ', "capability");\n' compiled = subprocess.run(['c++', '-std=c++11', '-fsyntax-only', '-I', str(ROOT / 'src'), '-x', 'c++', '-'], input=code, text=True, capture_output=True) self.assertEqual(compiled.returncode, 0, compiled.stderr) def test_actual_qr_paths_keep_red_quiet_zone_and_black_modules_at_night(self): qr_common = r''' struct QRCode { uint8_t size=21; }; const int ECC_LOW=0; int qrcode_initText(QRCode* qr,uint8_t*,uint8_t,int,const char*) { qr->size=21;return 0; } bool qrcode_getModule(const QRCode*,unsigned col,unsigned row) { return col==row || col==0; } int lgfx_qrcode_initText(QRCode* q,uint8_t* b,uint8_t v,int e,const char* t) { return qrcode_initText(q,b,v,e,t); } bool lgfx_qrcode_getModule(const QRCode* q,unsigned c,unsigned r) { return qrcode_getModule(q,c,r); } ''' source = (UI / 'DisplayDriver.cpp').read_text() generic = r''' struct QrDisplay { Canvas canvas; uint32_t color=0; int width() const { return 128; } int height() const { return 128; } bool isEink() const { return false; } void setColor(ColorVal c) { color=mesh::ui::color_theme::render(c); } void fillRect(int x,int y,int w,int h) { canvas.fillRect(x,y,w,h,color); } bool drawQrCodeWithBorder(const char*,int,int,int,int); }; ''' generic += body(source, 'bool DisplayDriver::drawQrCodeWithBorder(').replace('DisplayDriver::', 'QrDisplay::') generic += r''' int main() { for (bool active : {false,true}) { night=active; QrDisplay d; assert(d.drawQrCodeWithBorder("X",0,0,64,-1)); assert(d.canvas.marks.size()==42); assert(d.canvas.marks[0]==(night ? 0xf800 : UIColor::popup_txt)); for (unsigned i=1;i