#include #include #include #include #include "config.h" #include "index.h" #include "fonts.h" #include "lowLevel.h" #include "upload_page.h" #include "prototypes.h" #ifdef __AVR__ #include // Required for 16 MHz Adafruit Trinket #endif Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); TextNode text_nodes[MAX_TEXT_NODES_COUNT]; MultiColorTextNode multi_color_text_node[MAX_TEXT_NODES_COUNT]; Cursor cursor; unsigned char global_node_id = 0; bool main_animation_started = false; unsigned char bottom_text_state = 0; unsigned char scroll_node_global_id = 0; bool scroll_node_active = false; Image saved_images[MAX_IMAGES_SAVED] = { {},{} }; const RGB animation_data[MAX_ANIMATIONS_COUNT][MAX_ANIMATION_FRAME_COUNT][PANEL_PIXEL_COUNT][DISPLAY_MAX_X + 1] = {}; Animation animations[MAX_ANIMATIONS_COUNT]; bool vehichles[][][] = { { {0}, {0,0,1,1,1,1,1,0}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0} }, { {0,0,1,1,0,0,0,0}, {0,1,1,1,0,0,0,0}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0} }, { {0,0,1,1,0,1,1,1}, {0,1,1,1,1,1,1,1}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0} }, { {0,0,0,0,1,1,0,0}, {0,0,1,1,1,1,1,0}, {0,1,1,1,1,1,1,1}, {0,0,1,0,0,0,1,0} }, { {0}, {0,0,0,1,1,1,1,1}, {0,0,1,1,1,1,1,1}, {0,0,0,1,0,0,1,0} }, { {1,1,1,1,1,1,1,1}, {0,1,0,1,0,1,0,1}, {0,1,1,1,1,1,1,1}, {0,0,1,0,0,0,1,0} }, { {0}, {0,0,1,1,0,0,0,0}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0}, }, { {1,1,0,1,1,1,1,1}, {1,1,0,1,1,1,1,1}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0} }, { {0,0,0,1,1,1,1,1}, {0,0,1,1,1,1,1,1}, {1,1,1,1,1,1,1,1}, {0,1,0,0,0,0,1,0} }, { {0}, {0,1,1,1,0,0,0,1}, {1,1,1,1,1,1,1,0}, {0,1,0,0,0,1,0,0} } } short getTextNodeY2(TextNode *node) { return node->pos_y + node->characterSize.height - 1; } short getTextNodeX2(TextNode *node) { if (node->character_count == 0) return node->pos_x; return node->pos_x + (node->characterSize.width * node->character_count) + (node->character_count - 1) - 1; } TextNode* addNewTextNode ( char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor, short pos_x, short pos_y, unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating ) { unsigned char text_length = strlen(text); if (text_length == 0){return nullptr;} for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (text_nodes[i].disappear_time == 0) { strncpy(text_nodes[i].content, text, TEXT_MAX_LENGTH); text_nodes[i].color = color; if (handle_pos_via_cursor) { text_nodes[i].pos_x = cursor.x; text_nodes[i].pos_y = cursor.y; } else { text_nodes[i].pos_x = pos_x; text_nodes[i].pos_y = pos_y; } text_nodes[i].characterSize.height = is_small ? SMALL_TEXT_HEIGHT : MEDIUM_TEXT_HEIGHT; text_nodes[i].characterSize.width = is_small ? SMALL_TEXT_WIDTH : MEDIUM_TEXT_WIDTH; text_nodes[i].character_count = text_length; text_nodes[i].scroll_slowness = scroll_slowness; text_nodes[i].is_scrolling = is_scrolling; text_nodes[i].is_repeating = is_repeating; text_nodes[i].disappear_time = disappear_time; text_nodes[i].global_id = global_node_id; global_node_id++; if (handle_pos_via_cursor) { cursor.x += (text_nodes[i].characterSize.width + 1) * text_length; } return &text_nodes[i]; } } return nullptr; } void modifyTextNodeByGlobalId(unsigned char global_id, uint32_t new_color, char new_text[TEXT_MAX_LENGTH + 1], unsigned char new_slowness) { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (text_nodes[i].global_id == global_id && text_nodes[i].disappear_time != 0) { text_nodes[i].color = new_color; strncpy(text_nodes[i].content, new_text, TEXT_MAX_LENGTH); text_nodes[i].character_count = strlen(new_text); text_nodes[i].scroll_slowness = new_slowness; break; } } } void modifyMultiColorTextNodeByGlobalId(unsigned char global_id, char new_text[TEXT_MAX_LENGTH + 1], RGBWithIndex new_colors[4], unsigned char new_color_count) { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (multi_color_text_node[i].global_id == global_id && multi_color_text_node[i].disappear_time != 0) { strncpy(multi_color_text_node[i].content, new_text, TEXT_MAX_LENGTH); multi_color_text_node[i].character_count = strlen(new_text); multi_color_text_node[i].color_count = new_color_count; for(int j = 0; j < new_color_count; j++) { multi_color_text_node[i].colors[j] = new_colors[j]; } break; } } } void scrollAllScrollableTexts(bool split_scroll_mode = false) { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (text_nodes[i].disappear_time == 0) {continue;} if (text_nodes[i].is_scrolling) { if (text_nodes[i].scroll_slowness > text_nodes[i].scroll_progress) { text_nodes[i].scroll_progress++; } else { text_nodes[i].scroll_progress = 0; short x1 = text_nodes[i].pos_x; short x2 = getTextNodeX2(&text_nodes[i]); if (split_scroll_mode || text_nodes[i].pos_y >= 7) { if (x2 < 0) { if (text_nodes[i].is_repeating) { text_nodes[i].pos_x = DISPLAY_MAX_X; } else { text_nodes[i].disappear_time = 1; } continue; } text_nodes[i].pos_x--; } else { if (x1 > DISPLAY_MAX_X) { if (text_nodes[i].is_repeating) { text_nodes[i].pos_x = -getTextNodeX2(&text_nodes[i]) + text_nodes[i].pos_x; } else { text_nodes[i].disappear_time = 1; } continue; } text_nodes[i].pos_x++; } } } cursor.x = text_nodes[i].pos_x; cursor.y = text_nodes[i].pos_y; for (unsigned char j = 0; j < text_nodes[i].character_count; j++) { char ch = text_nodes[i].content[j]; if (ch < '!' || ch > '~') { ch = ' '; } drawCharacter(font7x5[ch - ' '], text_nodes[i].characterSize.height, text_nodes[i].characterSize.width, text_nodes[i].color, &cursor); } } } MultiColorTextNode* addNewMultiColor ( char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor, short pos_x, short pos_y, unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating ) { unsigned char text_length = strlen(text); if (text_length == 0){return nullptr;} for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (multi_color_text_node[i].disappear_time == 0) { strncpy(multi_color_text_node[i].content, text, TEXT_MAX_LENGTH); multi_color_text_node[i].color_count = color_count; for(int j = 0; j < color_count; j++) { multi_color_text_node[i].colors[j] = colors[j]; } if (handle_pos_via_cursor) { multi_color_text_node[i].pos_x = cursor.x; multi_color_text_node[i].pos_y = cursor.y; } else { multi_color_text_node[i].pos_x = pos_x; multi_color_text_node[i].pos_y = pos_y; } multi_color_text_node[i].characterSize.height = is_small ? SMALL_TEXT_HEIGHT : MEDIUM_TEXT_HEIGHT; multi_color_text_node[i].characterSize.width = is_small ? SMALL_TEXT_WIDTH : MEDIUM_TEXT_WIDTH; multi_color_text_node[i].character_count = text_length; multi_color_text_node[i].scroll_slowness = scroll_slowness; multi_color_text_node[i].is_scrolling = is_scrolling; multi_color_text_node[i].is_repeating = is_repeating; multi_color_text_node[i].disappear_time = disappear_time; multi_color_text_node[i].global_id = global_node_id; global_node_id++; if (handle_pos_via_cursor) { cursor.x += (multi_color_text_node[i].characterSize.width + 1) * text_length; } return &multi_color_text_node[i]; } } return nullptr; } short getMultiColorTextNodeX2(MultiColorTextNode *node) { if (node->character_count == 0) return node->pos_x; return node->pos_x + (node->characterSize.width * node->character_count) + (node->character_count - 1) - 1; } void scrollAllMultiColorTexts(bool split_scroll_mode = false) { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (multi_color_text_node[i].disappear_time == 0) {continue;} if (multi_color_text_node[i].is_scrolling) { if (multi_color_text_node[i].scroll_slowness > multi_color_text_node[i].scroll_progress) { multi_color_text_node[i].scroll_progress++; } else { multi_color_text_node[i].scroll_progress = 0; short x1 = multi_color_text_node[i].pos_x; short x2 = getMultiColorTextNodeX2(&multi_color_text_node[i]); if (split_scroll_mode || multi_color_text_node[i].pos_y >= 7) { if (x2 < 0) { if (multi_color_text_node[i].is_repeating) { multi_color_text_node[i].pos_x = DISPLAY_MAX_X; } else { multi_color_text_node[i].disappear_time = 1; } continue; } multi_color_text_node[i].pos_x--; } else { if (x1 > DISPLAY_MAX_X) { if (multi_color_text_node[i].is_repeating) { multi_color_text_node[i].pos_x = -getMultiColorTextNodeX2(&multi_color_text_node[i]) + multi_color_text_node[i].pos_x; } else { multi_color_text_node[i].disappear_time = 1; } continue; } multi_color_text_node[i].pos_x++; } } } cursor.x = multi_color_text_node[i].pos_x; cursor.y = multi_color_text_node[i].pos_y; uint32_t current_color = pixels.Color(multi_color_text_node[i].colors[0].r, multi_color_text_node[i].colors[0].g, multi_color_text_node[i].colors[0].b); unsigned char color_index = 0; for (unsigned char j = 0; j < multi_color_text_node[i].character_count; j++) { if (color_index < multi_color_text_node[i].color_count - 1 && multi_color_text_node[i].colors[color_index+1].start_index == j) { color_index++; current_color = pixels.Color(multi_color_text_node[i].colors[color_index].r, multi_color_text_node[i].colors[color_index].g, multi_color_text_node[i].colors[color_index].b); } char ch = multi_color_text_node[i].content[j]; if (ch < '!' || ch > '~') { ch = ' '; } drawCharacter(font7x5[ch - ' '], multi_color_text_node[i].characterSize.height, multi_color_text_node[i].characterSize.width, current_color, &cursor); } } } void drawImageFromMemoryByIndex(unsigned char image_index, short pos_x, short pos_y, unsigned char brightness) { Image* img = &saved_images[image_index]; auto dimBy = [brightness](unsigned char color) { return color * brightness / 100; }; for (unsigned char y = 0; y < img->height; y++) { for (unsigned char x = 0; x < img->width; x++) { setPixel(x + pos_x, y + pos_y, pixels.Color(dimBy(img->pixels[y][x].r), dimBy(img->pixels[y][x].g), dimBy(img->pixels[y][x].b))); } } } void displayAnimationFrame(unsigned char animation_index) { unsigned short frame = animations[animation_index].current_frame; for (unsigned char y = 0; y < PANEL_PIXEL_COUNT; y++) { for (unsigned char x = 0; x <= DISPLAY_MAX_X; x++) { const RGB& px = animation_data[animation_index][frame][y][x]; setPixel(x, y, pixels.Color(px.r * brightness / 100, px.g * brightness / 100, px.b * brightness / 100)); } } } void tickAnimations() { for (unsigned char i = 0; i < MAX_ANIMATIONS_COUNT; i++) { if (!animations[i].is_playing) continue; animations[i].frame_progress++; if (animations[i].frame_progress > animations[i].frame_delay) { animations[i].frame_progress = 0; animations[i].current_frame++; if (animations[i].current_frame >= animations[i].frame_count) { if (animations[i].is_looping) { animations[i].current_frame = 0; } else { animations[i].current_frame = animations[i].frame_count - 1; animations[i].is_playing = false; } } } } } void playAnimation(unsigned char index, unsigned char frame_delay, bool loop) { animations[index].current_frame = 0; animations[index].frame_progress = 0; animations[index].frame_delay = frame_delay; animations[index].is_playing = true; animations[index].is_looping = loop; } void stopAnimation(unsigned char index) { animations[index].is_playing = false; } void handleDisappearTimers() { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (text_nodes[i].disappear_time > 0) { text_nodes[i].disappear_time--; if (text_nodes[i].disappear_time == 0) { fillPixels(text_nodes[i].pos_x, text_nodes[i].pos_y, getTextNodeX2(&text_nodes[i]), getTextNodeY2(&text_nodes[i]), 0); } } } } void handleMultiColorDisappearTimers() { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (multi_color_text_node[i].disappear_time > 0) { multi_color_text_node[i].disappear_time--; if (multi_color_text_node[i].disappear_time == 0) { fillPixels(multi_color_text_node[i].pos_x, multi_color_text_node[i].pos_y, getMultiColorTextNodeX2(&multi_color_text_node[i]), multi_color_text_node[i].pos_y + multi_color_text_node[i].characterSize.height - 1, 0); } } } } bool isNodeExistingByGlobal(unsigned char global_id) { for (unsigned char i = 0; i < MAX_TEXT_NODES_COUNT; i++) { if (text_nodes[i].global_id == global_id && text_nodes[i].disappear_time != 0) { return true; } if (multi_color_text_node[i].global_id == global_id && multi_color_text_node[i].disappear_time != 0) { return true; } } return false; } void resetGlobalIdToAfterIntroValue() { global_node_id = 2; } void setup() { Serial.begin(115200); Serial.println("----------------------------------------------------"); pixels.begin(); pixels.clear(); addNewTextNode("NO", 0xFF050505, false, 0, 0, 1, true, true); addNewTextNode("INTERNET", 0xFF050505, false, 0, 9, 1, true, true); scrollAllScrollableTexts(true); pixels.show(); start_server(); } void handleProgram1() { if (!main_animation_started) { if (!isNodeExistingByGlobal(0) && !isNodeExistingByGlobal(1)) { main_animation_started = true; // Top row: "Witamy w PTI" — slow scroll, repeating, white + blue PTI addNewMultiColor("Witamy w PTI", (RGBWithIndex[4]){ {40, 40, 40, 0}, // white (dimmed) from char 0 {0, 0, 50, 9} // blue (dimmed) from char 9 ("PTI") }, 2, false, 0, 0, 3, true, true, -1, true); } } // Bottom row: alternating scrolling texts if (main_animation_started && (!scroll_node_active || !isNodeExistingByGlobal(scroll_node_global_id))) { TextNode* node = nullptr; if (bottom_text_state == 0) { node = addNewTextNode("Technik informatyk + ai, robotyka", pixels.Color(0, 0, 50), false, DISPLAY_MAX_X, 9, 1, true, true, -1, false); } else if (bottom_text_state == 1) { node = addNewTextNode("Technik programista", pixels.Color(50, 40, 0), false, DISPLAY_MAX_X, 9, 1, true, true, -1, false); } else if (bottom_text_state == 2) { node = addNewTextNode("Technik poligrafii i grafiki komputerowej", pixels.Color(0, 45, 0), false, DISPLAY_MAX_X, 9, 1, true, true, -1, false); } else { node = addNewTextNode("Technik reklamy", pixels.Color(60, 20, 0), false, DISPLAY_MAX_X, 9, 1, true, true, -1, false); } if (node != nullptr) { scroll_node_global_id = node->global_id; scroll_node_active = true; bottom_text_state = (bottom_text_state + 1) % 4; } } } void loop() { pixels.clear(); handle_server(); handleProgram1(); handleDisappearTimers(); handleMultiColorDisappearTimers(); scrollAllScrollableTexts(true); scrollAllMultiColorTexts(true); pixels.show(); }