@@ -0,0 +1,293 @@
|
||||
#include <vamp_pico/display.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <vamp_style/font.hpp>
|
||||
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto pin_lcd_cs = std::uint32_t{9};
|
||||
constexpr auto pin_lcd_sclk = std::uint32_t{10};
|
||||
constexpr auto pin_lcd_mosi = std::uint32_t{11};
|
||||
constexpr auto pin_lcd_miso = std::uint32_t{12};
|
||||
constexpr auto pin_lcd_rst = std::uint32_t{13};
|
||||
constexpr auto pin_lcd_dc = std::uint32_t{14};
|
||||
constexpr auto pin_lcd_bl = std::uint32_t{15};
|
||||
|
||||
auto select() -> void {
|
||||
gpio_put(pin_lcd_cs, 0);
|
||||
}
|
||||
|
||||
auto deselect() -> void {
|
||||
gpio_put(pin_lcd_cs, 1);
|
||||
}
|
||||
|
||||
auto command(std::uchar_t value) -> void {
|
||||
gpio_put(pin_lcd_dc, 0);
|
||||
select();
|
||||
spi_write_blocking(spi1, &value, 1);
|
||||
deselect();
|
||||
}
|
||||
|
||||
auto data(std::uchar_t const* values, std::size_t count) -> void {
|
||||
gpio_put(pin_lcd_dc, 1);
|
||||
select();
|
||||
spi_write_blocking(spi1, values, count);
|
||||
deselect();
|
||||
}
|
||||
|
||||
auto data8(std::uchar_t value) -> void {
|
||||
data(&value, 1);
|
||||
}
|
||||
|
||||
auto set_window(std::int32_t x0, std::int32_t y0, std::int32_t x1, std::int32_t y1) -> void {
|
||||
x0 = std::clamp(x0, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1});
|
||||
y0 = std::clamp(y0, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1});
|
||||
x1 = std::clamp(x1, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1});
|
||||
y1 = std::clamp(y1, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1});
|
||||
|
||||
command(0x2a);
|
||||
auto column = std::array<std::uchar_t, 4>{
|
||||
static_cast<std::uchar_t>((x0 >> 8) & 0xff),
|
||||
static_cast<std::uchar_t>(x0 & 0xff),
|
||||
static_cast<std::uchar_t>((x1 >> 8) & 0xff),
|
||||
static_cast<std::uchar_t>(x1 & 0xff),
|
||||
};
|
||||
data(column.data(), column.size());
|
||||
|
||||
command(0x2b);
|
||||
auto row = std::array<std::uchar_t, 4>{
|
||||
static_cast<std::uchar_t>((y0 >> 8) & 0xff),
|
||||
static_cast<std::uchar_t>(y0 & 0xff),
|
||||
static_cast<std::uchar_t>((y1 >> 8) & 0xff),
|
||||
static_cast<std::uchar_t>(y1 & 0xff),
|
||||
};
|
||||
data(row.data(), row.size());
|
||||
command(0x2c);
|
||||
}
|
||||
|
||||
auto same_color(vamp_style::color left, vamp_style::color right) -> std::bool_t {
|
||||
return left.r == right.r && left.g == right.g && left.b == right.b && left.a == right.a;
|
||||
}
|
||||
|
||||
template <std::size_t Count>
|
||||
auto same_text(std::array<std::char_t, Count> const& cached, std::char_t const* text) -> std::bool_t {
|
||||
auto const* value = text == nullptr ? "" : text;
|
||||
return std::strncmp(cached.data(), value, Count) == 0;
|
||||
}
|
||||
|
||||
template <std::size_t Count>
|
||||
auto copy_text(std::array<std::char_t, Count>& cached, std::char_t const* text) -> void {
|
||||
auto const* value = text == nullptr ? "" : text;
|
||||
auto count = std::min(Count - 1U, std::strlen(value));
|
||||
std::memcpy(cached.data(), value, count);
|
||||
cached[count] = '\0';
|
||||
}
|
||||
|
||||
auto blend_channel(std::uint8_t foreground, std::uint8_t background, std::uint8_t alpha) -> std::uint8_t {
|
||||
auto fg = static_cast<std::uint32_t>(foreground) * alpha;
|
||||
auto bg = static_cast<std::uint32_t>(background) * (15U - alpha);
|
||||
return static_cast<std::uint8_t>((fg + bg + 7U) / 15U);
|
||||
}
|
||||
|
||||
auto blend(vamp_style::color foreground, vamp_style::color background, std::uint8_t alpha) -> vamp_style::color {
|
||||
if (alpha >= 15U) {
|
||||
return foreground;
|
||||
}
|
||||
return {
|
||||
.r = blend_channel(foreground.r, background.r, alpha),
|
||||
.g = blend_channel(foreground.g, background.g, alpha),
|
||||
.b = blend_channel(foreground.b, background.b, alpha),
|
||||
.a = 255,
|
||||
};
|
||||
}
|
||||
|
||||
auto draw_centered_text(vamp_pico::display& display, std::int32_t y, std::char_t const* text, std::uint16_t color, std::int32_t preferred_scale, std::int32_t max_width) -> void {
|
||||
if (text == nullptr || text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
auto scale = preferred_scale;
|
||||
while (scale > 1 && vamp_style::ui_text_width(text, scale) > max_width) {
|
||||
--scale;
|
||||
}
|
||||
auto width = vamp_style::ui_text_width(text, scale);
|
||||
auto x = std::max<std::int32_t>(12, (vamp_pico::display_width - width) / 2);
|
||||
display.draw_text(x, y, text, color, scale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto vamp_pico::display::init() -> void {
|
||||
spi_init(spi1, 62'500'000);
|
||||
gpio_set_function(pin_lcd_sclk, GPIO_FUNC_SPI);
|
||||
gpio_set_function(pin_lcd_mosi, GPIO_FUNC_SPI);
|
||||
gpio_set_function(pin_lcd_miso, GPIO_FUNC_SPI);
|
||||
gpio_init(pin_lcd_cs);
|
||||
gpio_init(pin_lcd_dc);
|
||||
gpio_init(pin_lcd_rst);
|
||||
gpio_init(pin_lcd_bl);
|
||||
gpio_set_dir(pin_lcd_cs, GPIO_OUT);
|
||||
gpio_set_dir(pin_lcd_dc, GPIO_OUT);
|
||||
gpio_set_dir(pin_lcd_rst, GPIO_OUT);
|
||||
gpio_set_dir(pin_lcd_bl, GPIO_OUT);
|
||||
deselect();
|
||||
gpio_put(pin_lcd_bl, 1);
|
||||
|
||||
gpio_put(pin_lcd_rst, 0);
|
||||
sleep_ms(50);
|
||||
gpio_put(pin_lcd_rst, 1);
|
||||
sleep_ms(120);
|
||||
|
||||
command(0x01);
|
||||
sleep_ms(150);
|
||||
command(0x11);
|
||||
sleep_ms(120);
|
||||
command(0x3a);
|
||||
data8(0x55);
|
||||
command(0x36);
|
||||
data8(0x60);
|
||||
command(0x21);
|
||||
command(0x13);
|
||||
command(0x29);
|
||||
sleep_ms(20);
|
||||
fill(vamp_pico::color_black);
|
||||
remote_valid_ = false;
|
||||
}
|
||||
|
||||
auto vamp_pico::display::fill(std::uint16_t color) -> void {
|
||||
fill_rect(0, 0, vamp_pico::display_width, vamp_pico::display_height, color);
|
||||
}
|
||||
|
||||
auto vamp_pico::display::fill_rect(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint16_t color) -> void {
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
auto x0 = std::clamp(x, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1});
|
||||
auto y0 = std::clamp(y, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1});
|
||||
auto x1 = std::clamp(x + width - std::int32_t{1}, std::int32_t{0}, vamp_pico::display_width - std::int32_t{1});
|
||||
auto y1 = std::clamp(y + height - std::int32_t{1}, std::int32_t{0}, vamp_pico::display_height - std::int32_t{1});
|
||||
if (x1 < x0 || y1 < y0) {
|
||||
return;
|
||||
}
|
||||
set_window(x0, y0, x1, y1);
|
||||
auto hi = static_cast<std::uchar_t>((color >> 8) & 0xff);
|
||||
auto lo = static_cast<std::uchar_t>(color & 0xff);
|
||||
auto row = std::array<std::uchar_t, vamp_pico::display_width * 2>{};
|
||||
for (auto i = std::size_t{}; i < row.size(); i += 2U) {
|
||||
row[i] = hi;
|
||||
row[i + 1U] = lo;
|
||||
}
|
||||
gpio_put(pin_lcd_dc, 1);
|
||||
select();
|
||||
for (auto yy = y0; yy <= y1; ++yy) {
|
||||
spi_write_blocking(spi1, row.data(), static_cast<std::size_t>(x1 - x0 + 1) * 2U);
|
||||
}
|
||||
deselect();
|
||||
}
|
||||
|
||||
auto vamp_pico::display::draw_text(std::int32_t x, std::int32_t y, std::char_t const* text, std::uint16_t color, std::int32_t scale) -> void {
|
||||
if (text == nullptr || scale <= 0) {
|
||||
return;
|
||||
}
|
||||
auto foreground = vamp_style::color{
|
||||
.r = static_cast<std::uint8_t>(((color >> 11U) & 0x1fU) * 255U / 31U),
|
||||
.g = static_cast<std::uint8_t>(((color >> 5U) & 0x3fU) * 255U / 63U),
|
||||
.b = static_cast<std::uint8_t>((color & 0x1fU) * 255U / 31U),
|
||||
.a = 255,
|
||||
};
|
||||
auto cursor = x;
|
||||
auto previous = std::char_t{};
|
||||
for (auto i = std::size_t{}; i < std::strlen(text); ++i) {
|
||||
if (previous != std::char_t{}) {
|
||||
cursor += vamp_style::ui_font_pair_adjust(previous, text[i]) * scale;
|
||||
}
|
||||
auto glyph = vamp_style::ui_font_glyph(text[i]);
|
||||
for (auto row = 0; row < vamp_style::ui_font_height; ++row) {
|
||||
auto col = std::int32_t{};
|
||||
while (col < static_cast<std::int32_t>(glyph.width)) {
|
||||
auto row_offset = static_cast<std::size_t>(row * vamp_style::ui_font_max_width);
|
||||
while (col < static_cast<std::int32_t>(glyph.width) && glyph.alpha[row_offset + static_cast<std::size_t>(col)] == 0U) {
|
||||
++col;
|
||||
}
|
||||
auto start = col;
|
||||
auto alpha = start < static_cast<std::int32_t>(glyph.width)
|
||||
? glyph.alpha[row_offset + static_cast<std::size_t>(start)]
|
||||
: std::uint8_t{};
|
||||
while (col < static_cast<std::int32_t>(glyph.width) && glyph.alpha[row_offset + static_cast<std::size_t>(col)] == alpha) {
|
||||
++col;
|
||||
}
|
||||
if (col > start) {
|
||||
auto run_color = vamp_style::rgb565(blend(foreground, vamp_style::background, alpha));
|
||||
fill_rect(cursor + start * scale, y + row * scale, (col - start) * scale, scale, run_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor += static_cast<std::int32_t>(glyph.advance) * scale;
|
||||
if (cursor >= vamp_pico::display_width) {
|
||||
return;
|
||||
}
|
||||
previous = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
auto vamp_pico::display::show_boot(std::char_t const* line1, std::char_t const* line2, std::uint16_t accent) -> void {
|
||||
remote_valid_ = false;
|
||||
fill(vamp_style::rgb565(vamp_style::background));
|
||||
fill_rect(0, 0, vamp_pico::display_width, 28, accent);
|
||||
draw_text(14, 48, "VAMP", vamp_style::rgb565(vamp_style::ink), 2);
|
||||
if (line1 != nullptr) {
|
||||
draw_text(92, 54, line1, vamp_style::rgb565(vamp_style::ink), 1);
|
||||
}
|
||||
if (line2 != nullptr) {
|
||||
draw_text(14, 96, line2, vamp_style::rgb565(vamp_style::muted), 1);
|
||||
}
|
||||
}
|
||||
|
||||
auto vamp_pico::display::show_remote(vamp_style::color status, std::char_t const* title, std::char_t const* part, std::uint32_t page, std::uint32_t pages, std::bool_t active) -> void {
|
||||
auto const* title_text = title == nullptr ? "" : title;
|
||||
auto const* part_text = part == nullptr ? "" : part;
|
||||
auto status_changed = !remote_valid_ || !same_color(last_status_, status);
|
||||
auto content_changed = !remote_valid_
|
||||
|| last_active_ != active
|
||||
|| !same_text(last_title_, title_text)
|
||||
|| !same_text(last_part_, part_text)
|
||||
|| last_page_ != page
|
||||
|| last_pages_ != pages;
|
||||
|
||||
if (!remote_valid_) {
|
||||
fill(vamp_style::rgb565(vamp_style::background));
|
||||
}
|
||||
if (status_changed) {
|
||||
fill_rect(0, 0, vamp_pico::display_width, 24, vamp_style::rgb565(status));
|
||||
}
|
||||
if (content_changed) {
|
||||
fill_rect(0, 24, vamp_pico::display_width, vamp_pico::display_height - 24, vamp_style::rgb565(vamp_style::background));
|
||||
if (active && title_text[0] != '\0') {
|
||||
draw_centered_text(*this, 66, title_text, vamp_style::rgb565(vamp_style::ink), 1, vamp_pico::display_width - 24);
|
||||
draw_centered_text(*this, 118, part_text, vamp_style::rgb565(vamp_style::muted), 1, vamp_pico::display_width - 24);
|
||||
if (pages != 0U) {
|
||||
auto page_text = std::array<std::char_t, 32>{};
|
||||
auto written = std::snprintf(page_text.data(), page_text.size(), "PAGE %lu/%lu", static_cast<unsigned long>(page), static_cast<unsigned long>(pages));
|
||||
if (written > 0) {
|
||||
draw_centered_text(*this, 168, page_text.data(), vamp_style::rgb565(vamp_style::muted), 1, vamp_pico::display_width - 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_status_ = status;
|
||||
last_active_ = active;
|
||||
copy_text(last_title_, title_text);
|
||||
copy_text(last_part_, part_text);
|
||||
last_page_ = page;
|
||||
last_pages_ = pages;
|
||||
remote_valid_ = true;
|
||||
}
|
||||
Reference in New Issue
Block a user