summaryrefslogtreecommitdiff
path: root/src/components/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/gfx')
-rw-r--r--src/components/gfx/Gfx.cpp207
-rw-r--r--src/components/gfx/Gfx.h60
2 files changed, 267 insertions, 0 deletions
diff --git a/src/components/gfx/Gfx.cpp b/src/components/gfx/Gfx.cpp
new file mode 100644
index 00000000..3c5dbfb7
--- /dev/null
+++ b/src/components/gfx/Gfx.cpp
@@ -0,0 +1,207 @@
+#include <libraries/svc/nrf_svci.h>
+#include <FreeRTOS.h>
+#include <task.h>
+#include "Gfx.h"
+#include "../../drivers/St7789.h"
+using namespace Pinetime::Components;
+
+Gfx::Gfx(Pinetime::Drivers::St7789 &lcd) : lcd{lcd} {
+}
+
+void Gfx::Init() {
+
+}
+
+void Gfx::ClearScreen() {
+ SetBackgroundColor(0x0000);
+
+ state.remainingIterations = 240 + 1;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(0, 0, width, height);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
+ WaitTransfertFinished();
+
+}
+
+void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t color) {
+ SetBackgroundColor(color);
+
+ state.remainingIterations = h;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.color = color;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(x, y, w, h);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(buffer), width * 2);
+
+ WaitTransfertFinished();
+}
+
+void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b) {
+ state.remainingIterations = h;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::FillRectangle;
+ state.color = 0x00;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(x, y, w, h);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(b), width * 2);
+
+ WaitTransfertFinished();
+}
+
+void Gfx::DrawString(uint8_t x, uint8_t y, uint16_t color, const char *text, const FONT_INFO *p_font, bool wrap) {
+ if (y > (height - p_font->height)) {
+ // Not enough space to write even single char.
+ return;
+ }
+
+ uint8_t current_x = x;
+ uint8_t current_y = y;
+
+ for (size_t i = 0; text[i] != '\0'; i++) {
+ if (text[i] == '\n') {
+ current_x = x;
+ current_y += p_font->height + p_font->height / 10;
+ } else {
+ DrawChar(p_font, (uint8_t) text[i], &current_x, current_y, color);
+ }
+
+ uint8_t char_idx = text[i] - p_font->startChar;
+ uint16_t char_width = text[i] == ' ' ? (p_font->height / 2) : p_font->charInfo[char_idx].widthBits;
+
+ if (current_x > (width - char_width)) {
+ if (wrap) {
+ current_x = x;
+ current_y += p_font->height + p_font->height / 10;
+ } else {
+ break;
+ }
+
+ if (y > (height - p_font->height)) {
+ break;
+ }
+ }
+ }
+}
+
+void Gfx::DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color) {
+ uint8_t char_idx = c - font->startChar;
+ uint16_t bytes_in_line = CEIL_DIV(font->charInfo[char_idx].widthBits, 8);
+ uint16_t bg = 0x0000;
+
+ if (c == ' ') {
+ *x += font->height / 2;
+ return;
+ }
+
+ // Build first line
+ for (uint16_t j = 0; j < bytes_in_line; j++) {
+ for (uint8_t k = 0; k < 8; k++) {
+ if ((1 << (7 - k)) & font->data[font->charInfo[char_idx].offset + j]) {
+ buffer[(j*8)+k] = color;
+ }
+ else {
+ buffer[(j*8)+k] = bg;
+ }
+ }
+ }
+
+ state.remainingIterations = font->height + 0;
+ state.currentIteration = 0;
+ state.busy = true;
+ state.action = Action::DrawChar;
+ state.font = const_cast<FONT_INFO *>(font);
+ state.character = c;
+ state.color = color;
+ state.taskToNotify = xTaskGetCurrentTaskHandle();
+
+ lcd.BeginDrawBuffer(*x, y, bytes_in_line*8, font->height);
+ lcd.NextDrawBuffer(reinterpret_cast<const uint8_t *>(&buffer), bytes_in_line*8*2);
+ WaitTransfertFinished();
+
+ *x += font->charInfo[char_idx].widthBits + font->spacePixels;
+}
+
+void Gfx::pixel_draw(uint8_t x, uint8_t y, uint16_t color) {
+ lcd.DrawPixel(x, y, color);
+}
+
+void Gfx::Sleep() {
+ lcd.Sleep();
+}
+
+void Gfx::Wakeup() {
+ lcd.Wakeup();
+}
+
+void Gfx::SetBackgroundColor(uint16_t color) {
+ for(int i = 0; i < width; i++) {
+ buffer[i] = color;
+ }
+}
+
+bool Gfx::GetNextBuffer(uint8_t **data, size_t &size) {
+ if(!state.busy) return false;
+ state.remainingIterations--;
+ if (state.remainingIterations == 0) {
+ state.busy = false;
+ NotifyEndOfTransfert(state.taskToNotify);
+ return false;
+ }
+
+ if(state.action == Action::FillRectangle) {
+ *data = reinterpret_cast<uint8_t *>(buffer);
+ size = width * 2;
+ } else if(state.action == Action::DrawChar) {
+ uint16_t bg = 0x0000;
+ uint8_t char_idx = state.character - state.font->startChar;
+ uint16_t bytes_in_line = CEIL_DIV(state.font->charInfo[char_idx].widthBits, 8);
+
+ for (uint16_t j = 0; j < bytes_in_line; j++) {
+ for (uint8_t k = 0; k < 8; k++) {
+ if ((1 << (7 - k)) & state.font->data[state.font->charInfo[char_idx].offset + ((state.currentIteration+1) * bytes_in_line) + j]) {
+ buffer[(j*8)+k] = state.color;
+ }
+ else {
+ buffer[(j*8)+k] = bg;
+ }
+ }
+ }
+
+ *data = reinterpret_cast<uint8_t *>(buffer);
+ size = bytes_in_line*8*2;
+ }
+
+ state.currentIteration++;
+
+ return true;
+}
+
+void Gfx::NotifyEndOfTransfert(TaskHandle_t task) {
+ if(task != nullptr) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ vTaskNotifyGiveFromISR(task, &xHigherPriorityTaskWoken);
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ }
+}
+
+void Gfx::WaitTransfertFinished() const {
+ ulTaskNotifyTake(pdTRUE, 500);
+}
+
+void Gfx::SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines) {
+ lcd.VerticalScrollDefinition(topFixedLines, scrollLines, bottomFixedLines);
+}
+
+void Gfx::SetScrollStartLine(uint16_t line) {
+ lcd.VerticalScrollStartAddress(line);
+}
+
diff --git a/src/components/gfx/Gfx.h b/src/components/gfx/Gfx.h
new file mode 100644
index 00000000..091f06f5
--- /dev/null
+++ b/src/components/gfx/Gfx.h
@@ -0,0 +1,60 @@
+#pragma once
+#include <cstdint>
+#include <nrf_font.h>
+#include <drivers/BufferProvider.h>
+#include <FreeRTOS.h>
+#include <task.h>
+
+
+namespace Pinetime {
+ namespace Drivers {
+ class St7789;
+ }
+ namespace Components {
+ class Gfx : public Pinetime::Drivers::BufferProvider {
+ public:
+ explicit Gfx(Drivers::St7789& lcd);
+ void Init();
+ void ClearScreen();
+ void DrawString(uint8_t x, uint8_t y, uint16_t color, const char* text, const FONT_INFO *p_font, bool wrap);
+ void DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint16_t color);
+ void FillRectangle(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);
+ void FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b);
+ void SetScrollArea(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
+ void SetScrollStartLine(uint16_t line);
+
+
+ void Sleep();
+ void Wakeup();
+ bool GetNextBuffer(uint8_t **buffer, size_t &size) override;
+ void pixel_draw(uint8_t x, uint8_t y, uint16_t color);
+
+
+ private:
+ static constexpr uint8_t width = 240;
+ static constexpr uint8_t height = 240;
+
+ enum class Action { None, FillRectangle, DrawChar};
+ struct State {
+ State() : busy{false}, action{Action::None}, remainingIterations{0}, currentIteration{0} {}
+ volatile bool busy;
+ volatile Action action;
+ volatile uint16_t remainingIterations;
+ volatile uint16_t currentIteration;
+ volatile FONT_INFO *font;
+ volatile uint16_t color;
+ volatile uint8_t character;
+ volatile TaskHandle_t taskToNotify = nullptr;
+ };
+
+ volatile State state;
+
+ uint16_t buffer[width]; // 1 line buffer
+ Drivers::St7789& lcd;
+
+ void SetBackgroundColor(uint16_t color);
+ void WaitTransfertFinished() const;
+ void NotifyEndOfTransfert(TaskHandle_t task);
+ };
+ }
+}