mirror of
https://github.com/esphome/esphome.git
synced 2024-12-02 11:44:13 +01:00
6682c43dfa
## Description: Move esphome-core codebase into esphome (and a bunch of other refactors). See https://github.com/esphome/feature-requests/issues/97 Yes this is a shit ton of work and no there's no way to automate it :( But it will be worth it 👍 Progress: - Core support (file copy etc): 80% - Base Abstractions (light, switch): ~50% - Integrations: ~10% - Working? Yes, (but only with ported components). Other refactors: - Moves all codegen related stuff into a single class: `esphome.codegen` (imported as `cg`) - Rework coroutine syntax - Move from `component/platform.py` to `domain/component.py` structure as with HA - Move all defaults out of C++ and into config validation. - Remove `make_...` helpers from Application class. Reason: Merge conflicts with every single new integration. - Pointer Variables are stored globally instead of locally in setup(). Reason: stack size limit. Future work: - Rework const.py - Move all `CONF_...` into a conf class (usage `conf.UPDATE_INTERVAL` vs `CONF_UPDATE_INTERVAL`). Reason: Less convoluted import block - Enable loading from `custom_components` folder. **Related issue (if applicable):** https://github.com/esphome/feature-requests/issues/97 **Pull request in [esphome-docs](https://github.com/esphome/esphome-docs) with documentation (if applicable):** esphome/esphome-docs#<esphome-docs PR number goes here> ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphomedocs](https://github.com/OttoWinter/esphomedocs).
449 lines
15 KiB
C++
449 lines
15 KiB
C++
#include "display_buffer.h"
|
|
#include "esphome/core/log.h"
|
|
#include "esphome/core/application.h"
|
|
|
|
namespace esphome {
|
|
namespace display {
|
|
|
|
static const char *TAG = "display";
|
|
|
|
const uint8_t COLOR_OFF = 0;
|
|
const uint8_t COLOR_ON = 1;
|
|
|
|
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
|
this->buffer_ = new uint8_t[buffer_length];
|
|
if (this->buffer_ == nullptr) {
|
|
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
|
return;
|
|
}
|
|
this->clear();
|
|
}
|
|
void DisplayBuffer::fill(int color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
|
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
|
int DisplayBuffer::get_width() {
|
|
switch (this->rotation_) {
|
|
case DISPLAY_ROTATION_90_DEGREES:
|
|
case DISPLAY_ROTATION_270_DEGREES:
|
|
return this->get_height_internal();
|
|
case DISPLAY_ROTATION_0_DEGREES:
|
|
case DISPLAY_ROTATION_180_DEGREES:
|
|
default:
|
|
return this->get_width_internal();
|
|
}
|
|
}
|
|
int DisplayBuffer::get_height() {
|
|
switch (this->rotation_) {
|
|
case DISPLAY_ROTATION_0_DEGREES:
|
|
case DISPLAY_ROTATION_180_DEGREES:
|
|
return this->get_height_internal();
|
|
case DISPLAY_ROTATION_90_DEGREES:
|
|
case DISPLAY_ROTATION_270_DEGREES:
|
|
default:
|
|
return this->get_width_internal();
|
|
}
|
|
}
|
|
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
|
void HOT DisplayBuffer::draw_pixel_at(int x, int y, int color) {
|
|
switch (this->rotation_) {
|
|
case DISPLAY_ROTATION_0_DEGREES:
|
|
break;
|
|
case DISPLAY_ROTATION_90_DEGREES:
|
|
std::swap(x, y);
|
|
x = this->get_width_internal() - x - 1;
|
|
break;
|
|
case DISPLAY_ROTATION_180_DEGREES:
|
|
x = this->get_width_internal() - x - 1;
|
|
y = this->get_height_internal() - y - 1;
|
|
break;
|
|
case DISPLAY_ROTATION_270_DEGREES:
|
|
std::swap(x, y);
|
|
y = this->get_height_internal() - y - 1;
|
|
break;
|
|
}
|
|
this->draw_absolute_pixel_internal(x, y, color);
|
|
App.feed_wdt();
|
|
}
|
|
void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, int color) {
|
|
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
|
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
|
int32_t err = dx + dy;
|
|
|
|
while (true) {
|
|
this->draw_pixel_at(x1, y1, color);
|
|
if (x1 == x2 && y1 == y2)
|
|
break;
|
|
int32_t e2 = 2 * err;
|
|
if (e2 >= dy) {
|
|
err += dy;
|
|
x1 += sx;
|
|
}
|
|
if (e2 <= dx) {
|
|
err += dx;
|
|
y1 += sy;
|
|
}
|
|
}
|
|
}
|
|
void HOT DisplayBuffer::horizontal_line(int x, int y, int width, int color) {
|
|
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
|
for (int i = x; i < x + width; i++)
|
|
this->draw_pixel_at(i, y, color);
|
|
}
|
|
void HOT DisplayBuffer::vertical_line(int x, int y, int height, int color) {
|
|
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
|
for (int i = y; i < y + height; i++)
|
|
this->draw_pixel_at(x, i, color);
|
|
}
|
|
void DisplayBuffer::rectangle(int x1, int y1, int width, int height, int color) {
|
|
this->horizontal_line(x1, y1, width, color);
|
|
this->horizontal_line(x1, y1 + height - 1, width, color);
|
|
this->vertical_line(x1, y1, height, color);
|
|
this->vertical_line(x1 + width - 1, y1, height, color);
|
|
}
|
|
void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, int color) {
|
|
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
|
|
for (int i = y1; i < y1 + height; i++) {
|
|
this->horizontal_line(x1, i, width, color);
|
|
}
|
|
}
|
|
void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, int color) {
|
|
int dx = -radius;
|
|
int dy = 0;
|
|
int err = 2 - 2 * radius;
|
|
int e2;
|
|
|
|
do {
|
|
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
|
|
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
|
|
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
|
|
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
|
|
e2 = err;
|
|
if (e2 < dy) {
|
|
err += ++dy * 2 + 1;
|
|
if (-dx == dy && e2 <= dx) {
|
|
e2 = 0;
|
|
}
|
|
}
|
|
if (e2 > dx) {
|
|
err += ++dx * 2 + 1;
|
|
}
|
|
} while (dx <= 0);
|
|
}
|
|
void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, int color) {
|
|
int dx = -int32_t(radius);
|
|
int dy = 0;
|
|
int err = 2 - 2 * radius;
|
|
int e2;
|
|
|
|
do {
|
|
this->draw_pixel_at(center_x - dx, center_y + dy, color);
|
|
this->draw_pixel_at(center_x + dx, center_y + dy, color);
|
|
this->draw_pixel_at(center_x + dx, center_y - dy, color);
|
|
this->draw_pixel_at(center_x - dx, center_y - dy, color);
|
|
int hline_width = 2 * (-dx) + 1;
|
|
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
|
|
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
|
|
e2 = err;
|
|
if (e2 < dy) {
|
|
err += ++dy * 2 + 1;
|
|
if (-dx == dy && e2 <= dx) {
|
|
e2 = 0;
|
|
}
|
|
}
|
|
if (e2 > dx) {
|
|
err += ++dx * 2 + 1;
|
|
}
|
|
} while (dx <= 0);
|
|
}
|
|
|
|
void DisplayBuffer::print(int x, int y, Font *font, int color, TextAlign align, const char *text) {
|
|
int x_start, y_start;
|
|
int width, height;
|
|
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
|
|
|
int i = 0;
|
|
int x_at = x_start;
|
|
while (text[i] != '\0') {
|
|
int match_length;
|
|
int glyph_n = font->match_next_glyph(text + i, &match_length);
|
|
if (glyph_n < 0) {
|
|
// Unknown char, skip
|
|
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
|
|
if (!font->get_glyphs().empty()) {
|
|
uint8_t glyph_width = font->get_glyphs()[0].width_;
|
|
for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++)
|
|
for (int glyph_y = 0; glyph_y < height; glyph_y++)
|
|
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
|
x_at += glyph_width;
|
|
}
|
|
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
const Glyph &glyph = font->get_glyphs()[glyph_n];
|
|
int scan_x1, scan_y1, scan_width, scan_height;
|
|
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
|
|
|
|
for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) {
|
|
for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) {
|
|
if (glyph.get_pixel(glyph_x, glyph_y)) {
|
|
this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
x_at += glyph.width_ + glyph.offset_x_;
|
|
|
|
i += match_length;
|
|
}
|
|
}
|
|
void DisplayBuffer::vprintf_(int x, int y, Font *font, int color, TextAlign align, const char *format, va_list arg) {
|
|
char buffer[256];
|
|
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
|
if (ret > 0)
|
|
this->print(x, y, font, color, align, buffer);
|
|
}
|
|
void DisplayBuffer::image(int x, int y, Image *image) {
|
|
for (int img_x = 0; img_x < image->get_width(); img_x++) {
|
|
for (int img_y = 0; img_y < image->get_height(); img_y++) {
|
|
this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_ON : COLOR_OFF);
|
|
}
|
|
}
|
|
}
|
|
void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
|
|
int *width, int *height) {
|
|
int x_offset, baseline;
|
|
font->measure(text, width, &x_offset, &baseline, height);
|
|
|
|
auto x_align = TextAlign(int(align) & 0x18);
|
|
auto y_align = TextAlign(int(align) & 0x07);
|
|
|
|
switch (x_align) {
|
|
case TextAlign::RIGHT:
|
|
*x1 = x - *width;
|
|
break;
|
|
case TextAlign::CENTER_HORIZONTAL:
|
|
*x1 = x - (*width) / 2;
|
|
break;
|
|
case TextAlign::LEFT:
|
|
default:
|
|
// LEFT
|
|
*x1 = x;
|
|
break;
|
|
}
|
|
|
|
switch (y_align) {
|
|
case TextAlign::BOTTOM:
|
|
*y1 = y - *height;
|
|
break;
|
|
case TextAlign::BASELINE:
|
|
*y1 = y - baseline;
|
|
break;
|
|
case TextAlign::CENTER_VERTICAL:
|
|
*y1 = y - (*height) / 2;
|
|
break;
|
|
case TextAlign::TOP:
|
|
default:
|
|
*y1 = y;
|
|
break;
|
|
}
|
|
}
|
|
void DisplayBuffer::print(int x, int y, Font *font, int color, const char *text) {
|
|
this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
|
|
}
|
|
void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
|
|
this->print(x, y, font, COLOR_ON, align, text);
|
|
}
|
|
void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
|
|
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
|
}
|
|
void DisplayBuffer::printf(int x, int y, Font *font, int color, TextAlign align, const char *format, ...) {
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
this->vprintf_(x, y, font, color, align, format, arg);
|
|
va_end(arg);
|
|
}
|
|
void DisplayBuffer::printf(int x, int y, Font *font, int color, const char *format, ...) {
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
|
|
va_end(arg);
|
|
}
|
|
void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char *format, ...) {
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
|
|
va_end(arg);
|
|
}
|
|
void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
|
|
va_list arg;
|
|
va_start(arg, format);
|
|
this->vprintf_(x, y, font, COLOR_ON, TextAlign::CENTER_LEFT, format, arg);
|
|
va_end(arg);
|
|
}
|
|
void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
|
|
void DisplayBuffer::set_pages(std::vector<DisplayPage *> pages) {
|
|
for (auto *page : pages)
|
|
page->set_parent(this);
|
|
|
|
for (uint32_t i = 0; i < pages.size() - 1; i++) {
|
|
pages[i]->set_next(pages[i + 1]);
|
|
pages[i + 1]->set_prev(pages[i]);
|
|
}
|
|
pages[0]->set_prev(pages[pages.size() - 1]);
|
|
pages[pages.size() - 1]->set_next(pages[0]);
|
|
this->show_page(pages[0]);
|
|
}
|
|
void DisplayBuffer::show_page(DisplayPage *page) { this->page_ = page; }
|
|
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
|
|
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
|
|
void DisplayBuffer::do_update_() {
|
|
this->clear();
|
|
if (this->page_ != nullptr) {
|
|
this->page_->get_writer()(*this);
|
|
} else if (this->writer_.has_value()) {
|
|
(*this->writer_)(*this);
|
|
}
|
|
}
|
|
#ifdef USE_TIME
|
|
void DisplayBuffer::strftime(int x, int y, Font *font, int color, TextAlign align, const char *format,
|
|
time::ESPTime time) {
|
|
char buffer[64];
|
|
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
|
if (ret > 0)
|
|
this->print(x, y, font, color, align, buffer);
|
|
}
|
|
void DisplayBuffer::strftime(int x, int y, Font *font, int color, const char *format, time::ESPTime time) {
|
|
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
|
|
}
|
|
void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
|
|
this->strftime(x, y, font, COLOR_ON, align, format, time);
|
|
}
|
|
void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) {
|
|
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
|
|
}
|
|
#endif
|
|
|
|
Glyph::Glyph(const char *a_char, const uint8_t *data_start, uint32_t offset, int offset_x, int offset_y, int width,
|
|
int height)
|
|
: char_(a_char),
|
|
data_(data_start + offset),
|
|
offset_x_(offset_x),
|
|
offset_y_(offset_y),
|
|
width_(width),
|
|
height_(height) {}
|
|
bool Glyph::get_pixel(int x, int y) const {
|
|
const int x_data = x - this->offset_x_;
|
|
const int y_data = y - this->offset_y_;
|
|
if (x_data < 0 || x_data >= this->width_ || y_data < 0 || y_data >= this->height_)
|
|
return false;
|
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
|
const uint32_t pos = x_data + y_data * width_8;
|
|
return pgm_read_byte(this->data_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
|
}
|
|
const char *Glyph::get_char() const { return this->char_; }
|
|
bool Glyph::compare_to(const char *str) const {
|
|
// 1 -> this->char_
|
|
// 2 -> str
|
|
for (uint32_t i = 0;; i++) {
|
|
if (this->char_[i] == '\0')
|
|
return true;
|
|
if (str[i] == '\0')
|
|
return false;
|
|
if (this->char_[i] > str[i])
|
|
return false;
|
|
if (this->char_[i] < str[i])
|
|
return true;
|
|
}
|
|
// this should not happen
|
|
return false;
|
|
}
|
|
int Glyph::match_length(const char *str) const {
|
|
for (uint32_t i = 0;; i++) {
|
|
if (this->char_[i] == '\0')
|
|
return i;
|
|
if (str[i] != this->char_[i])
|
|
return 0;
|
|
}
|
|
// this should not happen
|
|
return 0;
|
|
}
|
|
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
|
*x1 = this->offset_x_;
|
|
*y1 = this->offset_y_;
|
|
*width = this->width_;
|
|
*height = this->height_;
|
|
}
|
|
int Font::match_next_glyph(const char *str, int *match_length) {
|
|
int lo = 0;
|
|
int hi = this->glyphs_.size() - 1;
|
|
while (lo != hi) {
|
|
int mid = (lo + hi + 1) / 2;
|
|
if (this->glyphs_[mid].compare_to(str))
|
|
lo = mid;
|
|
else
|
|
hi = mid - 1;
|
|
}
|
|
*match_length = this->glyphs_[lo].match_length(str);
|
|
if (*match_length <= 0)
|
|
return -1;
|
|
return lo;
|
|
}
|
|
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
|
*baseline = this->baseline_;
|
|
*height = this->bottom_;
|
|
int i = 0;
|
|
int min_x = 0;
|
|
bool has_char = false;
|
|
int x = 0;
|
|
while (str[i] != '\0') {
|
|
int match_length;
|
|
int glyph_n = this->match_next_glyph(str + i, &match_length);
|
|
if (glyph_n < 0) {
|
|
// Unknown char, skip
|
|
if (!this->get_glyphs().empty())
|
|
x += this->get_glyphs()[0].width_;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
const Glyph &glyph = this->glyphs_[glyph_n];
|
|
if (!has_char)
|
|
min_x = glyph.offset_x_;
|
|
else
|
|
min_x = std::min(min_x, x + glyph.offset_x_);
|
|
x += glyph.width_ + glyph.offset_x_;
|
|
|
|
i += match_length;
|
|
has_char = true;
|
|
}
|
|
*x_offset = min_x;
|
|
*width = x - min_x;
|
|
}
|
|
const std::vector<Glyph> &Font::get_glyphs() const { return this->glyphs_; }
|
|
Font::Font(std::vector<Glyph> &&glyphs, int baseline, int bottom)
|
|
: glyphs_(std::move(glyphs)), baseline_(baseline), bottom_(bottom) {}
|
|
|
|
bool Image::get_pixel(int x, int y) const {
|
|
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
|
return false;
|
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
|
const uint32_t pos = x + y * width_8;
|
|
return pgm_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
|
}
|
|
int Image::get_width() const { return this->width_; }
|
|
int Image::get_height() const { return this->height_; }
|
|
Image::Image(const uint8_t *data_start, int width, int height)
|
|
: width_(width), height_(height), data_start_(data_start) {}
|
|
|
|
DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {}
|
|
void DisplayPage::show() { this->parent_->show_page(this); }
|
|
void DisplayPage::show_next() { this->next_->show(); }
|
|
void DisplayPage::show_prev() { this->prev_->show(); }
|
|
void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; }
|
|
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
|
|
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
|
|
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
|
|
|
|
} // namespace display
|
|
} // namespace esphome
|