diff options
Diffstat (limited to 'ui.h')
-rw-r--r-- | ui.h | 426 |
1 files changed, 426 insertions, 0 deletions
@@ -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; + } + } +} |