From d01caf679f6f819a501687278d75be110646d6ed Mon Sep 17 00:00:00 2001 From: Raymaekers Luca Date: Sat, 23 Nov 2024 03:32:40 +0100 Subject: Created InputBox widget --- chatty.c | 165 ++++++++----------------- ui.c | 296 -------------------------------------------- ui.h | 426 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 479 insertions(+), 408 deletions(-) delete mode 100644 ui.c create mode 100644 ui.h diff --git a/chatty.c b/chatty.c index 6631ebf..9dcc2b0 100644 --- a/chatty.c +++ b/chatty.c @@ -1,5 +1,5 @@ #define TB_IMPL -#include "termbox2.h" +#include "external/termbox2.h" #include #include @@ -12,7 +12,7 @@ #define TIMEOUT_POLL 60 * 1000 // time to reconnect in seconds #define TIMEOUT_RECONNECT 1 -#define INPUT_LIMIT 512 +#define MAX_INPUT_LEN 512 // Filepath where user ID is stored #define ID_FILE "_id" // Filepath where logged @@ -26,7 +26,7 @@ #include "chatty.h" #include "protocol.h" -#include "ui.c" +#include "ui.h" enum { FDS_BI = 0, // for one-way communication with the server (eg. TextMessage) FDS_UNI, // For two-way communication with the server (eg. IDMessage) @@ -273,6 +273,7 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) return Result; } + // home screen, the first screen the user sees // it displays a prompt with the user input of input_len wide characters // and the received messages from msgsArena @@ -280,28 +281,18 @@ void screen_home(Arena* ScratchArena, Arena* MessagesArena, u32 MessagesNum, Arena* ClientsArena, struct pollfd* fds, - u32 Input[], u32 InputLen) + wchar_t Input[], u32 InputLen) { - // config options - const s32 box_max_len = 80; - const s32 box_x = 0, box_y = global.height - 3, box_pad_x = 1, box_mar_x = 1, box_bwith = 1, box_height = 3; - const u32 prompt_x = box_x + box_pad_x + box_mar_x + box_bwith + InputLen; - - // 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)) + u32 BoxHeight = 3; + u32 BoxWidth = global.width - 1; + + if (global.height < BoxHeight || + global.width < 8) { - // + 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: @@ -312,7 +303,7 @@ screen_home(Arena* ScratchArena, { s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2; - u32 FreeHeight = global.height - box_height; + u32 FreeHeight = global.height - BoxHeight; if (FreeHeight <= 0) goto draw_prompt; @@ -395,9 +386,9 @@ screen_home(Arena* ScratchArena, // Only display when there is enough space if (global.width > VerticalBarOffset + 2) { - raw_result RawText = markdown_to_raw(ScratchArena, (u32*)&message->text, message->len); + raw_result RawText = markdown_to_raw(ScratchArena, (wchar_t*)&message->text, message->len); markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena, - (u32*)&message->text, + (wchar_t*)&message->text, message->len); u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0, @@ -455,67 +446,11 @@ screen_home(Arena* ScratchArena, tb_print(VerticalBarOffset, Y, 0, 0, "│"); draw_prompt: - // 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. - { - u32 box_len = 0; - if (global.width >= box_max_len + 2 * box_mar_x) - box_len = box_max_len; - else - box_len = global.width - box_mar_x * 2; - - // +2 for corners and null terminator - u32 box_up[box_len + 1]; - u32 box_in[box_len + 1]; - u32 box_down[box_len + 1]; - u32 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 (InputLen > freesp) - { - u32* text_offs = Input + (InputLen - 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); - } - } + InputBox(0, FreeHeight, BoxWidth, BoxHeight, + Input, InputLen, + True); + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); + if (fds[FDS_UNI].fd == -1 || fds[FDS_BI].fd == -1) { @@ -544,8 +479,8 @@ main(int argc, char** argv) u32 MessagesNum = 0; // Number of messages in msgsArena s32 nrecv = 0; // number of bytes received - u32 Input[INPUT_LIMIT] = {0}; // input buffer - u32 InputLen = 0; // number of characters in input + wchar_t Input[MAX_INPUT_LEN] = {0}; // input buffer + u32 InputIndex = 0; // number of characters in input Arena ScratchArena; Arena MessagesArena; @@ -632,7 +567,10 @@ main(int argc, char** argv) tb_init(); tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd); - screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputLen); + screen_home(&ScratchArena, + &MessagesArena, MessagesNum, + &ClientsArena, fds, + Input, InputIndex); tb_present(); // main loop @@ -703,24 +641,24 @@ main(int argc, char** argv) { case TB_KEY_CTRL_W: // delete consecutive whitespace - while (InputLen) + while (InputIndex) { - if (Input[InputLen - 1] == L' ') + if (Input[InputIndex - 1] == L' ') { - Input[InputLen - 1] = 0; - InputLen--; + Input[InputIndex - 1] = 0; + InputIndex--; continue; } break; } // delete until whitespace - while (InputLen) + while (InputIndex) { - if (Input[InputLen - 1] == L' ') + if (Input[InputIndex - 1] == L' ') break; // erase - Input[InputLen - 1] = 0; - InputLen--; + Input[InputIndex - 1] = 0; + InputIndex--; } break; case TB_KEY_CTRL_Z: @@ -732,7 +670,7 @@ main(int argc, char** argv) } break; case TB_KEY_CTRL_Y: // Paste clipboard contents to input { - u32 OutputBufferLen = INPUT_LIMIT - InputLen; + u32 OutputBufferLen = MAX_INPUT_LEN - InputIndex; if (OutputBufferLen <= 0) break; u8 OutputBuffer[OutputBufferLen]; @@ -758,28 +696,31 @@ main(int argc, char** argv) { // convert u8 to u32 u32 ch = OutputBuffer[BufferIndex]; - Input[InputLen] = ch; - InputLen++; + Input[InputIndex] = ch; + InputIndex++; } } break; case TB_KEY_CTRL_I: { for (u32 i = 0; - i < TAB_WIDTH && InputLen < INPUT_LIMIT - 1; + i < TAB_WIDTH && InputIndex < MAX_INPUT_LEN - 1; i++) { - Input[InputLen] = L' '; - InputLen++; + Input[InputIndex] = L' '; + InputIndex++; } } break; + case TB_KEY_BACKSPACE2: + if (InputIndex) InputIndex--; + break; case TB_KEY_CTRL_D: case TB_KEY_CTRL_C: quit = 1; break; case TB_KEY_CTRL_M: // send message { - raw_result RawText = markdown_to_raw(0, Input, InputLen); + raw_result RawText = markdown_to_raw(0, Input, InputIndex); if (RawText.Len == 0) // do not send empty message @@ -789,8 +730,8 @@ main(int argc, char** argv) break; // null terminate - Input[InputLen] = 0; - InputLen++; + Input[InputIndex] = 0; + InputIndex++; // Save header HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header)); @@ -801,9 +742,9 @@ main(int argc, char** argv) // Save message TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE); sendmsg->timestamp = time(0); - sendmsg->len = InputLen; + sendmsg->len = InputIndex; - u32 text_size = InputLen * sizeof(*Input); + u32 text_size = InputIndex * sizeof(*Input); ArenaPush(&MessagesArena, text_size); memcpy(&sendmsg->text, Input, text_size); @@ -813,20 +754,20 @@ main(int argc, char** argv) // also clear input } // fallthrough case TB_KEY_CTRL_U: // clear input - bzero(Input, InputLen * sizeof(*Input)); - InputLen = 0; + bzero(Input, InputIndex * sizeof(*Input)); + InputIndex = 0; break; default: if (ev.ch == 0) break; // TODO: show error - if (InputLen == INPUT_LIMIT - 1) // last byte reserved for \0 + if (InputIndex == MAX_INPUT_LEN - 1) // last byte reserved for \0 break; // append key to input buffer - Input[InputLen] = ev.ch; - InputLen++; + Input[InputIndex] = ev.ch; + InputIndex++; } if (quit) break; @@ -839,7 +780,7 @@ main(int argc, char** argv) tb_poll_event(&ev); } - screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputLen); + screen_home(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); tb_present(); } diff --git a/ui.c b/ui.c deleted file mode 100644 index 8d7cab4..0000000 --- a/ui.c +++ /dev/null @@ -1,296 +0,0 @@ -// Format option at a position in raw text, used when iterating to know when to toggle a color -// option. -typedef struct { - u32 Position; - u32 Color; -} format_option; - -// Array of format options and length of said array -typedef struct { - format_option* Options; - u32 Len; -} markdown_formatoptions; - -typedef struct { - u32* Text; - u32 Len; -} raw_result; - -// Return True if ch is whitespace -Bool -is_whitespace(u32 ch) -{ - if (ch == L' ') - return True; - return False; -} - -// Return True if ch is a supported markdown markup character -// TODO: tilde -Bool -is_markdown(u32 ch) -{ - if (ch == L'_' || - ch == L'*') - return True; - return False; -} - -// Print `Text`, `Len` characters long with markdown -// NOTE: This function has no wrapping support -void -tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len) -{ - for (u32 ch = 0; ch < Len; ch++) - { - if (Text[ch] == L'_') - { - if (ch < Len - 1 && Text[ch + 1] == L'_') - { - fg ^= TB_UNDERLINE; - ch++; - } - else - { - fg ^= TB_ITALIC; - } - } - else if (Text[ch] == L'*') - { - if (ch < Len - 1 && Text[ch + 1] == L'*') - { - fg ^= TB_BOLD; - ch++; - } - else - { - fg ^= TB_ITALIC; - } - } - else - { - tb_printf(X, Y, fg, bg, "%lc", Text[ch]); -#ifdef DEBUG - tb_present(); -#endif - X++; - } - } -} - -// Print `Text`, `Len` characters long as a string wrapped at `XLimit` width and `YLimit` height. -void -tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len) -{ - // Iterator in text - assert(XLimit > 0); - assert(YLimit > 0); - u32 i = XLimit; - - u32 PrevI = 0; - - // For printing - u32 t = 0; - - while(i < Len) - { - // Search backwards for whitespace - while (!is_whitespace(Text[i])) - { - i--; - - // Failed to find whitespace, break on limit at character - if (i == PrevI) - { - i += XLimit; - break; - } - } - - t = Text[i]; - Text[i] = 0; - tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); -#ifdef DEBUG - tb_present(); -#endif - - Text[i] = t; - - if (is_whitespace(Text[i])) i++; - - PrevI = i; - i += XLimit; - - if (Y >= YLimit - 1) - { - break; - } - } - tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); -} - -// Print raw string with markdown format options in `MDFormat`, wrapped at -// `XLimit` and `YLimit`. The string is offset by `XOffset` and `YOffset`. -// `fg` and `bg` are passed to `tb_printf`. -// `Len` is the length of the string not including a null terminator -// The wrapping algorithm searches for a whitespace backwards and if none are found it wraps at -// `XLimit`. -// This function first builds an array of positions where to wrap and then prints `Text` by -// character using the array in `MDFormat.Options` and `WrapPositions` to know when to act. -// Returns how many times wrapped -u32 -tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, - u32* Text, u32 Len, - u32 XLimit, u32 YLimit, - markdown_formatoptions MDFormat) -{ - XLimit -= XOffset; - YLimit -= YOffset; - assert(YLimit > 0); - assert(XLimit > 0); - - u32 TextIndex = XLimit; - u32 PrevTextIndex = 0; - - u32 WrapPositions[Len/XLimit + 1]; - u32 WrapPositionsLen = 0; - - // Get wrap positions - while (TextIndex < Len) - { - while (!is_whitespace(Text[TextIndex])) - { - TextIndex--; - - if (TextIndex == PrevTextIndex) - { - TextIndex += XLimit; - break; - } - } - - WrapPositions[WrapPositionsLen] = TextIndex; - WrapPositionsLen++; - - PrevTextIndex = TextIndex; - TextIndex += XLimit; - } - - u32 MDFormatOptionsIndex = 0; - u32 WrapPositionsIndex = 0; - u32 X = XOffset, Y = YOffset; - - for (u32 TextIndex = 0; TextIndex < Len; TextIndex++) - { - if (MDFormatOptionsIndex < MDFormat.Len && - TextIndex == MDFormat.Options[MDFormatOptionsIndex].Position) - { - fg ^= MDFormat.Options[MDFormatOptionsIndex].Color; - MDFormatOptionsIndex++; - } - if (WrapPositionsIndex < WrapPositionsLen && - TextIndex == WrapPositions[WrapPositionsIndex]) - { - Y++; - if (Y == YLimit) return WrapPositionsIndex + 1; - WrapPositionsIndex++; - X = XOffset; - if (is_whitespace(Text[TextIndex])) continue; - } - tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]); - } - assert(WrapPositionsIndex == WrapPositionsLen); - assert(MDFormat.Len == MDFormatOptionsIndex); - - return WrapPositionsLen + 1; -} - -// Return string without markdown markup characters using `is_markdown()` -// ScratchArena is used to allocate space for the raw text -// If ScratchArena is null then it will only return then length of the raw string -// Len should be characters + null terminator -// Copies the null terminator as well -raw_result -markdown_to_raw(Arena* ScratchArena, u32* Text, u32 Len) -{ - raw_result Result = {0}; - if (ScratchArena) - { - Result.Text = ScratchArena->addr; - } - - for (u32 i = 0; i < Len; i++) - { - if (!is_markdown(Text[i])) - { - if (ScratchArena) - { - u32* ch = ArenaPush(ScratchArena, sizeof(*ch)); - *ch = Text[i]; - } - Result.Len++; - } - } - - return Result; -} - -// Get a string with markdown in it and fill array in makrdown_formtoptions with position and colors -// Use Scratcharena to make allocations on that buffer, The Maximimum space needed is Len, eg. when -// the string is only markup characters. -markdown_formatoptions -preprocess_markdown(Arena* ScratchArena, u32* Text, u32 Len) -{ - markdown_formatoptions Result = {0}; - Result.Options = (format_option*)((u8*)ScratchArena->addr + ScratchArena->pos); - - format_option* FormatOpt; - - // raw char iterator - u32 rawch = 0; - - for (u32 i = 0; i < Len; i++) - { - switch (Text[i]) - { - case L'_': - { - FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); - Result.Len++; - - FormatOpt->Position = rawch; - if (i < Len - 1 && Text[i + 1] == '_') - { - FormatOpt->Color = TB_UNDERLINE; - i++; - } - else - { - FormatOpt->Color = TB_ITALIC; - } - } break; - case L'*': - { - FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); - Result.Len++; - - FormatOpt->Position = rawch; - if (i < Len - 1 && Text[i + 1] == '*') - { - FormatOpt->Color = TB_BOLD; - i++; - } - else - { - FormatOpt->Color = TB_ITALIC; - } - } break; - default: - { - rawch++; - } break; - } - } - - return Result; -} diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..ff98598 --- /dev/null +++ b/ui.h @@ -0,0 +1,426 @@ +// Format option at a position in raw text, used when iterating to know when to toggle a color +// option. +typedef struct { + u32 Position; + u32 Color; +} format_option; + +// Array of format options and length of said array +typedef struct { + format_option* Options; + u32 Len; +} markdown_formatoptions; + +typedef struct { + u32* Text; + u32 Len; +} raw_result; + +// Return True if ch is whitespace +Bool +is_whitespace(u32 ch) +{ + if (ch == L' ') + return True; + return False; +} + +// Return True if ch is a supported markdown markup character +// TODO: tilde +Bool +is_markdown(u32 ch) +{ + if (ch == L'_' || + ch == L'*') + return True; + return False; +} + +// Print `Text`, `Len` characters long with markdown +// NOTE: This function has no wrapping support +void +tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len) +{ + for (u32 ch = 0; ch < Len; ch++) + { + if (Text[ch] == L'_') + { + if (ch < Len - 1 && Text[ch + 1] == L'_') + { + fg ^= TB_UNDERLINE; + ch++; + } + else + { + fg ^= TB_ITALIC; + } + } + else if (Text[ch] == L'*') + { + if (ch < Len - 1 && Text[ch + 1] == L'*') + { + fg ^= TB_BOLD; + ch++; + } + else + { + fg ^= TB_ITALIC; + } + } + else + { + tb_printf(X, Y, fg, bg, "%lc", Text[ch]); +#ifdef DEBUG + tb_present(); +#endif + X++; + } + } +} + +// Print `Text`, `Len` characters long as a string wrapped at `XLimit` width and `YLimit` height. +void +tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len) +{ + // Iterator in text + assert(XLimit > 0); + assert(YLimit > 0); + u32 i = XLimit; + + u32 PrevI = 0; + + // For printing + u32 t = 0; + + while(i < Len) + { + // Search backwards for whitespace + while (!is_whitespace(Text[i])) + { + i--; + + // Failed to find whitespace, break on limit at character + if (i == PrevI) + { + i += XLimit; + break; + } + } + + t = Text[i]; + Text[i] = 0; + tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); +#ifdef DEBUG + tb_present(); +#endif + + Text[i] = t; + + if (is_whitespace(Text[i])) i++; + + PrevI = i; + i += XLimit; + + if (Y >= YLimit - 1) + { + break; + } + } + tb_printf(X, Y++, 0, 0, "%ls", Text + PrevI); +} + +// Print raw string with markdown format options in `MDFormat`, wrapped at +// `XLimit` and `YLimit`. The string is offset by `XOffset` and `YOffset`. +// `fg` and `bg` are passed to `tb_printf`. +// `Len` is the length of the string not including a null terminator +// The wrapping algorithm searches for a whitespace backwards and if none are found it wraps at +// `XLimit`. +// This function first builds an array of positions where to wrap and then prints `Text` by +// character using the array in `MDFormat.Options` and `WrapPositions` to know when to act. +// Returns how many times wrapped +u32 +tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg, + u32* Text, u32 Len, + u32 XLimit, u32 YLimit, + markdown_formatoptions MDFormat) +{ + XLimit -= XOffset; + YLimit -= YOffset; + assert(YLimit > 0); + assert(XLimit > 0); + + u32 TextIndex = XLimit; + u32 PrevTextIndex = 0; + + u32 WrapPositions[Len/XLimit + 1]; + u32 WrapPositionsLen = 0; + + // Get wrap positions + while (TextIndex < Len) + { + while (!is_whitespace(Text[TextIndex])) + { + TextIndex--; + + if (TextIndex == PrevTextIndex) + { + TextIndex += XLimit; + break; + } + } + + WrapPositions[WrapPositionsLen] = TextIndex; + WrapPositionsLen++; + + PrevTextIndex = TextIndex; + TextIndex += XLimit; + } + + u32 MDFormatOptionsIndex = 0; + u32 WrapPositionsIndex = 0; + u32 X = XOffset, Y = YOffset; + + for (u32 TextIndex = 0; TextIndex < Len; TextIndex++) + { + if (MDFormatOptionsIndex < MDFormat.Len && + TextIndex == MDFormat.Options[MDFormatOptionsIndex].Position) + { + fg ^= MDFormat.Options[MDFormatOptionsIndex].Color; + MDFormatOptionsIndex++; + } + if (WrapPositionsIndex < WrapPositionsLen && + TextIndex == WrapPositions[WrapPositionsIndex]) + { + Y++; + if (Y == YLimit) return WrapPositionsIndex + 1; + WrapPositionsIndex++; + X = XOffset; + if (is_whitespace(Text[TextIndex])) continue; + } + tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]); + } + assert(WrapPositionsIndex == WrapPositionsLen); + assert(MDFormat.Len == MDFormatOptionsIndex); + + return WrapPositionsLen + 1; +} + +// Return string without markdown markup characters using `is_markdown()` +// ScratchArena is used to allocate space for the raw text +// If ScratchArena is null then it will only return then length of the raw string +// Len should be characters + null terminator +// Copies the null terminator as well +raw_result +markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len) +{ + raw_result Result = {0}; + if (ScratchArena) + { + Result.Text = ScratchArena->addr; + } + + for (u32 i = 0; i < Len; i++) + { + if (!is_markdown(Text[i])) + { + if (ScratchArena) + { + u32* ch = ArenaPush(ScratchArena, sizeof(*ch)); + *ch = Text[i]; + } + Result.Len++; + } + } + + return Result; +} + +// Get a string with markdown in it and fill array in makrdown_formtoptions with position and colors +// Use Scratcharena to make allocations on that buffer, The Maximimum space needed is Len, eg. when +// the string is only markup characters. +markdown_formatoptions +preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len) +{ + markdown_formatoptions Result = {0}; + Result.Options = (format_option*)((u8*)ScratchArena->addr + ScratchArena->pos); + + format_option* FormatOpt; + + // raw char iterator + u32 rawch = 0; + + for (u32 i = 0; i < Len; i++) + { + switch (Text[i]) + { + case L'_': + { + FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); + Result.Len++; + + FormatOpt->Position = rawch; + if (i < Len - 1 && Text[i + 1] == '_') + { + FormatOpt->Color = TB_UNDERLINE; + i++; + } + else + { + FormatOpt->Color = TB_ITALIC; + } + } break; + case L'*': + { + FormatOpt = ArenaPush(ScratchArena, sizeof(*FormatOpt)); + Result.Len++; + + FormatOpt->Position = rawch; + if (i < Len - 1 && Text[i + 1] == '*') + { + FormatOpt->Color = TB_BOLD; + i++; + } + else + { + FormatOpt->Color = TB_ITALIC; + } + } break; + default: + { + rawch++; + } break; + } + } + + return Result; +} + +void +InputBox(u32 BoxX, u32 BoxY, u32 BoxWidth, u32 BoxHeight, + wchar_t *Text, u32 TextLen, + Bool Focused) +{ + // ╭───────╮ + // M│P....█P│M + // ╰───────╯ + // . -> text + // █ -> cursor + // P -> padding (symmetric) + // M -> margin (symmetric) + + // Configuration options + u32 MarginX = 1; + u32 PaddingX = 1; + u32 MinTextSpace = 3; + u32 BorderWidth = 1; + + // TODO: return early if there is not enough space + if (BoxHeight < 3) + return; + if (BoxWidth < MarginX * 2 + PaddingX * 2 + MinTextSpace) + return; + + // Get 0-based coordinate + BoxWidth -= 2* MarginX; + BoxHeight--; + + wchar_t ur = L'╭'; + wchar_t ru = L'╯'; + wchar_t rd = L'╮'; + wchar_t dr = L'╰'; + wchar_t lr = L'─'; + wchar_t ud = L'│'; + + // Draw Borders + { + // Draw the top bar + tb_printf(BoxX + MarginX, BoxY, 0, 0, "%lc", ur); + for (u32 X = 1; + X < BoxWidth; + X++) + { + tb_printf(BoxX + X + MarginX, BoxY, 0, 0, "%lc", lr); + } + tb_printf(BoxX + BoxWidth + MarginX, BoxY, 0, 0, "%lc", rd); + + // Draw vertical bars + for (u32 Y = 1; + Y < BoxHeight; + Y++) + { + tb_printf(BoxX + MarginX, BoxY + Y, 0, 0, "%lc", ud); + tb_printf(BoxX + BoxWidth + MarginX, BoxY + Y, 0, 0, "%lc", ud); + } + + // Draw the bottom bar + tb_printf(BoxWidth + MarginX, BoxY + BoxHeight, 0, 0, "%lc", ru); + for (u32 X = 1; + X < BoxWidth; + X++) + { + tb_printf(BoxX + X + MarginX, BoxY + BoxHeight, 0, 0, "%lc", lr); + } + tb_printf(BoxX + MarginX, BoxY + BoxHeight, 0, 0, "%lc", dr); + } + + // Draw the text + u32 TextX = BoxX + MarginX + PaddingX + 1; + u32 TextY = BoxY + 1; + u32 TextWidth = BoxWidth - TextX - MarginX + 1; + u32 TextHeight = BoxHeight - BorderWidth * 2 + 1; + u32 TextDisplaySize = TextWidth * TextHeight; + + u32 At = 0; + // If there is not enough space to fit the text scroll one line by advancing by textwidth. + if (TextLen >= TextDisplaySize) + { + // TextHeight - 1 : scroll by one line + At = (TextLen / TextWidth - (TextHeight - 1)) * TextWidth; + } + +#ifdef DEBUG + tb_printf(BoxX + 1, BoxY, 0, 0, "%d/%d %dx%d %d", TextLen, MAX_INPUT_LEN, TextWidth, TextHeight, At); +#endif + + // Keep if needed for cursor position + u32 XOffset = 0, YOffset = 0; + while (At < TextLen) + { + for (YOffset = 0; + YOffset < TextHeight && At < TextLen; + YOffset++) + { + for (XOffset = 0; + XOffset < TextWidth && At < TextLen; + XOffset++) + { + tb_printf(TextX + XOffset, TextY + YOffset, 0, 0, "%lc", Text[At]); + At++; + } + } + } + + // Set the cursor + if (Focused) + { + if (TextLen == 0) + { + // When there is no text + global.cursor_x = TextX; + global.cursor_y = TextY; + } + else if (TextLen % TextWidth == 0) + { + // When at the end of width put the cursor on the next line + global.cursor_x = TextX; + global.cursor_y = TextY + YOffset; + } + else + { + // Put cursor after the text + // Minus one because of the for loop + global.cursor_x = (TextX-1) + XOffset + 1; + global.cursor_y = (TextY-1) + YOffset; + } + } +} -- cgit v1.2.3