Adafruit_SSD1306みたいなことをJUCEでやる

## SSD1306 を緩くシミュレート ##
SSD1306 (ちっさいOLED) を使ったハードウェアシンセサイザーを作ってるんだけど、コードをガリガリ書いてテストするのは完全にMacでやりたいんで、SSD1306部分をシミュレートするものが欲しかった。なのでChatGPTに書かせた。これで実機を離れてコード書ける。実機での確認はたまにやればいい感じで！

![IMG_8349.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107818/a3db7305-7848-4dfd-8329-7f3598757695.jpeg)

凄いバッチリなフォントあった！！！ありがとう素晴らしい！！！
https://fontstruct.com/fontstructions/show/2422258/adafruits-gfx-library-default-font

```SSD1306Similator.h
#pragma once

#include <JuceHeader.h>
#include <cstdarg>

class SSD1306Simulator : public juce::Component
{
public:
    SSD1306Simulator(int width, int height)
        : displayWidth(width), displayHeight(height),
          image(juce::Image::PixelFormat::RGB, displayWidth, displayHeight, true),
          graphics(image),
          typeface(juce::Typeface::createSystemTypefaceFor(BinaryData::Adafruits5x7_otf, BinaryData::Adafruits5x7_otfSize)),
          lcdFont(juce::FontOptions(typeface).withHeight(8.0f))
    {
        setTextSize(1);
        clearDisplay();
    }

    SSD1306Simulator() : SSD1306Simulator(128, 64) {}

    bool begin()
    {
        clearDisplay();
        return true;
    }

    void setTextSize(int size)
    {
        textSize = juce::jlimit(1, 4, size);
        lcdFont = juce::FontOptions(typeface).withHeight(textSize * 8.0f);
    }

    void setTextColor(juce::Colour fg, juce::Colour bg = juce::Colours::transparentBlack)
    {
        textColour = fg;
        bgColour = bg;
    }

    void setTextWrap(bool w) { textWrap = w; }

    void clearDisplay()
    {
        graphics.fillAll(inverted ? juce::Colours::white : juce::Colours::black);
        cursorX = 0;
        cursorY = 0;
    }

    void invertDisplay(bool i)
    {
        inverted = i;
        clearDisplay();
    }

    void dim(bool d)
    {
        dimmed = d;
        displayColour = dimmed ? juce::Colours::grey : juce::Colours::white;
    }

    void drawPixel(int x, int y, bool color)
    {
        if (x < 0 || x >= displayWidth || y < 0 || y >= displayHeight) return;
        graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black);
        graphics.fillRect(x, y, 1, 1);
    }

    void drawLine(int x0, int y0, int x1, int y1, bool color)
    {
        graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black);
        graphics.drawLine((float)x0, (float)y0, (float)x1, (float)y1);
    }

    void drawFastHLine(int x, int y, int w, bool color) { fillRect(x, y, w, 1, color); }
    void drawFastVLine(int x, int y, int h, bool color) { fillRect(x, y, 1, h, color); }
    void drawRect(int x, int y, int w, int h, bool color) { graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black); graphics.drawRect(x, y, w, h); }
    void fillRect(int x, int y, int w, int h, bool color) { graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black); graphics.fillRect(x, y, w, h); }
    void fillScreen(bool color) { graphics.fillAll((color ^ inverted) ? textColour : juce::Colours::black); }

    void drawCircle(int x, int y, int r, bool color) { graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black); graphics.drawEllipse(x - r, y - r, r * 2, r * 2, 1.0f); }
    void fillCircle(int x, int y, int r, bool color) { graphics.setColour((color ^ inverted) ? textColour : juce::Colours::black); graphics.fillEllipse(x - r, y - r, r * 2, r * 2); }

    void setCursor(int x, int y) { cursorX = x; cursorY = y; }

    void print(const char* text)
    {
        if (!text) return;
        juce::Graphics g(image);
        g.setFont(lcdFont);
        g.setColour(textColour);

        // `drawText()` を使って確実に描画
        g.drawText(text, cursorX, cursorY, displayWidth - cursorX, textSize * 8, juce::Justification::topLeft);

        cursorX += static_cast<int>(std::strlen(text)) * (textSize * 6);
    }

    void printf(const char* format, ...)
    {
        if (!format) return;
        va_list args;
        va_start(args, format);
        
        char buffer[256];
        int len = std::vsnprintf(buffer, sizeof(buffer), format, args);
        
        va_end(args);
        
        if (len < 0 || len >= static_cast<int>(sizeof(buffer))) {
            return; // エラー時やバッファが足りなかった場合は何もしない
        }
        
        print(buffer);
    }

    void println(const char* text) { print(text); cursorX = 0; cursorY += textSize * 8; }
    void println()
    {
        cursorX = 0;
        cursorY += textSize * 8;
    }

    void display()
    {
        repaint(); // `display()` で画面更新
    }

    void paint(juce::Graphics& g) override
    {
        g.drawImage(image, getLocalBounds().toFloat()); // `juce::Image` の内容を `Graphics` に描画
    }

private:
    int displayWidth, displayHeight;
    juce::Image image;
    juce::Graphics graphics;
    juce::Typeface::Ptr typeface;
    juce::Font lcdFont;
    juce::Colour displayColour = juce::Colours::white;
    juce::Colour textColour = juce::Colours::white;
    juce::Colour bgColour = juce::Colours::black;
    int cursorX = 0;
    int cursorY = 0;
    int textSize = 1;
    bool inverted = false;
    bool dimmed = false;
    bool textWrap = true;
};
```