#ifdef ESP_PLATFORM #include "MystifyScreensaver.h" #include #include namespace tt::service::displayidle { // Helper to generate random float in range [min, max] static float randomFloat(float min, float max) { if (max <= min) return min; return min + (max - min) * static_cast(rand()) / static_cast(RAND_MAX); } void MystifyScreensaver::start(lv_obj_t* overlay, lv_coord_t screenW, lv_coord_t screenH) { for (auto& polygon : polygons_) { initPolygon(polygon, overlay, screenW, screenH); } } void MystifyScreensaver::stop() { for (auto& polygon : polygons_) { for (auto& trailLines : polygon.lines) { for (auto& line : trailLines) { line = nullptr; // Objects deleted by parent overlay } } polygon.historyHead = 0; polygon.historyFull = false; } } void MystifyScreensaver::update(lv_coord_t screenW, lv_coord_t screenH) { constexpr float minSpeed = 0.5f; constexpr float maxSpeed = 2.5f; for (auto& polygon : polygons_) { // Periodic color change polygon.colorChangeCounter++; if (polygon.colorChangeCounter >= COLOR_CHANGE_INTERVAL) { polygon.colorChangeCounter = 0; uint32_t colorHex = COLOR_POOL[rand() % COLOR_POOL.size()]; polygon.color = lv_color_hex(colorHex); updateLineColors(polygon); } // Move vertices for (auto& vertex : polygon.vertices) { vertex.x += vertex.dx; vertex.y += vertex.dy; // Bounce off edges with slight angle variation for more organic movement if (vertex.x <= 0) { vertex.x = 0; vertex.dx = std::fabs(vertex.dx) + randomFloat(-0.2f, 0.2f); } else if (vertex.x >= screenW - 1) { vertex.x = static_cast(screenW - 1); vertex.dx = -std::fabs(vertex.dx) + randomFloat(-0.2f, 0.2f); } if (vertex.y <= 0) { vertex.y = 0; vertex.dy = std::fabs(vertex.dy) + randomFloat(-0.2f, 0.2f); } else if (vertex.y >= screenH - 1) { vertex.y = static_cast(screenH - 1); vertex.dy = -std::fabs(vertex.dy) + randomFloat(-0.2f, 0.2f); } // Clamp speeds to prevent runaway acceleration or stalling if (std::fabs(vertex.dx) < minSpeed) vertex.dx = (vertex.dx >= 0 ? minSpeed : -minSpeed); if (std::fabs(vertex.dy) < minSpeed) vertex.dy = (vertex.dy >= 0 ? minSpeed : -minSpeed); if (std::fabs(vertex.dx) > maxSpeed) vertex.dx = (vertex.dx >= 0 ? maxSpeed : -maxSpeed); if (std::fabs(vertex.dy) > maxSpeed) vertex.dy = (vertex.dy >= 0 ? maxSpeed : -maxSpeed); } // Shift history int newHead = polygon.historyHead + 1; if (newHead >= TRAIL_LENGTH) { newHead = 0; polygon.historyFull = true; } polygon.historyHead = newHead; // Store current positions at head for (int v = 0; v < NUM_VERTICES; v++) { polygon.history[newHead][v].x = polygon.vertices[v].x; polygon.history[newHead][v].y = polygon.vertices[v].y; } // Pre-calculate history indices int histIndices[TRAIL_LENGTH]; for (int t = 0; t < TRAIL_LENGTH; t++) { int idx = newHead - t; histIndices[t] = (idx >= 0) ? idx : idx + TRAIL_LENGTH; } // Calculate valid trail count int validTrailCount = polygon.historyFull ? TRAIL_LENGTH : (newHead + 1); // Update line positions for (int t = 0; t < TRAIL_LENGTH; t++) { auto& trailLines = polygon.lines[t]; // Hide lines beyond valid history if (t >= validTrailCount) { for (int e = 0; e < NUM_VERTICES; e++) { if (trailLines[e]) { lv_obj_add_flag(trailLines[e], LV_OBJ_FLAG_HIDDEN); } } continue; } int histIndex = histIndices[t]; auto& hist = polygon.history[histIndex]; auto& linePointsTrail = polygon.linePoints[t]; for (int e = 0; e < NUM_VERTICES; e++) { if (!trailLines[e]) continue; lv_obj_remove_flag(trailLines[e], LV_OBJ_FLAG_HIDDEN); int v2 = (e + 1) % NUM_VERTICES; linePointsTrail[e][0].x = hist[e].x; linePointsTrail[e][0].y = hist[e].y; linePointsTrail[e][1].x = hist[v2].x; linePointsTrail[e][1].y = hist[v2].y; lv_line_set_points(trailLines[e], linePointsTrail[e].data(), 2); } } } } void MystifyScreensaver::initPolygon(Polygon& polygon, lv_obj_t* parent, lv_coord_t screenW, lv_coord_t screenH) { // Sanity check - avoid undefined behavior from modulo by zero if (screenW <= 0 || screenH <= 0) return; // Pick a random color from the pool uint32_t colorHex = COLOR_POOL[rand() % COLOR_POOL.size()]; polygon.color = lv_color_hex(colorHex); polygon.historyHead = 0; polygon.historyFull = false; // Stagger color changes so polygons don't all change at once polygon.colorChangeCounter = rand() % COLOR_CHANGE_INTERVAL; // Initialize vertices with random positions and velocities for (int v = 0; v < NUM_VERTICES; v++) { auto& vertex = polygon.vertices[v]; vertex.x = static_cast(rand() % screenW); vertex.y = static_cast(rand() % screenH); // Slower speeds (0.8-2.0) 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(std::fabs(vertex.dx) - std::fabs(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.history[t][v].x = polygon.vertices[v].x; polygon.history[t][v].y = polygon.vertices[v].y; } } // Create line objects with pre-computed opacity values for (int t = 0; t < TRAIL_LENGTH; t++) { int opacity = LV_OPA_COVER - (t * LV_OPA_COVER / TRAIL_LENGTH); if (opacity < LV_OPA_10) opacity = LV_OPA_10; for (int e = 0; e < NUM_VERTICES; e++) { lv_obj_t* line = lv_line_create(parent); if (line == nullptr) { polygon.lines[t][e] = nullptr; continue; } lv_obj_remove_style_all(line); lv_obj_set_style_line_color(line, polygon.color, 0); lv_obj_set_style_line_opa(line, opacity, 0); lv_obj_set_style_line_width(line, 2, 0); polygon.lines[t][e] = line; } } } void MystifyScreensaver::updateLineColors(Polygon& polygon) { // Update all line colors when color changes for (int t = 0; t < TRAIL_LENGTH; t++) { for (int e = 0; e < NUM_VERTICES; e++) { if (polygon.lines[t][e]) { lv_obj_set_style_line_color(polygon.lines[t][e], polygon.color, 0); } } } } } // namespace tt::service::displayidle #endif // ESP_PLATFORM