Mystify Demo, TwoEleven updates & Snake (#21)
* **New Features** * Added Mystify screensaver demo with animated polygons and trails * Added Snake game with multiple difficulties, high-score persistence, and multi-input (touch/keyboard) support * Added CLI tool for building, packaging, and deploying apps (end-to-end build/install/run workflow) * Per-grid-size high-score persistence added to 2048 app; expanded keyboard controls (WASD and device-specific mappings) * **Documentation** * Added Snake README with gameplay, controls, and usage instructions
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
|
||||
|
||||
idf_component_register(
|
||||
SRC_DIRS "Source"
|
||||
# Library headers must be included directly,
|
||||
# because all regular dependencies get stripped by elf_loader's cmake script
|
||||
INCLUDE_DIRS "Include" "../../../Libraries/TactilityCpp/Include"
|
||||
REQUIRES TactilitySDK
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "drivers/DisplayDriver.h"
|
||||
#include "drivers/TouchDriver.h"
|
||||
|
||||
void runApplication(DisplayDriver* display, TouchDriver* touch);
|
||||
@@ -0,0 +1,317 @@
|
||||
#pragma once
|
||||
|
||||
#include "PixelBuffer.h"
|
||||
#include "drivers/DisplayDriver.h"
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <esp_random.h>
|
||||
|
||||
/**
|
||||
* Mystify Screensaver Demo
|
||||
*
|
||||
* Classic Windows-style mystify screensaver with bouncing polygons and trailing edges.
|
||||
* Adapted to work with DisplayDriver and PixelBuffer abstractions.
|
||||
*
|
||||
* Usage:
|
||||
* MystifyDemo mystify;
|
||||
* mystify.init(display);
|
||||
*
|
||||
* while (!shouldExit) {
|
||||
* mystify.update();
|
||||
* }
|
||||
*/
|
||||
class MystifyDemo {
|
||||
public:
|
||||
static constexpr int NUM_POLYGONS = 2;
|
||||
static constexpr int NUM_VERTICES = 4;
|
||||
static constexpr int TRAIL_LENGTH = 8;
|
||||
static constexpr int COLOR_CHANGE_INTERVAL = 200; // Frames between color changes
|
||||
static constexpr int STRIP_HEIGHT = 16; // Draw in strips to avoid SPI buffer overflow
|
||||
|
||||
MystifyDemo() = default;
|
||||
~MystifyDemo() { deinit(); }
|
||||
|
||||
// Non-copyable, non-movable (owns PixelBuffer)
|
||||
MystifyDemo(const MystifyDemo&) = delete;
|
||||
MystifyDemo& operator=(const MystifyDemo&) = delete;
|
||||
MystifyDemo(MystifyDemo&&) = delete;
|
||||
MystifyDemo& operator=(MystifyDemo&&) = delete;
|
||||
|
||||
bool init(DisplayDriver* display) {
|
||||
if (!display) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display_ = display;
|
||||
width_ = display->getWidth();
|
||||
height_ = display->getHeight();
|
||||
|
||||
if (width_ <= 0 || height_ <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seed random generator with hardware entropy
|
||||
srand(static_cast<unsigned>(esp_random()));
|
||||
|
||||
// Allocate full-screen framebuffer
|
||||
void* mem = malloc(sizeof(PixelBuffer));
|
||||
if (!mem) {
|
||||
return false;
|
||||
}
|
||||
framebuffer_ = new(mem) PixelBuffer(width_, height_, display->getColorFormat());
|
||||
|
||||
initPolygons();
|
||||
return true;
|
||||
}
|
||||
|
||||
void deinit() {
|
||||
if (framebuffer_) {
|
||||
framebuffer_->~PixelBuffer();
|
||||
free(framebuffer_);
|
||||
framebuffer_ = nullptr;
|
||||
}
|
||||
display_ = nullptr;
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (!framebuffer_ || !display_) return;
|
||||
|
||||
// Clear framebuffer to black
|
||||
framebuffer_->clear();
|
||||
|
||||
// Update and draw each polygon
|
||||
for (int p = 0; p < NUM_POLYGONS; p++) {
|
||||
updatePolygon(polygons_[p]);
|
||||
drawPolygon(polygons_[p]);
|
||||
}
|
||||
|
||||
// Send framebuffer to display in strips (full screen is too large for single SPI transaction)
|
||||
display_->lock();
|
||||
for (int y = 0; y < height_; y += STRIP_HEIGHT) {
|
||||
int stripEnd = (y + STRIP_HEIGHT > height_) ? height_ : y + STRIP_HEIGHT;
|
||||
display_->drawBitmap(0, y, width_, stripEnd, framebuffer_->getDataAtRow(y));
|
||||
}
|
||||
display_->unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
// Smooth sub-pixel movement with floats
|
||||
struct Vertex {
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float dx = 0;
|
||||
float dy = 0;
|
||||
};
|
||||
|
||||
struct Polygon {
|
||||
Vertex vertices[NUM_VERTICES];
|
||||
// History: [trail_index][vertex_index] = {x, y}
|
||||
int16_t historyX[TRAIL_LENGTH][NUM_VERTICES];
|
||||
int16_t historyY[TRAIL_LENGTH][NUM_VERTICES];
|
||||
uint8_t colorIndex;
|
||||
int historyHead = 0;
|
||||
bool historyFull = false;
|
||||
int colorChangeCounter = 0;
|
||||
};
|
||||
|
||||
// Vibrant colors as RGB888 for format-agnostic rendering
|
||||
struct Color {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
static constexpr Color COLOR_POOL[] = {
|
||||
{255, 0, 255}, // Magenta
|
||||
{0, 255, 255}, // Cyan
|
||||
{255, 255, 0}, // Yellow
|
||||
{255, 128, 0}, // Orange
|
||||
{0, 255, 128}, // Spring green
|
||||
{128, 0, 255}, // Purple
|
||||
{255, 64, 128}, // Hot pink
|
||||
{128, 255, 0}, // Lime
|
||||
};
|
||||
static constexpr int COLOR_POOL_SIZE = sizeof(COLOR_POOL) / sizeof(COLOR_POOL[0]);
|
||||
|
||||
DisplayDriver* display_ = nullptr;
|
||||
PixelBuffer* framebuffer_ = nullptr;
|
||||
uint16_t width_ = 0;
|
||||
uint16_t height_ = 0;
|
||||
Polygon polygons_[NUM_POLYGONS];
|
||||
|
||||
static float randomFloat(float min, float max) {
|
||||
return min + (max - min) * (static_cast<float>(rand()) / static_cast<float>(RAND_MAX));
|
||||
}
|
||||
|
||||
void initPolygons() {
|
||||
for (int p = 0; p < NUM_POLYGONS; p++) {
|
||||
Polygon& polygon = polygons_[p];
|
||||
|
||||
// Pick random color from pool
|
||||
polygon.colorIndex = rand() % COLOR_POOL_SIZE;
|
||||
polygon.historyHead = 0;
|
||||
polygon.historyFull = false;
|
||||
// Stagger color changes so polygons don't change simultaneously
|
||||
polygon.colorChangeCounter = rand() % COLOR_CHANGE_INTERVAL;
|
||||
|
||||
// Initialize vertices with random positions and velocities
|
||||
for (int v = 0; v < NUM_VERTICES; v++) {
|
||||
Vertex& vertex = polygon.vertices[v];
|
||||
vertex.x = static_cast<float>(rand() % width_);
|
||||
vertex.y = static_cast<float>(rand() % height_);
|
||||
|
||||
// Speed range for smooth movement
|
||||
vertex.dx = randomFloat(0.8f, 2.0f);
|
||||
vertex.dy = randomFloat(0.8f, 2.0f);
|
||||
if (rand() % 2) vertex.dx = -vertex.dx;
|
||||
if (rand() % 2) vertex.dy = -vertex.dy;
|
||||
|
||||
// Ensure dx != dy for more interesting movement patterns
|
||||
if (std::fabs(vertex.dx - vertex.dy) < 0.3f) {
|
||||
vertex.dy += (vertex.dy > 0 ? 0.5f : -0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize history with current positions
|
||||
for (int t = 0; t < TRAIL_LENGTH; t++) {
|
||||
for (int v = 0; v < NUM_VERTICES; v++) {
|
||||
polygon.historyX[t][v] = static_cast<int16_t>(polygon.vertices[v].x);
|
||||
polygon.historyY[t][v] = static_cast<int16_t>(polygon.vertices[v].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updatePolygon(Polygon& polygon) {
|
||||
constexpr float minSpeed = 0.5f;
|
||||
constexpr float maxSpeed = 2.5f;
|
||||
|
||||
// Periodic color change
|
||||
polygon.colorChangeCounter++;
|
||||
if (polygon.colorChangeCounter >= COLOR_CHANGE_INTERVAL) {
|
||||
polygon.colorChangeCounter = 0;
|
||||
// Pick a different color
|
||||
uint8_t newColor;
|
||||
do {
|
||||
newColor = rand() % COLOR_POOL_SIZE;
|
||||
} while (newColor == polygon.colorIndex && COLOR_POOL_SIZE > 1);
|
||||
polygon.colorIndex = newColor;
|
||||
}
|
||||
|
||||
// Move vertices
|
||||
for (int v = 0; v < NUM_VERTICES; v++) {
|
||||
Vertex& vertex = polygon.vertices[v];
|
||||
vertex.x += vertex.dx;
|
||||
vertex.y += vertex.dy;
|
||||
|
||||
// Bounce off edges with slight angle variation for organic movement
|
||||
if (vertex.x <= 0) {
|
||||
vertex.x = 0;
|
||||
vertex.dx = std::fabs(vertex.dx);
|
||||
vertex.dy *= (1.0f + randomFloat(-0.1f, 0.1f));
|
||||
} else if (vertex.x >= width_ - 1) {
|
||||
vertex.x = static_cast<float>(width_ - 1);
|
||||
vertex.dx = -std::fabs(vertex.dx);
|
||||
vertex.dy *= (1.0f + randomFloat(-0.1f, 0.1f));
|
||||
}
|
||||
|
||||
if (vertex.y <= 0) {
|
||||
vertex.y = 0;
|
||||
vertex.dy = std::fabs(vertex.dy);
|
||||
vertex.dx *= (1.0f + randomFloat(-0.1f, 0.1f));
|
||||
} else if (vertex.y >= height_ - 1) {
|
||||
vertex.y = static_cast<float>(height_ - 1);
|
||||
vertex.dy = -std::fabs(vertex.dy);
|
||||
vertex.dx *= (1.0f + randomFloat(-0.1f, 0.1f));
|
||||
}
|
||||
|
||||
// Clamp speeds to prevent runaway acceleration or stalling
|
||||
auto clampSpeed = [minSpeed, maxSpeed](float& speed) {
|
||||
float sign = (speed >= 0) ? 1.0f : -1.0f;
|
||||
float absSpeed = std::fabs(speed);
|
||||
if (absSpeed < minSpeed) absSpeed = minSpeed;
|
||||
if (absSpeed > maxSpeed) absSpeed = maxSpeed;
|
||||
speed = sign * absSpeed;
|
||||
};
|
||||
clampSpeed(vertex.dx);
|
||||
clampSpeed(vertex.dy);
|
||||
}
|
||||
|
||||
// Advance history ring buffer
|
||||
polygon.historyHead = (polygon.historyHead + 1) % TRAIL_LENGTH;
|
||||
if (polygon.historyHead == 0) {
|
||||
polygon.historyFull = true;
|
||||
}
|
||||
|
||||
// Store current positions
|
||||
for (int v = 0; v < NUM_VERTICES; v++) {
|
||||
polygon.historyX[polygon.historyHead][v] = static_cast<int16_t>(polygon.vertices[v].x);
|
||||
polygon.historyY[polygon.historyHead][v] = static_cast<int16_t>(polygon.vertices[v].y);
|
||||
}
|
||||
}
|
||||
|
||||
void drawPolygon(const Polygon& polygon) {
|
||||
const Color& baseColor = COLOR_POOL[polygon.colorIndex];
|
||||
|
||||
// Draw trail from oldest to newest (so newest is on top)
|
||||
for (int t = TRAIL_LENGTH - 1; t >= 0; t--) {
|
||||
int histIndex = polygon.historyHead - t;
|
||||
if (histIndex < 0) histIndex += TRAIL_LENGTH;
|
||||
|
||||
// Skip if we don't have enough history yet
|
||||
if (!polygon.historyFull && histIndex > polygon.historyHead) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate brightness for this trail frame (older = dimmer)
|
||||
int brightness = 255 - (t * 230 / TRAIL_LENGTH);
|
||||
if (brightness < 25) brightness = 25;
|
||||
|
||||
// Scale color by brightness
|
||||
uint8_t r = (baseColor.r * brightness) / 255;
|
||||
uint8_t g = (baseColor.g * brightness) / 255;
|
||||
uint8_t b = (baseColor.b * brightness) / 255;
|
||||
|
||||
// Draw edges connecting vertices
|
||||
for (int e = 0; e < NUM_VERTICES; e++) {
|
||||
int nextVertex = (e + 1) % NUM_VERTICES;
|
||||
|
||||
int x0 = polygon.historyX[histIndex][e];
|
||||
int y0 = polygon.historyY[histIndex][e];
|
||||
int x1 = polygon.historyX[histIndex][nextVertex];
|
||||
int y1 = polygon.historyY[histIndex][nextVertex];
|
||||
|
||||
drawLine(x0, y0, x1, y1, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bresenham's line algorithm
|
||||
void drawLine(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
|
||||
int dx = std::abs(x1 - x0);
|
||||
int dy = std::abs(y1 - y0);
|
||||
int sx = (x0 < x1) ? 1 : -1;
|
||||
int sy = (y0 < y1) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
while (true) {
|
||||
setPixel(x0, y0, r, g, b);
|
||||
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if (e2 > -dy) {
|
||||
err -= dy;
|
||||
x0 += sx;
|
||||
}
|
||||
if (e2 < dx) {
|
||||
err += dx;
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (x >= 0 && x < width_ && y >= 0 && y < height_) {
|
||||
framebuffer_->setPixel(x, y, r, g, b);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <esp_log.h>
|
||||
#include "drivers/Colors.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <tt_hal_display.h>
|
||||
|
||||
class PixelBuffer {
|
||||
uint16_t pixelWidth;
|
||||
uint16_t pixelHeight;
|
||||
ColorFormat colorFormat;
|
||||
uint8_t* data;
|
||||
|
||||
public:
|
||||
|
||||
PixelBuffer(uint16_t pixelWidth, uint16_t pixelHeight, ColorFormat colorFormat) :
|
||||
pixelWidth(pixelWidth),
|
||||
pixelHeight(pixelHeight),
|
||||
colorFormat(colorFormat)
|
||||
{
|
||||
data = static_cast<uint8_t*>(malloc(pixelWidth * pixelHeight * getPixelSize()));
|
||||
assert(data != nullptr);
|
||||
}
|
||||
|
||||
~PixelBuffer() {
|
||||
free(data);
|
||||
}
|
||||
|
||||
uint16_t getPixelWidth() const {
|
||||
return pixelWidth;
|
||||
}
|
||||
|
||||
uint16_t getPixelHeight() const {
|
||||
return pixelHeight;
|
||||
}
|
||||
|
||||
ColorFormat getColorFormat() const {
|
||||
return colorFormat;
|
||||
}
|
||||
|
||||
void* getData() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
uint32_t getDataSize() const {
|
||||
return pixelWidth * pixelHeight * getPixelSize();
|
||||
}
|
||||
|
||||
void* getDataAtRow(uint16_t row) const {
|
||||
auto address = reinterpret_cast<uint32_t>(data) + (row * getRowDataSize());
|
||||
return reinterpret_cast<void*>(address);
|
||||
}
|
||||
|
||||
uint16_t getRowDataSize() const {
|
||||
return pixelWidth * getPixelSize();
|
||||
}
|
||||
|
||||
uint8_t getPixelSize() const {
|
||||
switch (colorFormat) {
|
||||
case COLOR_FORMAT_MONOCHROME:
|
||||
return 1;
|
||||
case COLOR_FORMAT_BGR565:
|
||||
case COLOR_FORMAT_BGR565_SWAPPED:
|
||||
case COLOR_FORMAT_RGB565:
|
||||
case COLOR_FORMAT_RGB565_SWAPPED:
|
||||
return 2;
|
||||
case COLOR_FORMAT_RGB888:
|
||||
return 3;
|
||||
default:
|
||||
// TODO: Crash with error
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* getPixelAddress(uint16_t x, uint16_t y) const {
|
||||
uint32_t offset = ((y * getPixelWidth()) + x) * getPixelSize();
|
||||
uint32_t address = reinterpret_cast<uint32_t>(data) + offset;
|
||||
return reinterpret_cast<uint8_t*>(address);
|
||||
}
|
||||
|
||||
void setPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b) const {
|
||||
auto address = getPixelAddress(x, y);
|
||||
switch (colorFormat) {
|
||||
case COLOR_FORMAT_MONOCHROME:
|
||||
*address = (uint8_t)((uint16_t)r + (uint16_t)g + (uint16_t)b / 3);
|
||||
break;
|
||||
case COLOR_FORMAT_BGR565:
|
||||
Colors::rgb888ToBgr565(r, g, b, reinterpret_cast<uint16_t*>(address));
|
||||
break;
|
||||
case COLOR_FORMAT_BGR565_SWAPPED: {
|
||||
// TODO: Make proper conversion function
|
||||
Colors::rgb888ToBgr565(r, g, b, reinterpret_cast<uint16_t*>(address));
|
||||
uint8_t temp = *address;
|
||||
*address = *(address + 1);
|
||||
*(address + 1) = temp;
|
||||
break;
|
||||
}
|
||||
case COLOR_FORMAT_RGB565: {
|
||||
Colors::rgb888ToRgb565(r, g, b, reinterpret_cast<uint16_t*>(address));
|
||||
break;
|
||||
}
|
||||
case COLOR_FORMAT_RGB565_SWAPPED: {
|
||||
// TODO: Make proper conversion function
|
||||
Colors::rgb888ToRgb565(r, g, b, reinterpret_cast<uint16_t*>(address));
|
||||
uint8_t temp = *address;
|
||||
*address = *(address + 1);
|
||||
*(address + 1) = temp;
|
||||
break;
|
||||
}
|
||||
case COLOR_FORMAT_RGB888: {
|
||||
uint8_t pixel[3] = { r, g, b };
|
||||
memcpy(address, pixel, 3);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// NO-OP
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void clear(int value = 0) const {
|
||||
memset(data, value, getDataSize());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
class Colors {
|
||||
|
||||
public:
|
||||
|
||||
static void rgb888ToRgb565(uint8_t red, uint8_t green, uint8_t blue, uint16_t* rgb565) {
|
||||
uint16_t _rgb565 = (red >> 3);
|
||||
_rgb565 = (_rgb565 << 6) | (green >> 2);
|
||||
_rgb565 = (_rgb565 << 5) | (blue >> 3);
|
||||
*rgb565 = _rgb565;
|
||||
}
|
||||
|
||||
static void rgb888ToBgr565(uint8_t red, uint8_t green, uint8_t blue, uint16_t* bgr565) {
|
||||
uint16_t _bgr565 = (blue >> 3);
|
||||
_bgr565 = (_bgr565 << 6) | (green >> 2);
|
||||
_bgr565 = (_bgr565 << 5) | (red >> 3);
|
||||
*bgr565 = _bgr565;
|
||||
}
|
||||
|
||||
static void rgb565ToRgb888(uint16_t rgb565, uint32_t* rgb888) {
|
||||
uint32_t _rgb565 = rgb565;
|
||||
uint8_t b = (_rgb565 >> 8) & 0xF8;
|
||||
uint8_t g = (_rgb565 >> 3) & 0xFC;
|
||||
uint8_t r = (_rgb565 << 3) & 0xF8;
|
||||
|
||||
uint8_t* r8p = reinterpret_cast<uint8_t*>(rgb888);
|
||||
uint8_t* g8p = r8p + 1;
|
||||
uint8_t* b8p = r8p + 2;
|
||||
|
||||
*r8p = r | ((r >> 3) & 0x7);
|
||||
*g8p = g | ((g >> 2) & 0x3);
|
||||
*b8p = b | ((b >> 3) & 0x7);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <tt_hal_display.h>
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
|
||||
/**
|
||||
* Wrapper for tt_hal_display_driver_*
|
||||
*/
|
||||
class DisplayDriver {
|
||||
|
||||
DisplayDriverHandle handle = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit DisplayDriver(DeviceId id) {
|
||||
assert(tt_hal_display_driver_supported(id));
|
||||
handle = tt_hal_display_driver_alloc(id);
|
||||
assert(handle != nullptr);
|
||||
}
|
||||
|
||||
~DisplayDriver() {
|
||||
tt_hal_display_driver_free(handle);
|
||||
}
|
||||
|
||||
bool lock(TickType_t timeout = tt::kernel::MAX_TICKS) const {
|
||||
return tt_hal_display_driver_lock(handle, timeout);
|
||||
}
|
||||
|
||||
void unlock() const {
|
||||
tt_hal_display_driver_unlock(handle);
|
||||
}
|
||||
|
||||
uint16_t getWidth() const {
|
||||
return tt_hal_display_driver_get_pixel_width(handle);
|
||||
}
|
||||
|
||||
uint16_t getHeight() const {
|
||||
return tt_hal_display_driver_get_pixel_height(handle);
|
||||
}
|
||||
|
||||
ColorFormat getColorFormat() const {
|
||||
return tt_hal_display_driver_get_colorformat(handle);
|
||||
}
|
||||
|
||||
void drawBitmap(int xStart, int yStart, int xEnd, int yEnd, const void* pixelData) const {
|
||||
tt_hal_display_driver_draw_bitmap(handle, xStart, yStart, xEnd, yEnd, pixelData);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <tt_hal_touch.h>
|
||||
|
||||
/**
|
||||
* Wrapper for tt_hal_touch_driver_*
|
||||
*/
|
||||
class TouchDriver {
|
||||
|
||||
TouchDriverHandle handle = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
explicit TouchDriver(DeviceId id) {
|
||||
assert(tt_hal_touch_driver_supported(id));
|
||||
handle = tt_hal_touch_driver_alloc(id);
|
||||
assert(handle != nullptr);
|
||||
}
|
||||
|
||||
~TouchDriver() {
|
||||
tt_hal_touch_driver_free(handle);
|
||||
}
|
||||
|
||||
bool getTouchedPoints(uint16_t* x, uint16_t* y, uint16_t* strength, uint8_t* count, uint8_t maxCount) const {
|
||||
return tt_hal_touch_driver_get_touched_points(handle, x, y, strength, count, maxCount);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "Application.h"
|
||||
#include "MystifyDemo.h"
|
||||
#include "PixelBuffer.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <Tactility/kernel/Kernel.h>
|
||||
|
||||
constexpr auto TAG = "Application";
|
||||
constexpr int MYSTIFY_FRAME_DELAY_MS = 50; // ~20 FPS for smooth animation
|
||||
|
||||
static bool isTouched(TouchDriver* touch) {
|
||||
uint16_t x, y, strength;
|
||||
uint8_t pointCount = 0;
|
||||
return touch->getTouchedPoints(&x, &y, &strength, &pointCount, 1);
|
||||
}
|
||||
|
||||
void runApplication(DisplayDriver* display, TouchDriver* touch) {
|
||||
// Run the Mystify screensaver demo
|
||||
MystifyDemo mystify;
|
||||
if (!mystify.init(display)) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MystifyDemo");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting Mystify demo - touch to exit");
|
||||
|
||||
do {
|
||||
mystify.update();
|
||||
|
||||
// Frame rate limiter - ~20 FPS for smooth animation
|
||||
tt::kernel::delayTicks(tt::kernel::millisToTicks(MYSTIFY_FRAME_DELAY_MS));
|
||||
} while (!isTouched(touch));
|
||||
|
||||
ESP_LOGI(TAG, "Mystify demo ended");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "Application.h"
|
||||
#include "drivers/DisplayDriver.h"
|
||||
#include "drivers/TouchDriver.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <tt_app.h>
|
||||
#include <tt_app_alertdialog.h>
|
||||
#include <tt_lvgl.h>
|
||||
|
||||
constexpr auto TAG = "Main";
|
||||
|
||||
/** Find a DisplayDevice that supports the DisplayDriver interface */
|
||||
static bool findUsableDisplay(DeviceId& deviceId) {
|
||||
uint16_t display_count = 0;
|
||||
if (!tt_hal_device_find(DEVICE_TYPE_DISPLAY, &deviceId, &display_count, 1)) {
|
||||
ESP_LOGE(TAG, "No display device found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tt_hal_display_driver_supported(deviceId)) {
|
||||
ESP_LOGE(TAG, "Display doesn't support driver mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Find a TouchDevice that supports the TouchDriver interface */
|
||||
static bool findUsableTouch(DeviceId& deviceId) {
|
||||
uint16_t touch_count = 0;
|
||||
if (!tt_hal_device_find(DEVICE_TYPE_TOUCH, &deviceId, &touch_count, 1)) {
|
||||
ESP_LOGE(TAG, "No touch device found");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tt_hal_touch_driver_supported(deviceId)) {
|
||||
ESP_LOGE(TAG, "Touch doesn't support driver mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void onCreate(AppHandle appHandle, void* data) {
|
||||
DeviceId display_id;
|
||||
if (!findUsableDisplay(display_id)) {
|
||||
tt_app_stop();
|
||||
tt_app_alertdialog_start("Error", "The display doesn't support the required features.", nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
DeviceId touch_id;
|
||||
if (!findUsableTouch(touch_id)) {
|
||||
tt_app_stop();
|
||||
tt_app_alertdialog_start("Error", "The touch driver doesn't support the required features.", nullptr, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop LVGL first (because it's currently using the drivers we want to use)
|
||||
tt_lvgl_stop();
|
||||
|
||||
ESP_LOGI(TAG, "Creating display driver");
|
||||
auto display = new DisplayDriver(display_id);
|
||||
|
||||
ESP_LOGI(TAG, "Creating touch driver");
|
||||
auto touch = new TouchDriver(touch_id);
|
||||
|
||||
// Run the main logic
|
||||
ESP_LOGI(TAG, "Running application");
|
||||
runApplication(display, touch);
|
||||
|
||||
ESP_LOGI(TAG, "Cleanup display driver");
|
||||
delete display;
|
||||
|
||||
ESP_LOGI(TAG, "Cleanup touch driver");
|
||||
delete touch;
|
||||
|
||||
ESP_LOGI(TAG, "Stopping application");
|
||||
tt_app_stop();
|
||||
}
|
||||
|
||||
static void onDestroy(AppHandle appHandle, void* data) {
|
||||
// Restart LVGL to resume rendering of regular apps
|
||||
if (!tt_lvgl_is_started()) {
|
||||
ESP_LOGI(TAG, "Restarting LVGL");
|
||||
tt_lvgl_start();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
tt_app_register((AppRegistration) {
|
||||
.onCreate = onCreate,
|
||||
.onDestroy = onDestroy
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user