#include "LcdDisplay.h" namespace { // Classic 5x8 dot-matrix LCD character generator ROM (HD44780-style), // rows top-to-bottom, bit 4 = leftmost column. Index = ASCII - 32. constexpr uint8_t kFont[95][8] = { {0,0,0,0,0,0,0,0}, // space {4,4,4,4,4,0,4,0}, // ! {10,10,10,0,0,0,0,0}, // " {0,10,31,10,31,10,0,0}, // # {4,15,20,14,5,30,4,0}, // $ {3,3,8,4,2,1,24,24}, // % {8,20,20,8,21,18,13,0}, // & {4,4,4,0,0,0,0,0}, // ' {2,4,8,8,8,4,2,0}, // ( {8,4,2,2,2,4,8,0}, // ) {0,10,4,31,4,10,0,0}, // * {0,4,4,31,4,4,0,0}, // + {0,0,0,0,6,2,4,0}, // , {0,0,0,31,0,0,0,0}, // - {0,0,0,0,0,0,12,0}, // . {1,2,4,8,16,0,0,0}, // / {14,17,19,21,25,17,14,0}, // 0 {4,12,4,4,4,4,14,0}, // 1 {14,17,1,2,4,8,31,0}, // 2 {14,17,1,6,1,17,14,0}, // 3 {2,6,10,18,31,2,2,0}, // 4 {31,16,30,1,1,17,14,0}, // 5 {6,8,16,30,17,17,14,0}, // 6 {31,1,2,4,8,8,8,0}, // 7 {14,17,17,14,17,17,14,0}, // 8 {14,17,17,15,1,2,12,0}, // 9 {0,0,6,0,0,6,0,0}, // : {0,0,6,0,0,6,2,4}, // ; {2,4,8,16,8,4,2,0}, // < {0,0,31,0,31,0,0,0}, // = {8,4,2,1,2,4,8,0}, // > {14,17,1,2,4,0,4,0}, // ? {14,17,1,13,21,17,14,0}, // @ {14,17,17,31,17,17,17,0}, // A {30,17,17,30,17,17,30,0}, // B {14,17,16,16,16,17,14,0}, // C {30,17,17,17,17,17,30,0}, // D {31,16,16,30,16,16,31,0}, // E {31,16,16,30,16,16,16,0}, // F {14,17,16,23,17,17,15,0}, // G {17,17,17,31,17,17,17,0}, // H {14,4,4,4,4,4,14,0}, // I {7,2,2,2,2,18,12,0}, // J {17,18,20,24,20,18,17,0}, // K {16,16,16,16,16,16,31,0}, // L {17,27,21,21,17,17,17,0}, // M {17,25,21,19,17,17,17,0}, // N {14,17,17,17,17,17,14,0}, // O {30,17,17,30,16,16,16,0}, // P {14,17,17,17,21,18,13,0}, // Q {30,17,17,30,20,18,17,0}, // R {15,16,16,14,1,1,30,0}, // S {31,4,4,4,4,4,4,0}, // T {17,17,17,17,17,17,14,0}, // U {17,17,17,17,17,10,4,0}, // V {17,17,17,21,21,27,17,0}, // W {17,17,10,4,10,17,17,0}, // X {17,17,17,10,4,4,4,0}, // Y {31,1,2,4,8,16,31,0}, // Z {14,8,8,8,8,8,14,0}, // [ {16,8,4,2,1,0,0,0}, // backslash {14,2,2,2,2,2,14,0}, // ] {4,10,17,0,0,0,0,0}, // ^ {0,0,0,0,0,0,31,0}, // _ {8,4,2,0,0,0,0,0}, // ` {0,0,14,1,15,17,15,0}, // a {16,16,30,17,17,17,30,0}, // b {0,0,14,16,16,17,14,0}, // c {1,1,15,17,17,17,15,0}, // d {0,0,14,17,31,16,14,0}, // e {2,4,4,14,4,4,4,0}, // f {0,0,15,17,17,15,1,14}, // g {16,16,30,17,17,17,17,0}, // h {0,6,0,4,4,4,4,0}, // i {0,6,0,4,4,4,8,0}, // j {0,0,16,18,20,24,20,0}, // k {0,4,4,4,4,4,4,14}, // l {0,0,26,21,21,21,21,0}, // m {0,0,18,29,17,17,17,0}, // n {0,0,14,17,17,17,14,0}, // o {0,30,17,17,30,16,16,0}, // p {0,0,15,17,17,15,1,1}, // q {0,0,30,17,16,16,16,0}, // r {0,0,15,16,14,1,30,0}, // s {0,4,4,14,4,4,2,0}, // t {0,0,17,17,17,19,13,0}, // u {0,0,17,17,17,10,4,0}, // v {0,0,17,17,21,21,10,0}, // w {0,0,17,10,4,10,17,0}, // x {0,0,17,17,17,10,12,0}, // y {0,0,31,2,4,8,31,0}, // z {4,8,8,16,8,8,4,0}, // { {4,4,4,4,4,4,4,0}, // | {8,4,4,2,4,4,8,0}, // } {13,18,0,0,0,0,0,0}, // ~ }; } // namespace LcdDisplay::LcdDisplay() { clear(); startTimerHz (30); } void LcdDisplay::setLine (int row, const juce::String& s) { if (row >= 0 && row < kRows) lines[row] = s; } void LcdDisplay::setInverted (int row, bool inverted) { if (row >= 0 && row < kRows) rowInverted[row] = inverted; } void LcdDisplay::showCursor (bool show) { cursorVisible = show; } void LcdDisplay::clear() { for (int r = 0; r < kRows; ++r) { lines[r] = ""; rowInverted[r] = false; } cursorCol = 0; cursorRow = 0; cursorVisible = false; } const uint8_t* LcdDisplay::getGlyph (juce::uint32 c) const { if (c < 32 || c > 126) c = '?'; return kFont[c - 32]; } void LcdDisplay::paint (juce::Graphics& g) { const float w = (float) getWidth(); const float h = (float) getHeight(); const juce::Colour bgCol (0xFF000000); const juce::Colour pixOn (0xFFE2E2E2); const juce::Colour pixOnDim (0xFF303030); const juce::Colour pixOff (0xFF0A0A0A); const juce::Colour pixInvert (0xFF8A8A8A); g.fillAll (bgCol); float cw = w / (float) kCols; float ch = h / (float) kRows; float ox = 0.0f; float oy = 0.0f; float padX = cw * 0.18f; float padY = ch * 0.10f; float gw = cw - padX * 2.0f; float gh = ch - padY * 2.0f; constexpr float kAspect = 2.0f; constexpr float kGapCol = 0.22f; constexpr float kGapRow = 0.12f; const float gridW = 5.0f + 4.0f * kGapCol; const float gridH = kAspect * (8.0f + 7.0f * kGapRow); const float cellW = juce::jmax (0.0f, juce::jmin (gw / gridW, gh / gridH)); const float cellH = cellW * kAspect; const float stepX = cellW * (1.0f + kGapCol); const float stepY = cellH * (1.0f + kGapRow); bool blinkOn = cursorVisible && ((int) (juce::Time::getMillisecondCounterHiRes() / 400.0) % 2 == 0); for (int row = 0; row < kRows; ++row) { const bool inverted = rowInverted[row]; const juce::String& line = lines[row]; for (int col = 0; col < kCols; ++col) { juce_wchar ch2 = col < line.length() ? line [(size_t) col] : (juce_wchar) ' '; const uint8_t* glyph = getGlyph ((juce::uint32) ch2); float cx = ox + (float) col * cw; float cy = oy + (float) row * ch; g.setColour (inverted ? pixInvert : pixOff); g.fillRect (cx + 0.5f, cy + 0.5f, cw - 1.0f, ch - 1.0f); float gridX = cx + padX + (gw - cellW * gridW) * 0.5f; float gridY = cy + padY + (gh - cellW * gridH) * 0.5f; for (int gRow = 0; gRow < 8; ++gRow) { uint8_t bits = glyph[gRow]; if (bits == 0) continue; for (int gCol = 0; gCol < 5; ++gCol) { if (bits & (1u << (4 - gCol))) { float px = gridX + (float) gCol * stepX; float py = gridY + (float) gRow * stepY; if (inverted) { g.setColour (pixOff); g.fillRect (px, py, cellW, cellH); } else { g.setColour (pixOnDim); g.fillRect (px - cellW * 0.10f, py - cellH * 0.05f, cellW * 1.20f, cellH * 1.10f); g.setColour (pixOn); g.fillRect (px, py, cellW, cellH); } } } } if (blinkOn && ! inverted && col == cursorCol && row == cursorRow) { g.setColour (pixOn); g.fillRect (cx + padX, cy + ch - padY - ch * 0.22f, cw - padX * 2.0f, ch * 0.18f); } } } } void LcdDisplay::timerCallback() { repaint(); } void LcdDisplay::mouseDown (const juce::MouseEvent& e) { (void)e; // Clicking disabled - navigate with joystick keys only. }