repeating texts

This commit is contained in:
2026-02-06 10:40:56 +01:00
parent 10c11415c1
commit b9eabc2d61
5 changed files with 279 additions and 22 deletions
+130 -3
View File
@@ -23,10 +23,23 @@ const char index_html[] PROGMEM = R"rawliteral(
} }
.text-controls { .text-controls {
margin-top: 20px; margin-top: 20px;
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
} }
.text-input-group { .text-input-group {
margin-bottom: 10px; margin-bottom: 10px;
} }
.param-explanation {
font-size: 0.8em;
color: #666;
}
pre {
text-align: left;
background-color: #f0f0f0;
padding: 10px;
white-space: pre-wrap;
}
</style> </style>
</head> </head>
<body> <body>
@@ -39,19 +52,89 @@ const char index_html[] PROGMEM = R"rawliteral(
<span id="brightnessValue">100</span> <span id="brightnessValue">100</span>
</div> </div>
<br> <br>
<div class="text-controls"> <div class="text-controls">
<h3>Send Text</h3> <h3>Add Text Node (Full Control)</h3>
<pre><code>void addNewTextNode(char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
<div class="text-input-group"> <div class="text-input-group">
<input type="text" id="textInput" placeholder="Enter text..."> <input type="text" id="textInput" placeholder="Enter text...">
<div class="param-explanation">The text to display.</div>
</div>
<div class="text-input-group">
<input type="color" id="textColorPicker" value="#ffffff"> <input type="color" id="textColorPicker" value="#ffffff">
<div class="param-explanation">Color of the text.</div>
</div>
<div class="text-input-group">
<input type="number" id="xInput" placeholder="X position">
<div class="param-explanation">X coordinate of the text.</div>
</div>
<div class="text-input-group">
<input type="number" id="yInput" placeholder="Y position">
<div class="param-explanation">Y coordinate of the text.</div>
</div>
<div class="text-input-group">
<select id="fontSizeInput">
<option value="small">Small</option>
<option value="medium">Medium</option>
</select>
<div class="param-explanation">Font size of the text.</div>
</div>
<div class="text-input-group">
<input type="number" id="slownessInput" placeholder="Slowness" value="2" min="0" max="255"> <input type="number" id="slownessInput" placeholder="Slowness" value="2" min="0" max="255">
<div class="param-explanation">Animation slowness (0-255).</div>
</div>
<div class="text-input-group">
<input type="checkbox" id="isRepeatingInput">
<label for="isRepeatingInput">Repeating</label>
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
</div> </div>
<div class="text-buttons"> <div class="text-buttons">
<button class="button" onclick="sendText()">Add Text</button>
<button class="button" onclick="sendText('top')">Send to Top</button> <button class="button" onclick="sendText('top')">Send to Top</button>
<button class="button" onclick="sendText('bottom')">Send to Bottom</button> <button class="button" onclick="sendText('bottom')">Send to Bottom</button>
</div> </div>
</div> </div>
<br>
<div class="text-controls">
<h3>Add Multicolor Text Node</h3>
<pre><code>void addNewMultiColor(char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
<div class="text-input-group">
<input type="text" id="multicolorTextInput" placeholder="Enter text...">
<div class="param-explanation">The text to display.</div>
</div>
<div class="text-input-group">
<input type="text" id="multicolorColorInput" placeholder="e.g., #ff0000,#00ff00,#0000ff">
<div class="param-explanation">Comma-separated list of hex colors (max 4).</div>
</div>
<div class="text-input-group">
<input type="number" id="multicolorXInput" placeholder="X position">
<div class="param-explanation">X coordinate of the text.</div>
</div>
<div class="text-input-group">
<input type="number" id="multicolorYInput" placeholder="Y position">
<div class="param-explanation">Y coordinate of the text.</div>
</div>
<div class="text-input-group">
<select id="multicolorFontSizeInput">
<option value="small">Small</option>
<option value="medium">Medium</option>
</select>
<div class="param-explanation">Font size of the text.</div>
</div>
<div class="text-input-group">
<input type="number" id="multicolorSlownessInput" placeholder="Slowness" value="2" min="0" max="255">
<div class="param-explanation">Animation slowness (0-255).</div>
</div>
<div class="text-input-group">
<input type="checkbox" id="multicolorIsRepeatingInput">
<label for="multicolorIsRepeatingInput">Repeating</label>
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
</div>
<div class="text-buttons">
<button class="button" onclick="sendMulticolorText()">Add Multicolor Text</button>
</div>
</div>
<script> <script>
function updateBrightness() { function updateBrightness() {
var brightness = document.getElementById("brightness").value; var brightness = document.getElementById("brightness").value;
@@ -66,6 +149,10 @@ const char index_html[] PROGMEM = R"rawliteral(
const text = document.getElementById('textInput').value; const text = document.getElementById('textInput').value;
const color = document.getElementById('textColorPicker').value; const color = document.getElementById('textColorPicker').value;
const slowness = document.getElementById('slownessInput').value; const slowness = document.getElementById('slownessInput').value;
const x = document.getElementById('xInput').value;
const y = document.getElementById('yInput').value;
const fontSize = document.getElementById('fontSizeInput').value;
const isRepeating = document.getElementById('isRepeatingInput').checked;
if (!text) { if (!text) {
alert('Please enter some text.'); alert('Please enter some text.');
@@ -75,7 +162,47 @@ const char index_html[] PROGMEM = R"rawliteral(
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("POST", "/text", true); xhr.open("POST", "/text", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("text=" + encodeURIComponent(text) + "&color=" + encodeURIComponent(color) + "&position=" + position + "&slowness=" + slowness);
let data = "text=" + encodeURIComponent(text) + "&color=" + encodeURIComponent(color) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
if(position) {
data += "&position=" + position;
} else {
if(x) data += "&x=" + x;
if(y) data += "&y=" + y;
}
if(fontSize) data += "&fontSize=" + fontSize;
xhr.send(data);
}
function sendMulticolorText() {
const text = document.getElementById('multicolorTextInput').value;
const colors = document.getElementById('multicolorColorInput').value;
const slowness = document.getElementById('multicolorSlownessInput').value;
const x = document.getElementById('multicolorXInput').value;
const y = document.getElementById('multicolorYInput').value;
const fontSize = document.getElementById('multicolorFontSizeInput').value;
const isRepeating = document.getElementById('multicolorIsRepeatingInput').checked;
if (!text) {
alert('Please enter some text.');
return;
}
if (!colors) {
alert('Please enter some colors.');
return;
}
var xhr = new XMLHttpRequest();
xhr.open("POST", "/multicolor-text", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
let data = "text=" + encodeURIComponent(text) + "&colors=" + encodeURIComponent(colors) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
if(x) data += "&x=" + x;
if(y) data += "&y=" + y;
if(fontSize) data += "&fontSize=" + fontSize;
xhr.send(data);
} }
</script> </script>
</body> </body>
+34 -4
View File
@@ -40,8 +40,8 @@ short getTextNodeX2(TextNode *node)
void addNewTextNode void addNewTextNode
( (
char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor, short pos_x, short pos_y,
unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1 unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating
) )
{ {
unsigned char text_length = strlen(text); unsigned char text_length = strlen(text);
@@ -69,6 +69,7 @@ void addNewTextNode
text_nodes[i].character_count = text_length; text_nodes[i].character_count = text_length;
text_nodes[i].scroll_slowness = scroll_slowness; text_nodes[i].scroll_slowness = scroll_slowness;
text_nodes[i].is_scrolling = is_scrolling; text_nodes[i].is_scrolling = is_scrolling;
text_nodes[i].is_repeating = is_repeating;
text_nodes[i].disappear_time = disappear_time; text_nodes[i].disappear_time = disappear_time;
ever_created_text_nodes++; ever_created_text_nodes++;
@@ -102,8 +103,15 @@ void scrollAllScrollableTexts(bool split_scroll_mode = false)
if (split_scroll_mode || text_nodes[i].pos_y < 7) if (split_scroll_mode || text_nodes[i].pos_y < 7)
{ {
if (x2 < 0) if (x2 < 0)
{
if (text_nodes[i].is_repeating)
{
text_nodes[i].pos_x = DISPLAY_MAX_X;
}
else
{ {
text_nodes[i].disappear_time = 1; text_nodes[i].disappear_time = 1;
}
continue; continue;
} }
text_nodes[i].pos_x--; text_nodes[i].pos_x--;
@@ -111,8 +119,15 @@ void scrollAllScrollableTexts(bool split_scroll_mode = false)
else else
{ {
if (x1 > DISPLAY_MAX_X) 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; text_nodes[i].disappear_time = 1;
}
continue; continue;
} }
text_nodes[i].pos_x++; text_nodes[i].pos_x++;
@@ -137,8 +152,8 @@ void scrollAllScrollableTexts(bool split_scroll_mode = false)
void addNewMultiColor void addNewMultiColor
( (
char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, 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 = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1 unsigned char scroll_slowness, bool is_scrolling, bool is_small, short disappear_time, bool is_repeating
) )
{ {
unsigned char text_length = strlen(text); unsigned char text_length = strlen(text);
@@ -170,6 +185,7 @@ void addNewMultiColor
multi_color_text_node[i].character_count = text_length; multi_color_text_node[i].character_count = text_length;
multi_color_text_node[i].scroll_slowness = scroll_slowness; multi_color_text_node[i].scroll_slowness = scroll_slowness;
multi_color_text_node[i].is_scrolling = is_scrolling; 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].disappear_time = disappear_time;
ever_created_multi_color_text_nodes++; ever_created_multi_color_text_nodes++;
@@ -209,8 +225,15 @@ void scrollAllMultiColorTexts(bool split_scroll_mode = false)
if (split_scroll_mode || multi_color_text_node[i].pos_y < 7) if (split_scroll_mode || multi_color_text_node[i].pos_y < 7)
{ {
if (x2 < 0) 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; multi_color_text_node[i].disappear_time = 1;
}
continue; continue;
} }
multi_color_text_node[i].pos_x--; multi_color_text_node[i].pos_x--;
@@ -218,8 +241,15 @@ void scrollAllMultiColorTexts(bool split_scroll_mode = false)
else else
{ {
if (x1 > DISPLAY_MAX_X) 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; multi_color_text_node[i].disappear_time = 1;
}
continue; continue;
} }
multi_color_text_node[i].pos_x++; multi_color_text_node[i].pos_x++;
+2 -2
View File
@@ -16,9 +16,9 @@ void drawImageFromMemoryByIndex(unsigned char image_index, short pos_x, short po
void setPixel(short x, short y, uint32_t color); void setPixel(short x, short y, uint32_t color);
void start_server(); void start_server();
void handle_server(); void handle_server();
void 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); void 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 = false);
void scrollAllScrollableTexts(bool split_scroll_mode); void scrollAllScrollableTexts(bool split_scroll_mode);
void 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); void 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 = false);
void scrollAllMultiColorTexts(bool split_scroll_mode); void scrollAllMultiColorTexts(bool split_scroll_mode);
void drawCharacter(const bool (*character)[5], unsigned char height, unsigned char width, uint32_t color, Cursor (*used_cursor)); void drawCharacter(const bool (*character)[5], unsigned char height, unsigned char width, uint32_t color, Cursor (*used_cursor));
void handleDisappearTimers(); void handleDisappearTimers();
+103 -5
View File
@@ -98,27 +98,124 @@ void handleBmpUpload() {
void handleText() { void handleText() {
if (server.hasArg("text") && server.hasArg("color") && server.hasArg("position") && server.hasArg("slowness")) { if (server.hasArg("text") && server.hasArg("color") && server.hasArg("slowness")) {
String text_str = server.arg("text"); String text_str = server.arg("text");
String colorStr = server.arg("color"); String colorStr = server.arg("color");
String position = server.arg("position");
unsigned char slowness = server.arg("slowness").toInt(); unsigned char slowness = server.arg("slowness").toInt();
short disappear_time = server.hasArg("disappear") ? server.arg("disappear").toInt() : -1; short disappear_time = server.hasArg("disappear") ? server.arg("disappear").toInt() : -1;
bool is_small = true;
if (server.hasArg("fontSize") && server.arg("fontSize") != "") {
if (server.arg("fontSize") == "medium") {
is_small = false;
}
}
bool is_repeating = false;
if (server.hasArg("is_repeating")) {
is_repeating = server.arg("is_repeating") == "true";
}
char text[TEXT_MAX_LENGTH + 1]; char text[TEXT_MAX_LENGTH + 1];
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1); text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
uint32_t color = strtol(colorStr.substring(1).c_str(), NULL, 16); uint32_t color = strtol(colorStr.substring(1).c_str(), NULL, 16);
if (server.hasArg("position")) {
String position = server.arg("position");
if (position == "top") { if (position == "top") {
addNewTextNode(text, color, false, 43, 0, slowness, true, false, disappear_time); addNewTextNode(text, color, false, 43, 0, slowness, true, is_small, disappear_time, is_repeating);
} else if (position == "bottom") { } else if (position == "bottom") {
addNewTextNode(text, color, false, -text_str.length() * 6, 9, slowness, true, false, disappear_time); addNewTextNode(text, color, false, -text_str.length() * 6, 9, slowness, true, is_small, disappear_time, is_repeating);
} }
server.send(200, "text/plain", "OK");
} else {
short x = 0;
short y = 0;
if (server.hasArg("x") && server.arg("x") != "") {
x = server.arg("x").toInt();
}
if (server.hasArg("y") && server.arg("y") != "") {
y = server.arg("y").toInt();
}
addNewTextNode(text, color, false, x, y, slowness, true, is_small, disappear_time, is_repeating);
server.send(200, "text/plain", "OK");
}
} else {
server.send(400, "text/plain", "Invalid arguments for /text");
}
}
void handleMulticolorText() {
if (server.hasArg("text") && server.hasArg("colors") && server.hasArg("slowness")) {
String text_str = server.arg("text");
String colors_str = server.arg("colors");
unsigned char slowness = server.arg("slowness").toInt();
short disappear_time = server.hasArg("disappear") ? server.arg("disappear").toInt() : -1;
bool is_small = true;
if (server.hasArg("fontSize") && server.arg("fontSize") != "") {
if (server.arg("fontSize") == "medium") {
is_small = false;
}
}
bool is_repeating = false;
if (server.hasArg("is_repeating")) {
is_repeating = server.arg("is_repeating") == "true";
}
char text[TEXT_MAX_LENGTH + 1];
text_str.toCharArray(text, TEXT_MAX_LENGTH + 1);
RGBWithIndex colors[4];
int color_count = 0;
String color_part = "";
int last_comma = -1;
for (int i = 0; i < colors_str.length() && color_count < 4; i++) {
if (colors_str.charAt(i) == ',') {
color_part = colors_str.substring(last_comma + 1, i);
last_comma = i;
if (color_part.length() > 0) {
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
colors[color_count].r = (color_val >> 16) & 0xFF;
colors[color_count].g = (color_val >> 8) & 0xFF;
colors[color_count].b = color_val & 0xFF;
color_count++;
}
}
}
// last color
if (color_count < 4) {
color_part = colors_str.substring(last_comma + 1);
if (color_part.length() > 0) {
long color_val = strtol(color_part.substring(1).c_str(), NULL, 16);
colors[color_count].r = (color_val >> 16) & 0xFF;
colors[color_count].g = (color_val >> 8) & 0xFF;
colors[color_count].b = color_val & 0xFF;
color_count++;
}
}
if (color_count > 0) {
int text_len = text_str.length();
int part_len = text_len / color_count;
for (int i = 0; i < color_count; i++) {
colors[i].start_index = i * part_len;
}
}
short x = 0;
short y = 0;
if (server.hasArg("x") && server.arg("x") != "") {
x = server.arg("x").toInt();
}
if (server.hasArg("y") && server.arg("y") != "") {
y = server.arg("y").toInt();
}
addNewMultiColor(text, colors, color_count, false, x, y, slowness, true, is_small, disappear_time, is_repeating);
server.send(200, "text/plain", "OK"); server.send(200, "text/plain", "OK");
} else { } else {
server.send(400, "text/plain", "Invalid arguments"); server.send(400, "text/plain", "Invalid arguments for /multicolor-text");
} }
} }
@@ -137,6 +234,7 @@ void start_server()
server.on("/", handleRoot); server.on("/", handleRoot);
server.on("/text", HTTP_POST, handleText); server.on("/text", HTTP_POST, handleText);
server.on("/multicolor-text", HTTP_POST, handleMulticolorText);
server.on("/brightness", HTTP_POST, handleBrightness); server.on("/brightness", HTTP_POST, handleBrightness);
server.on("/upload-page", HTTP_GET, handleUploadPage); server.on("/upload-page", HTTP_GET, handleUploadPage);
server.on("/upload-bmp", HTTP_POST, []() { server.on("/upload-bmp", HTTP_POST, []() {
+4 -2
View File
@@ -34,9 +34,10 @@ struct TextNode
unsigned char scroll_progress; unsigned char scroll_progress;
short disappear_time; short disappear_time;
bool is_scrolling; bool is_scrolling;
bool is_repeating;
TextNode() : color(0), pos_x(0), pos_y(0), character_count(0), scroll_slowness(0), scroll_progress(0), characterSize({7,5}), disappear_time(0), is_scrolling(true) {} TextNode() : color(0), pos_x(0), pos_y(0), character_count(0), scroll_slowness(0), scroll_progress(0), characterSize({7,5}), disappear_time(0), is_scrolling(true), is_repeating(false) {}
}; };
struct RGB struct RGB
@@ -77,8 +78,9 @@ struct MultiColorTextNode
unsigned char scroll_progress; unsigned char scroll_progress;
short disappear_time; short disappear_time;
bool is_scrolling; bool is_scrolling;
bool is_repeating;
MultiColorTextNode() : pos_x(0), pos_y(0), character_count(0), scroll_slowness(0), scroll_progress(0), characterSize({7,5}), disappear_time(0), is_scrolling(true), color_count(0) {} MultiColorTextNode() : pos_x(0), pos_y(0), character_count(0), scroll_slowness(0), scroll_progress(0), characterSize({7,5}), disappear_time(0), is_scrolling(true), color_count(0), is_repeating(false) {}
}; };
struct Image struct Image