diff options
author | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-10-25 18:11:06 +0200 |
---|---|---|
committer | Raymaekers Luca <raymaekers.luca@gmail.com> | 2024-10-25 18:11:06 +0200 |
commit | 00c2fd786dee339b857771aa142211e894388a5f (patch) | |
tree | fffe48578eeb6fcadd3a0f5b09f1fcb9df69e724 | |
parent | 511720945f9bb2c60fd61fded2c11f6da762b8a1 (diff) |
Wrap messages when exceeding available width
Includes:
- Changed fillstr to use wide characters
- made the prompt box opaque
- More documentation on the code
-rw-r--r-- | v2/chatty.c | 236 |
1 files changed, 198 insertions, 38 deletions
diff --git a/v2/chatty.c b/v2/chatty.c index ec850de..b197324 100644 --- a/v2/chatty.c +++ b/v2/chatty.c @@ -23,61 +23,221 @@ enum { FDS_SERVER = 0, FDS_MAX }; // fill str array with char -void fillstr(u8 *str, u8 ch, u8 len) +void fillstr(wchar_t *str, wchar_t ch, u32 len) { for (int i = 0; i < len; i++) { str[i] = ch; } } -// home screen, the first screen the user sees -// it displays a prompt for user input and the received messages from msgsArena -void screen_home(Arena *msgsArena, wchar_t input[], u32 input_len) +// Centered popup displaying message in the appropriate cololrs +void popup(u32 fg, u32 bg, char *text) +{ + u32 len = strlen(text); + assert(len > 0); + tb_print(global.width / 2 - len / 2, global.height / 2, fg, bg, text); +} + +// Print `text` of text_len` wide characters wrapped to limit. x, y, fg and +// bg will be passed to the tb_printf() function calls. +// pfx is a string that will be printed first and will not be wrapped on characters like msg->text, +// this is useful when for example: printing messages and wanting to have consistent +// timestamp+author name. +// Returns the number of lines printed. +// TODO: remove text_len and calculate it in the function +// TODO: add y limit +// TODO:(bug) text after pfx is wrapped one too soon +// TODO: text == NULL to know how many lines *would* be printed +// TODO: check if text[i] goes out of bounds +int tb_printf_wrap(u32 x, u32 y, u32 fg, u32 bg, wchar_t *text, s32 text_len, char *pfx, s32 limit) { - Message *messages = msgsArena->memory; - assert(messages != NULL); - for (int i = 0; i < (msgsArena->pos / sizeof(Message)); i++) { - // Color user's own messages - u32 fg = 0; - if (strncmp(username, (char *)messages[i].author, AUTHOR_LEN) == 0) { - fg = TB_CYAN; + assert(limit > 0); + + /// Algorithm + // 1. Advance by limit + // 2. Look backwards for whitespace + // 3. split the string at the whitespace + // 4. print the string + // 5. restore the string (optional) + // 6. set the offset + // 7. repeat step 1. until i > len + // 8. print remaining part of the string + + // lines y, incremented after each wrap + s32 ly = y; + // character the text is split on + wchar_t t = 0; + // index used for searching in string + s32 i = limit; + // previous i for windowing through the text + s32 offset = 0; + // used when retrying to get a longer limit + u32 failed = 0; + + // NOTE: We can assume that we need to wrap, therefore print a newline after the prefix string + if (pfx != NULL) { + tb_printf(x, ly, fg, bg, "%s", pfx); + + s32 pfx_len = strlen(pfx); + if (limit > pfx_len + text_len) { + // everything fits on one line + tb_printf(x, y, fg, bg, "%s%ls", pfx, text); + return 1; } else { - fg = TB_WHITE; + ly++; } + } - // TODO: wrap when exceeding prompt size - tb_printf(0, i, fg, 0, "%s [%s] %ls", messages[i].timestamp, messages[i].author, messages[i].text); + while (i < text_len) { + // search backwards for whitespace + while (i > offset && text[i] != L' ') + i--; + + // retry with bigger limit + if (i == offset) { + offset = i; + failed++; + i += limit + failed * limit; + continue; + } else { + failed = 0; + } + + t = text[i]; + text[i] = 0; + tb_printf(x, ly, fg, bg, "%ls", text + offset); + text[i] = t; + + i++; // after the space + ly++; + + offset = i; + i += limit; } + tb_printf(x, ly, fg, bg, "%ls", text + offset); + ly++; - int len = global.width * 80 / 100; - wchar_t su[len + 2]; - wchar_t sd[len + 2]; - wchar_t lr = L'─', ur = L'╭', rd = L'╮', dr = L'╰', ru = L'╯', ud = L'│'; + return ly - y; +} + +// home screen, the first screen the user sees +// it displays a prompt for user input and the received messages from msgsArena +void screen_home(Arena *msgsArena, wchar_t input[], u32 input_len) +{ + // config options + const int box_max_len = 80; + const int box_min_len = 3; + const int box_x = 0, box_y = global.height - 3, box_pad_x = 1, box_mar_x = 1, box_bwith = 1, box_height = 3; + const int prompt_x = box_x + box_pad_x + box_mar_x + box_bwith + input_len; + + // the minimum height required is the hight for the box prompt + // the minimum width required is that one character should fit in the box prompt + if (global.height < box_height || global.width < (box_x + box_mar_x * 2 + box_pad_x * 2 + box_bwith * 2 + 1)) { + // + 1 for cursor + tb_hide_cursor(); + return; + } else { + // show cursor + // TODO: show cursor as block character instead of using the real cursor + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + } + + // Print messages in msgsArena, if there are too many to display, start printing from an offset. + // Looks like this: + // 03:24:29 [1234567890ab] hello homes how are + // you doing? + // 03:24:33 [TlasT] I am fine { - // top bar for prompt - su[0] = ur; - for (int i = 1; i < len; i++) { - su[i] = lr; + Message *messages = msgsArena->memory; + assert(messages != NULL); + // on what line to print the current message, used for scrolling + u32 msg_y = 0; + + u32 freesp = global.height - (global.height - box_height); + if (freesp <= 0) + goto draw_prompt; + + u32 nmessages = (msgsArena->pos / sizeof(Message)); + u32 offs = (nmessages > freesp) ? nmessages - freesp : 0; + + for (int i = offs; i < nmessages; i++) { + // Color user's own messages + u32 fg = 0; + if (strncmp(username, (char *)messages[i].author, AUTHOR_LEN) == 0) { + fg = TB_CYAN; + } else { + fg = TB_WHITE; + } + + u32 ty = 0; + char pfx[AUTHOR_LEN + TIMESTAMP_LEN - 2 + 5] = {0}; + sprintf(pfx, "%s [%s] ", messages[i].timestamp, messages[i].author); + ty = tb_printf_wrap(0, msg_y, fg, 0, messages[i].text, messages[i].text_len - 1, pfx, global.width); + msg_y += ty; } - su[len] = rd; - su[len + 1] = 0; - // bottom bar for prompt - sd[0] = dr; - for (int i = 1; i < len; i++) { - sd[i] = lr; + // Draw prompt box which is a box made out of + // should look like this: ╭───────╮ + // │ text█ │ + // ╰───────╯ + // the text is padded to the left and right by box_pad_x + // the middle/inner part is opaque + // TODO: wrapping when the text is bigger & alternated with scrolling when there is not + // enough space. + draw_prompt: { + + int box_len = 0; + if (global.width >= box_mar_x * 2 + box_min_len) { + // whole screen, but max out at box_max_len + box_len = (global.width >= box_max_len + 2) ? box_max_len : global.width - box_mar_x * 2; + } else { + box_len = box_mar_x * 2 + box_min_len; // left + right side } - sd[len] = ru; - sd[len + 1] = 0; - } - tb_printf(1, global.height - 3, 0, 0, "%ls", su); - tb_printf(1, global.height - 2, 0, 0, "%lc", ud); - global.cursor_x = 1 + 2 + input_len; - global.cursor_y = global.height - 2; - tb_printf(1 + 2, global.height - 2, 0, 0, "%ls", input); - tb_printf(1 + len, global.height - 2, 0, 0, "%lc", ud); - tb_printf(1, global.height - 1, 0, 0, "%ls", sd); + // +2 for corners and null terminator + wchar_t box_up[box_len + 1]; + wchar_t box_in[box_len + 1]; + wchar_t box_down[box_len + 1]; + wchar_t lr = L'─', ur = L'╭', rd = L'╮', dr = L'╰', ru = L'╯', ud = L'│'; + + // top bar + box_up[0] = ur; + fillstr(box_up + 1, lr, box_len - 1); + box_up[box_len - 1] = rd; + box_up[box_len] = 0; + // inner part + fillstr(box_in + 1, L' ', box_len - 1); + box_in[0] = ud; + box_in[box_len - 1] = ud; + box_in[box_len] = 0; + // bottom bar + box_down[0] = dr; + fillstr(box_down + 1, lr, box_len - 1); + box_down[box_len - 1] = ru; + box_down[box_len] = 0; + + tb_printf(box_x + box_mar_x, box_y, 0, 0, "%ls", box_up); + tb_printf(box_x + box_mar_x, box_y + 1, 0, 0, "%ls", box_in); + tb_printf(box_x + box_mar_x, box_y + 2, 0, 0, "%ls", box_down); + + global.cursor_y = box_y + 1; + + // NOTE: wrapping would be better. + // Scroll the text when it exceeds the prompt's box length + u32 freesp = box_len - box_pad_x * 2 - box_bwith * 2; + if (freesp <= 0) + return; + + if (input_len > freesp) { + wchar_t *text_offs = input + (input_len - freesp); + tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", text_offs); + global.cursor_x = box_x + box_pad_x + box_mar_x + box_bwith + freesp; + } else { + global.cursor_x = prompt_x; + tb_printf(box_x + box_mar_x + box_pad_x + box_bwith, box_y + 1, 0, 0, "%ls", input); + } + } + } } int main(int argc, char **argv) |