Skip to content

File display.h

File List > disp > display.h

Go to the documentation of this file

Source Code

#pragma once
#ifndef DSY_DISPLAY_H
#define DSY_DISPLAY_H 
#include <cmath>
#include "util/oled_fonts.h"
#include "daisy_core.h"
#include "graphics_common.h"

#ifndef deg2rad
#define deg2rad(deg) ((deg)*3.141592 / 180.0)
#endif

namespace daisy
{
class OneBitGraphicsDisplay
{
  public:
    OneBitGraphicsDisplay() {}
    virtual ~OneBitGraphicsDisplay() {}

    virtual uint16_t Height() const = 0;
    virtual uint16_t Width() const  = 0;

    Rectangle GetBounds() const
    {
        return Rectangle(int16_t(Width()), int16_t(Height()));
    }


    size_t CurrentX() { return currentX_; };
    size_t CurrentY() { return currentY_; };

    virtual void Fill(bool on) = 0;

    virtual void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on) = 0;

    virtual void DrawLine(uint_fast8_t x1,
                          uint_fast8_t y1,
                          uint_fast8_t x2,
                          uint_fast8_t y2,
                          bool         on)
        = 0;

    virtual void DrawRect(uint_fast8_t x1,
                          uint_fast8_t y1,
                          uint_fast8_t x2,
                          uint_fast8_t y2,
                          bool         on,
                          bool         fill = false)
        = 0;

    void DrawRect(const Rectangle& rect, bool on, bool fill = false)
    {
        DrawRect(rect.GetX(),
                 rect.GetY(),
                 rect.GetRight(),
                 rect.GetBottom(),
                 on,
                 fill);
    }

    virtual void DrawArc(uint_fast8_t x,
                         uint_fast8_t y,
                         uint_fast8_t radius,
                         int_fast16_t start_angle,
                         int_fast16_t sweep,
                         bool         on)
        = 0;

    void
    DrawCircle(uint_fast8_t x, uint_fast8_t y, uint_fast8_t radius, bool on)
    {
        DrawArc(x, y, radius, 0, 360, on);
    };

    virtual char WriteChar(char ch, FontDef font, bool on) = 0;

    virtual char WriteString(const char* str, FontDef font, bool on) = 0;

    virtual Rectangle WriteStringAligned(const char*    str,
                                         const FontDef& font,
                                         Rectangle      boundingBox,
                                         Alignment      alignment,
                                         bool           on)
        = 0;

    void SetCursor(uint16_t x, uint16_t y)
    {
        currentX_ = (x >= Width()) ? Width() - 1 : x;
        currentY_ = (y >= Height()) ? Height() - 1 : y;
    }

    virtual void Update() = 0;

    virtual bool UpdateFinished() = 0;

  protected:
    uint16_t currentX_;
    uint16_t currentY_;
};

template <class ChildType>
class OneBitGraphicsDisplayImpl : public OneBitGraphicsDisplay
{
  public:
    OneBitGraphicsDisplayImpl() {}
    virtual ~OneBitGraphicsDisplayImpl() {}

    void DrawLine(uint_fast8_t x1,
                  uint_fast8_t y1,
                  uint_fast8_t x2,
                  uint_fast8_t y2,
                  bool         on) override
    {
        int_fast16_t deltaX = abs((int_fast16_t)x2 - (int_fast16_t)x1);
        int_fast16_t deltaY = abs((int_fast16_t)y2 - (int_fast16_t)y1);
        int_fast16_t signX  = ((x1 < x2) ? 1 : -1);
        int_fast16_t signY  = ((y1 < y2) ? 1 : -1);
        int_fast16_t error  = deltaX - deltaY;
        int_fast16_t error2;

        // If we write "ChildType::DrawPixel(x2, y2, on);", we end up with
        // all sorts of weird compiler errors when the Child class is a template
        // class. The only way around this is to use this very verbose syntax:
        ((ChildType*)(this))->ChildType::DrawPixel(x2, y2, on);

        while((x1 != x2) || (y1 != y2))
        {
            ((ChildType*)(this))->ChildType::DrawPixel(x1, y1, on);
            error2 = error * 2;
            if(error2 > -deltaY)
            {
                error -= deltaY;
                x1 += signX;
            }

            if(error2 < deltaX)
            {
                error += deltaX;
                y1 += signY;
            }
        }
    }

    void DrawRect(uint_fast8_t x1,
                  uint_fast8_t y1,
                  uint_fast8_t x2,
                  uint_fast8_t y2,
                  bool         on,
                  bool         fill = false) override
    {
        if(fill)
        {
            for(uint_fast8_t x = x1; x <= x2; x++)
            {
                for(uint_fast8_t y = y1; y <= y2; y++)
                {
                    ((ChildType*)(this))->ChildType::DrawPixel(x, y, on);
                }
            }
        }
        else
        {
            ((ChildType*)(this))->ChildType::DrawLine(x1, y1, x2, y1, on);
            ((ChildType*)(this))->ChildType::DrawLine(x2, y1, x2, y2, on);
            ((ChildType*)(this))->ChildType::DrawLine(x2, y2, x1, y2, on);
            ((ChildType*)(this))->ChildType::DrawLine(x1, y2, x1, y1, on);
        }
    }

    void DrawArc(uint_fast8_t x,
                 uint_fast8_t y,
                 uint_fast8_t radius,
                 int_fast16_t start_angle,
                 int_fast16_t sweep,
                 bool         on) override
    {
        // Values to calculate the circle
        int_fast16_t t_x, t_y, err, e2;

        // Temporary values to speed up comparisons
        float t_sxy, t_syx, t_sxny, t_synx;
        float t_exy, t_eyx, t_exny, t_eynx;

        float start_angle_rad, end_angle_rad;
        float start_x, start_y, end_x, end_y;

        bool d1, d2, d3, d4;

        d1 = d2 = d3 = d4 = true;

        bool circle = false;

        if(sweep < 0)
        {
            start_angle += sweep;
            sweep = -sweep;
        }

        start_angle_rad = deg2rad(start_angle);
        end_angle_rad   = deg2rad(start_angle + sweep);

        start_x = cos(start_angle_rad) * radius;
        start_y = -sin(start_angle_rad) * radius;
        end_x   = cos(end_angle_rad) * radius;
        end_y   = -sin(end_angle_rad) * radius;

        // Check if start and endpoint are very near
        if((end_x - start_x) * (end_x - start_x)
               + (end_y - start_y) * (end_y - start_y)
           < 2.0f)
        {
            if(sweep > 180)
                circle = true;
            else
                // Nothing to draw
                return;
        }

        t_x = -radius;
        t_y = 0;
        err = 2 - 2 * radius;

        do
        {
            if(!circle)
            {
                t_sxy  = start_x * t_y;
                t_syx  = start_y * t_x;
                t_sxny = start_x * -t_y;
                t_synx = start_y * -t_x;
                t_exy  = end_x * t_y;
                t_eyx  = end_y * t_x;
                t_exny = end_x * -t_y;
                t_eynx = end_y * -t_x;

                if(sweep > 180)
                {
                    d1 = (t_sxy - t_synx < 0 || t_exy - t_eynx > 0);
                    d2 = (t_sxy - t_syx < 0 || t_exy - t_eyx > 0);
                    d3 = (t_sxny - t_syx < 0 || t_exny - t_eyx > 0);
                    d4 = (t_sxny - t_synx < 0 || t_exny - t_eynx > 0);
                }
                else
                {
                    d1 = (t_sxy - t_synx < 0 && t_exy - t_eynx > 0);
                    d2 = (t_sxy - t_syx < 0 && t_exy - t_eyx > 0);
                    d3 = (t_sxny - t_syx < 0 && t_exny - t_eyx > 0);
                    d4 = (t_sxny - t_synx < 0 && t_exny - t_eynx > 0);
                }
            }

            if(d1)
                ((ChildType*)(this))
                    ->ChildType::DrawPixel(x - t_x, y + t_y, on);
            if(d2)
                ((ChildType*)(this))
                    ->ChildType::DrawPixel(x + t_x, y + t_y, on);
            if(d3)
                ((ChildType*)(this))
                    ->ChildType::DrawPixel(x + t_x, y - t_y, on);
            if(d4)
                ((ChildType*)(this))
                    ->ChildType::DrawPixel(x - t_x, y - t_y, on);

            e2 = err;
            if(e2 <= t_y)
            {
                t_y++;
                err = err + (t_y * 2 + 1);
                if(-t_x == t_y && e2 <= t_x)
                {
                    e2 = 0;
                }
            }
            if(e2 > t_x)
            {
                t_x++;
                err = err + (t_x * 2 + 1);
            }
        } while(t_x <= 0);
    }

    char WriteChar(char ch, FontDef font, bool on) override
    {
        uint32_t i, b, j;

        // Check if character is valid
        if(ch < 32 || ch > 126)
            return 0;

        // Check remaining space on current line
        if(Width() < (currentX_ + font.FontWidth)
           || Height() < (currentY_ + font.FontHeight))
        {
            // Not enough space on current line
            return 0;
        }

        // Use the font to write
        for(i = 0; i < font.FontHeight; i++)
        {
            b = font.data[(ch - 32) * font.FontHeight + i];
            for(j = 0; j < font.FontWidth; j++)
            {
                if((b << j) & 0x8000)
                {
                    ((ChildType*)(this))
                        ->ChildType::DrawPixel(
                            currentX_ + j, (currentY_ + i), on);
                }
                else
                {
                    ((ChildType*)(this))
                        ->ChildType::DrawPixel(
                            currentX_ + j, (currentY_ + i), !on);
                }
            }
        }

        // The current space is now taken
        SetCursor(currentX_ + font.FontWidth, currentY_);

        // Return written char for validation
        return ch;
    }

    char WriteString(const char* str, FontDef font, bool on) override
    {
        // Write until null-byte
        while(*str)
        {
            if(((ChildType*)(this))->ChildType::WriteChar(*str, font, on)
               != *str)
            {
                // Char could not be written
                return *str;
            }

            // Next char
            str++;
        }

        // Everything ok
        return *str;
    }

    Rectangle WriteStringAligned(const char*    str,
                                 const FontDef& font,
                                 Rectangle      boundingBox,
                                 Alignment      alignment,
                                 bool           on) override
    {
        const auto alignedRect
            = GetTextRect(str, font).AlignedWithin(boundingBox, alignment);
        SetCursor(alignedRect.GetX(), alignedRect.GetY());
        ((ChildType*)(this))->ChildType::WriteString(str, font, on);
        return alignedRect;
    }

  private:
    uint32_t strlen(const char* string)
    {
        uint32_t result = 0;
        while(*string++ != '\0')
            result++;
        return result;
    }

    Rectangle GetTextRect(const char* text, const FontDef& font)
    {
        const auto numChars = strlen(text);
        return {int16_t(numChars * font.FontWidth), font.FontHeight};
    }
};

} // namespace daisy

#endif