File 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