2026-09-11 16:38:28 +02:00
|
|
|
#include "LcdDisplay.h"
|
|
|
|
|
|
|
|
|
|
LcdDisplay::LcdDisplay()
|
|
|
|
|
{
|
|
|
|
|
clear();
|
|
|
|
|
startTimerHz (30);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LcdDisplay::rasterize (juce::uint32 c)
|
|
|
|
|
{
|
|
|
|
|
if (c >= 256 || glyphCache[c].cached) return;
|
|
|
|
|
|
2026-09-11 17:06:23 +02:00
|
|
|
juce::Image img (juce::Image::ARGB, kRasterW, kRasterH, true);
|
2026-09-11 16:38:28 +02:00
|
|
|
{
|
|
|
|
|
juce::Graphics g (img);
|
|
|
|
|
g.setColour (juce::Colours::white);
|
2026-09-11 17:06:23 +02:00
|
|
|
g.setFont (juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(),
|
|
|
|
|
14.0f, juce::Font::plain }));
|
2026-09-11 16:38:28 +02:00
|
|
|
juce::String s;
|
|
|
|
|
s << (juce_wchar) c;
|
2026-09-11 17:06:23 +02:00
|
|
|
g.drawText (s, img.getBounds().removeFromBottom (kRasterH - 2),
|
2026-09-11 16:38:28 +02:00
|
|
|
juce::Justification::centredBottom, false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-11 17:06:23 +02:00
|
|
|
const float sx = (float) kRasterW / 5.0f;
|
|
|
|
|
const float sy = (float) kRasterH / 8.0f;
|
2026-09-11 16:38:28 +02:00
|
|
|
|
|
|
|
|
for (int row = 0; row < 8; ++row)
|
|
|
|
|
glyphCache[c].rows[row] = 0;
|
|
|
|
|
|
|
|
|
|
for (int row = 0; row < 8; ++row)
|
|
|
|
|
{
|
|
|
|
|
uint8_t bits = 0;
|
|
|
|
|
for (int col = 0; col < 5; ++col)
|
|
|
|
|
{
|
|
|
|
|
int count = 0, total = 0;
|
|
|
|
|
int px0 = (int) (col * sx);
|
|
|
|
|
int py0 = (int) (row * sy);
|
2026-09-11 17:06:23 +02:00
|
|
|
int px1 = juce::jmin ((int) ((col + 1) * sx) - 1, kRasterW - 1);
|
|
|
|
|
int py1 = juce::jmin ((int) ((row + 1) * sy) - 1, kRasterH - 1);
|
2026-09-11 16:38:28 +02:00
|
|
|
for (int y = py0; y <= py1; ++y)
|
|
|
|
|
for (int x = px0; x <= px1; ++x)
|
|
|
|
|
{
|
|
|
|
|
total++;
|
|
|
|
|
if (img.getPixelAt (x, y).getAlpha() > 127)
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
if (total > 0 && count > total / 3)
|
|
|
|
|
bits |= (uint8_t) (1u << (4 - col));
|
|
|
|
|
}
|
|
|
|
|
glyphCache[c].rows[row] = bits;
|
|
|
|
|
}
|
|
|
|
|
glyphCache[c].cached = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t* LcdDisplay::getGlyph (juce::uint32 c)
|
|
|
|
|
{
|
|
|
|
|
if (c >= 256) c = '?';
|
|
|
|
|
if (! glyphCache[c].cached)
|
|
|
|
|
rasterize (c);
|
|
|
|
|
return glyphCache[c].rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LcdDisplay::setLine (int row, const juce::String& s)
|
|
|
|
|
{
|
|
|
|
|
if (row >= 0 && row < kRows)
|
|
|
|
|
lines[row] = s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LcdDisplay::showCursor (bool show) { cursorVisible = show; }
|
|
|
|
|
|
|
|
|
|
void LcdDisplay::clear()
|
|
|
|
|
{
|
|
|
|
|
for (int r = 0; r < kRows; ++r)
|
|
|
|
|
lines[r] = "";
|
|
|
|
|
cursorCol = 0;
|
|
|
|
|
cursorRow = 0;
|
|
|
|
|
cursorVisible = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void LcdDisplay::paint (juce::Graphics& g)
|
|
|
|
|
{
|
|
|
|
|
const float w = (float) getWidth();
|
|
|
|
|
const float h = (float) getHeight();
|
|
|
|
|
|
|
|
|
|
const juce::Colour bgCol (0xFF0D1A05);
|
|
|
|
|
const juce::Colour pixOn (0xFF4AE038);
|
|
|
|
|
const juce::Colour pixOnDim (0xFF1E3A12);
|
|
|
|
|
const juce::Colour pixOff (0xFF0F1C07);
|
|
|
|
|
|
|
|
|
|
g.fillAll (bgCol);
|
|
|
|
|
|
2026-09-11 17:06:23 +02:00
|
|
|
float cw = w / (float) kCols;
|
|
|
|
|
float ch = h / (float) kRows;
|
|
|
|
|
float ox = 0.0f;
|
|
|
|
|
float oy = 0.0f;
|
2026-09-11 16:38:28 +02:00
|
|
|
|
|
|
|
|
bool blinkOn = cursorVisible &&
|
|
|
|
|
((int) (juce::Time::getMillisecondCounterHiRes() / 400.0) % 2 == 0);
|
|
|
|
|
|
|
|
|
|
for (int row = 0; row < kRows; ++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) ' ';
|
|
|
|
|
uint8_t* glyph = getGlyph ((juce::uint32) ch2);
|
|
|
|
|
|
|
|
|
|
float cx = ox + (float) col * cw;
|
|
|
|
|
float cy = oy + (float) row * ch;
|
|
|
|
|
|
|
|
|
|
g.setColour (pixOff);
|
|
|
|
|
g.fillRect (cx + 0.5f, cy + 0.5f, cw - 1.0f, ch - 1.0f);
|
|
|
|
|
|
2026-09-11 17:06:23 +02:00
|
|
|
float padX = cw * 0.12f;
|
|
|
|
|
float padY = ch * 0.1f;
|
|
|
|
|
float gw = cw - padX * 2.0f;
|
|
|
|
|
float gh = ch - padY * 2.0f;
|
|
|
|
|
|
2026-09-11 16:38:28 +02:00
|
|
|
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)))
|
|
|
|
|
{
|
2026-09-11 17:06:23 +02:00
|
|
|
float px = cx + padX + (float) gCol * gw / 5.0f;
|
|
|
|
|
float py = cy + padY + (float) gRow * gh / 8.0f;
|
|
|
|
|
float pw = gw / 5.0f - 0.5f;
|
|
|
|
|
float ph = gh / 8.0f - 0.4f;
|
2026-09-11 16:38:28 +02:00
|
|
|
|
|
|
|
|
g.setColour (pixOnDim);
|
2026-09-11 17:06:23 +02:00
|
|
|
g.fillRect (px - 0.6f, py - 0.4f, pw + 1.2f, ph + 0.8f);
|
2026-09-11 16:38:28 +02:00
|
|
|
g.setColour (pixOn);
|
|
|
|
|
g.fillRect (px, py, pw, ph);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (blinkOn && col == cursorCol && row == cursorRow)
|
|
|
|
|
{
|
|
|
|
|
g.setColour (pixOn);
|
2026-09-11 17:06:23 +02:00
|
|
|
g.fillRect (cx + padX, cy + ch - padY - ch * 0.22f, cw - padX * 2.0f, ch * 0.18f);
|
2026-09-11 16:38:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-11 17:06:23 +02:00
|
|
|
void LcdDisplay::timerCallback() { repaint(); }
|