mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-29 08:18:12 +00:00
fix(ui): honour newlines in message text instead of overdrawing lines
A message containing a line break drew two words on top of each other in the fullscreen reader. wrapLines() treated '\n' as an ordinary character: it measured it via getCodepointWidth() -- which reports a full 6px cell for it, since 0x0A sits below the font's first glyph -- and copied it into the wrapped line. Both display drivers' print() then acts on '\n' by resetting the cursor to x=0 and stepping down one row, so the tail of that line was drawn straight over the following one. wrapLines() now ends the line at '\n'/'\r' (CRLF counts as one break) and consumes the byte rather than emitting it, preserving blank lines the sender typed while still skipping degenerate empty wrap segments so the loop can't stall. This covers the fullscreen view and the history list's portrait bubbles, which share the function. drawTextEllipsized() folds newlines into spaces for the same reason: it draws one line clipped to max_width, and the compact one-line message previews in the landscape list feed it raw message bodies. A space keeps the words apart and measures the same, so the ellipsis maths is unchanged; for names and labels it's a no-op. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,10 +51,10 @@ struct FullscreenMsgView {
|
||||
void begin() { scroll = 0; active = true; }
|
||||
|
||||
// Pixel-accurate word-wrap: breaks at last space that fits within max_px,
|
||||
// hard-breaks long words. Walks the string codepoint-by-codepoint, summing
|
||||
// per-codepoint widths via DisplayDriver::getCodepointWidth — O(n) instead
|
||||
// of O(n²) substring re-measurement. Works for both fixed- and
|
||||
// variable-width fonts.
|
||||
// hard-breaks long words, and honours newlines the sender put in the text.
|
||||
// Walks the string codepoint-by-codepoint, summing per-codepoint widths via
|
||||
// DisplayDriver::getCodepointWidth — O(n) instead of O(n²) substring
|
||||
// re-measurement. Works for both fixed- and variable-width fonts.
|
||||
static int wrapLines(DisplayDriver& display, const char* text, int max_px,
|
||||
char out[][FS_CHARS_MAX], int max_lines) {
|
||||
int count = 0;
|
||||
@@ -63,12 +63,21 @@ struct FullscreenMsgView {
|
||||
const uint8_t* p = seg;
|
||||
const uint8_t* last_sp = nullptr; // pointer to space byte (cut here, drop the space)
|
||||
const uint8_t* last_fit = seg; // farthest point that still fits
|
||||
const uint8_t* brk = nullptr; // explicit newline that ends this line
|
||||
int width = 0;
|
||||
|
||||
while (*p && (p - seg) < FS_CHARS_MAX - 1) {
|
||||
const uint8_t* cp_start = p;
|
||||
uint32_t cp = DisplayDriver::decodeCodepoint(p);
|
||||
if ((p - seg) >= FS_CHARS_MAX) { p = cp_start; break; }
|
||||
// A newline in the message ends the line right here, and is consumed
|
||||
// rather than copied out. Letting one through was drawing two words on
|
||||
// top of each other: both display drivers' print() acts on '\n' by
|
||||
// resetting the cursor to x=0 and stepping down one row, so the rest of
|
||||
// this wrapped line landed on the following one. Measuring it is just
|
||||
// as wrong — it has no ink, but glyphXAdvance() reports a full 6px cell
|
||||
// for it (it's below the font's first codepoint), so it also ate width.
|
||||
if (cp == '\n' || cp == '\r') { brk = cp_start; break; }
|
||||
uint16_t cw = display.getCodepointWidth(cp);
|
||||
if (width + cw > max_px && cp_start > seg) { p = cp_start; break; }
|
||||
if (cp == ' ') last_sp = cp_start;
|
||||
@@ -78,7 +87,11 @@ struct FullscreenMsgView {
|
||||
|
||||
const uint8_t* end;
|
||||
const uint8_t* next_seg;
|
||||
if (!*p) {
|
||||
if (brk) {
|
||||
end = brk;
|
||||
next_seg = brk + 1;
|
||||
if (*brk == '\r' && *next_seg == '\n') next_seg++; // CRLF is one break, not two
|
||||
} else if (!*p) {
|
||||
end = p; next_seg = p;
|
||||
} else if (last_sp && last_sp > seg) {
|
||||
end = last_sp; next_seg = last_sp + 1;
|
||||
@@ -87,7 +100,10 @@ struct FullscreenMsgView {
|
||||
}
|
||||
|
||||
int len = (int)(end - seg);
|
||||
if (len <= 0) { seg = *seg ? seg + 1 : seg; continue; }
|
||||
// An empty line is only emitted for a real newline (a blank line the
|
||||
// sender typed); an empty wrap segment would loop forever, so skip it.
|
||||
if (len <= 0 && !brk) { seg = *seg ? seg + 1 : seg; continue; }
|
||||
if (len < 0) len = 0;
|
||||
if (len > FS_CHARS_MAX - 1) len = FS_CHARS_MAX - 1;
|
||||
memcpy(out[count], seg, len);
|
||||
out[count][len] = '\0';
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
- **Typing on CardKB right after cycling a T9 letter, then tapping that same T9 cell again quickly, could overwrite an unrelated character.** None of CardKB's direct actions — typing, moving the cursor with the arrows, or opening the placeholder picker with Tab — were invalidating the pending T9 cycle the way every on-screen keypress already does, so the next tap "continued" the cycle against a moved cursor.
|
||||
- **The keyboard's text preview could split a Cyrillic, Greek or accented character in half at the end of a line**, drawing garbage on both sides of the break, and fitted only half as much text per line as it had room for. Line breaks in the preview were counted in bytes rather than characters — every other part of the keyboard already counted characters.
|
||||
- **Caps-lock and one-shot Shift looked identical on the keyboard's ⇧ key**, so there was no way to tell whether the next letter alone or every following letter would be capitalised. Caps-lock is now marked with an underline on the key.
|
||||
- **A message containing a line break drew two words on top of each other.** Newlines the sender typed were treated as ordinary characters — measured as a blank 6px cell, then handed to the display, which moved back to the left edge one row down mid-line and drew the rest of it over the following line. Line breaks now end the line properly (blank lines included) in the fullscreen message view and the history list, and fold into a space in the one-line message previews.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -324,7 +324,15 @@ public:
|
||||
virtual void drawTextEllipsized(int x, int y, int max_width, const char* str) {
|
||||
char temp_str[256]; // reasonable buffer size
|
||||
translateUTF8ToBlocks(temp_str, str, sizeof(temp_str));
|
||||
|
||||
|
||||
// Fold newlines into spaces: this draws ONE line clipped to max_width, but
|
||||
// print() acts on '\n' by returning to x=0 one row down, which would spill
|
||||
// the tail onto whatever is drawn below. Message bodies (the compact
|
||||
// one-line previews in the history list) are the texts that carry them;
|
||||
// for labels and names this is a no-op. A space keeps the words apart and
|
||||
// measures the same, so the width/ellipsis maths below is unaffected.
|
||||
for (char* q = temp_str; *q; q++) if (*q == '\n' || *q == '\r') *q = ' ';
|
||||
|
||||
if (getTextWidth(temp_str) <= max_width) {
|
||||
setCursor(x, y);
|
||||
print(temp_str);
|
||||
|
||||
Reference in New Issue
Block a user