diff options
author | Raymaekers Luca <luca@spacehb.net> | 2025-04-27 12:52:06 +0200 |
---|---|---|
committer | Raymaekers Luca <luca@spacehb.net> | 2025-04-27 13:05:34 +0200 |
commit | f87f7b4f0aaccc65d03ccee5bb11915ead6fb0e1 (patch) | |
tree | d54df0bfde3dbffa02b1f138af4f12456f261e54 /ui.h | |
parent | 0574f5a7c5159a2ae1d7d2182cec982509947db9 (diff) |
First pass at preparing for Github
Diffstat (limited to 'ui.h')
-rw-r--r-- | ui.h | 819 |
1 files changed, 0 insertions, 819 deletions
@@ -1,819 +0,0 @@ -#ifndef UI_H -#define UI_H - -/* Macro's */ - -#include <stdint.h> -#include <stdbool.h> -#include "termbox2.h" -#include "arena.h" - -/* Types */ - -// 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; - -// Rectangle -typedef struct { - s32 X, Y, W, H; -} rect; - -// Characters to use for drawing a box -// See DrawBox() for an example -typedef struct { - wchar_t ur, ru, rd, dr, lr, ud; -} box_characters; - -/* Functions */ - -bool IsInRect(rect Rect, s32 X, s32 Y); -bool is_whitespace(u32 ch); -bool is_markdown(u32 ch); -void tb_print_wrapped(u32 X, u32 Y, u32 XLimit, u32 YLimit, u32* Text, u32 Len); -void tb_print_markdown(u32 X, u32 Y, u32 fg, u32 bg, u32* Text, u32 Len); -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); -raw_result markdown_to_raw(Arena* ScratchArena, wchar_t* Text, u32 Len); -markdown_formatoptions preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len); - -/* Input Box UI */ - -// assumes TEXTBOX_MAX_INPUT to be set -// -// #define TEXTBOX_MAX_INPUT 128 - -#define TEXTBOX_PADDING_X 1 -#define TEXTBOX_BORDER_WIDTH 1 -#define TEXTBOX_MIN_WIDTH TEXTBOX_PADDING_X * 2 + TEXTBOX_BORDER_WIDTH * 2 + 1; -#define TEXTBOXFROMBOX(Box) \ - { \ - .X = Box.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X, \ - .Y = Box.Y + TEXTBOX_BORDER_WIDTH, \ - .W = Box.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2, \ - .H = Box.H - TEXTBOX_BORDER_WIDTH * 2 \ - } - -void DrawBox(rect Rect, box_characters *Chars); -void DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen); -void DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen); -void TextBoxScrollLeft(rect Text, u32 *TextOffset); -void TextBoxScrollRight(rect Text, u32 *TextOffset); -void TextBoxDelete(wchar_t* Text, u64 Pos); -void TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch); -u32 TextBoxKeypress(struct tb_event ev, - rect TextR, wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr); - -// Draw box along boundaries in Rect with optional Chars. -void -DrawBox(rect Rect, box_characters *Chars) -{ - wchar_t ur, ru, rd, dr, lr, ud; - if (!Chars) - { - ur = L'╭'; - ru = L'╯'; - rd = L'╮'; - dr = L'╰'; - lr = L'─'; - ud = L'│'; - } - else - { - ur = Chars->ur; - ru = Chars->ru; - rd = Chars->rd; - dr = Chars->dr; - lr = Chars->lr; - ud = Chars->ud; - } - - Rect.H--; - Rect.W--; - - tb_printf(Rect.X, Rect.Y, 0, 0, "%lc", ur); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y, 0, 0, "%lc", rd); - - // Draw vertical bars - for (s32 Y = 1; Y < Rect.H; Y++) - { - tb_printf(Rect.X, Rect.Y + Y, 0, 0, "%lc", ud); - tb_printf(Rect.X + Rect.W, Rect.Y + Y, 0, 0, "%lc", ud); - } - - tb_printf(Rect.X, Rect.Y + Rect.H, 0, 0, "%lc", dr); - for (s32 X = 1; X < Rect.W; X++) - { - tb_printf(Rect.X + X, Rect.Y + Rect.H, 0, 0, "%lc", lr); - } - tb_printf(Rect.X + Rect.W, Rect.Y + Rect.H, 0, 0, "%lc", ru); -} - -// SCROLLING -// ╭──────────╮ ╭──────────╮ Going Left on the first character scrolls up. -// │ █3 4 │ => │ 1 2█ │ Cursor on end of the top line. -// │ 5 6 │ │ 3 4 │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Right on the last character scrolls down. -// │ 1 3 │ => │ 2 4 │ Puts cursor on start of the bottom line. -// │ 2 4█ │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Down on bottom line scrolls down. -// │ 1 3 │ => │ 2 4 │ Cursor stays on bottom line. -// │ 2 █ 4 │ │ █ │ -// ╰──────────╯ ╰──────────╯ -// -// ╭──────────╮ ╭──────────╮ Going Up on top line scrolls up. -// │ 3 █ 4 │ => │ 1 █ 2 │ Cursor stays on top line. -// │ 5 6 │ │ 3 5 │ -// ╰──────────╯ ╰──────────╯ -// -// In code this translates to changing global.cursor_{x,y} and TextOffset accordingly. - -// Scroll one character to the left -void -TextBoxScrollLeft(rect Text, u32 *TextOffset) -{ - // If text is on the first character of the box scroll up - if (global.cursor_x == Text.X && - global.cursor_y == Text.Y) - { - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y = Text.Y; - - *TextOffset -= Text.W; - } - else - { - if (global.cursor_x == Text.X) - { - // Got to previous line - global.cursor_x = Text.X + Text.W - 1; - global.cursor_y--; - } - else - { - global.cursor_x--; - } - } -} - -// Scroll one character to the right -void -TextBoxScrollRight(rect Text, u32 *TextOffset) -{ - // If cursor is on the last character scroll forwards - if (global.cursor_x == Text.X + Text.W - 1 && - global.cursor_y == Text.Y + Text.H - 1) - { - global.cursor_x = Text.X; - global.cursor_y = Text.Y + Text.H - 1; - - *TextOffset += Text.W; - } - else - { - global.cursor_x++; - if (global.cursor_x == Text.X + Text.W) - { - global.cursor_x = Text.X; - global.cursor_y++; - } - } -} - - -// Delete a character in Text at Pos -void -TextBoxDelete(wchar_t* Text, u64 Pos) -{ - memmove(Text + Pos, - Text + Pos + 1, - (TEXTBOX_MAX_INPUT - Pos - 1) * sizeof(*Text)); -} - -// Insert a ev.ch in Input at InputPos -void -TextBoxInsert(wchar_t *Input, u32 InputPos, u32 InputLen, wchar_t ch) -{ - if (InputPos < InputLen) - { - memmove(Input + InputPos, - Input + InputPos - 1, - (InputLen - InputPos + 1) * sizeof(*Input)); - } - Input[InputPos] = ch; -} - -// Handle the key event ev changing Text, TextLenPtr, and TextOffsetPtr accordingly. -// InputPos is the position in the Input relating to the cursor position. -// TextR is the bounding box for the text. -// -// Returns non-zero when a key event was handled. -// -// TODO: pass by value and return struct with updated values -u32 -TextBoxKeypress(struct tb_event ev, rect TextR, - wchar_t *Text, u32 *TextLenPtr, u32 TextPos, u32 *TextOffsetPtr) -{ - u32 Result = 1; - - u32 TextLen = *TextLenPtr; - u32 TextOffset = *TextOffsetPtr; - - switch (ev.key) - { - - // Delete character backwards - case TB_KEY_CTRL_8: - // case TB_KEY_BACKSPACE2: - { - if (TextPos == 0) break; - - TextBoxDelete(Text, TextPos - 1); - TextLen--; - - TextBoxScrollLeft(TextR, &TextOffset); - - } break; - - // Delete character forwards - case TB_KEY_CTRL_D: - { - if (TextPos == TextLen) break; - TextBoxDelete(Text, TextPos); - TextLen--; - // Delete(Text, Position) - } break; - - // Delete word backwards - case TB_KEY_CTRL_W: - { - u32 At = TextPos; - // Find character to stop on - while (At && is_whitespace(Text[At - 1])) At--; - while (At && !is_whitespace(Text[At - 1])) At--; - - s32 NDelete = TextPos - At; - memmove(Text + At, Text + TextPos, (TextLen - TextPos) * sizeof(Text[At])); - TextLen -= NDelete; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - // NOTE: this could be calculated at once instead - while(NDelete--) TextBoxScrollLeft(TextR, &TextOffset); - - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - - } break; - - // Delete until start of Text - case TB_KEY_CTRL_U: - { - memmove(Text, Text + TextPos, (TextLen - TextPos) * sizeof(*Text)); - TextLen -= TextPos; -#ifdef DEBUG - Text[TextLen] = 0; -#endif - global.cursor_x = TextR.X; - global.cursor_y = TextR.Y; - TextOffset = 0; - } break; - - // Delete until end of Text - case TB_KEY_CTRL_K: - { - TextLen = TextPos; - Text[TextPos] = 0; - } break; - - // Move to start of line - case TB_KEY_CTRL_A: global.cursor_x = TextR.X; break; - - // Move to end of line - case TB_KEY_CTRL_E: - { - if (global.cursor_x == TextR.X + TextR.W - 1) break; - - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % TextR.W; - } - else - { - global.cursor_x = TextR.X + TextR.W - 1; - } - } break; - - // Move backwards - case TB_KEY_CTRL_B: - case TB_KEY_ARROW_LEFT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At && is_whitespace(Text[At])) At--; - while(At && !is_whitespace(Text[At])) At--; - while(TextPos - At++) TextBoxScrollLeft(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == 0) break; - TextBoxScrollLeft(TextR, &TextOffset); - } - } break; - - // Move forwards - case TB_KEY_CTRL_F: - case TB_KEY_ARROW_RIGHT: - { - // Move forward by word - if (ev.mod == TB_MOD_CTRL) - { - u32 At = TextPos; - while(At < TextLen && is_whitespace(Text[At])) At++; - while(At < TextLen && !is_whitespace(Text[At])) At++; - while(At-- - TextPos) TextBoxScrollRight(TextR, &TextOffset); - } - // Move forward by character - else - { - if (TextPos == TextLen) break; - TextBoxScrollRight(TextR, &TextOffset); - } - } break; - - // Move up - case TB_KEY_CTRL_P: - case TB_KEY_ARROW_UP: - { - if (global.cursor_y == TextR.Y) - { - if (TextOffset == 0) - { - global.cursor_x = TextR.X; - - break; - } - - TextOffset -= TextR.W; - global.cursor_y = TextR.Y; - } - else - { - global.cursor_y--; - } - } break; - - // Move down - case TB_KEY_CTRL_N: - case TB_KEY_ARROW_DOWN: - { - if (TextPos + TextR.W > TextLen) - { - // Put the cursor on the last character - global.cursor_x = TextR.X + (TextLen - TextOffset) % (TextR.W); - global.cursor_y = TextR.Y + (TextLen - TextOffset) / TextR.W; - - // If cursor ended 1 line under the bottom line this means that the text - // needs to be scrolled. - if (global.cursor_y == TextR.Y + TextR.H) - { - TextOffset += TextR.W; - global.cursor_y--; - } - - break; - } - - if (global.cursor_y == TextR.Y + TextR.H - 1) - { - TextOffset += TextR.W; - } - else - { - global.cursor_y++; - } - } break; - default: - { - Result = 0; - } - } - - *TextLenPtr = TextLen; - *TextOffsetPtr = TextOffset; - - return Result; -} - -// Draws characters from Text fitting in the TextR rectangle. -// InputLen is the amount of characters in Text. -// -// NOTE: TextR is always filled, when not enough characters in Input it will uses spaces instead. -// This makes it easy to update the textbox by recalling this function. -void -DrawTextBox(rect TextR, wchar_t *Text, u32 TextLen) -{ - // Draw the text right of the cursor - // NOTE: the cursor is assumed to be in the box - Assert(IsInRect(TextR, global.cursor_x, global.cursor_y)); - s32 AtX = TextR.X, AtY = TextR.Y; - u32 At = 0; - while (AtY < TextR.Y + TextR.H) - { - if (At < TextLen) - { - tb_printf(AtX++, AtY, 0, 0, "%lc", Text[At++]); - } - else - { - tb_printf(AtX++, AtY, 0, 0, " "); - } - - if (AtX == TextR.X + TextR.W) { AtY++; AtX = TextR.X; } - } -} - -// NOTE: To ensure that the text looks the same even when scrolling it you must provide the whole text, -// wrap the whole text and the only show the portion that can fit in the Text Rectangle. - -// When line will exceed width break the word on the next line. This is done by looking backwards -// for whitespace from TextR.W width. When a whitespace is found text is wrapped on the next line. -// TODO: this does not work yet. -void -DrawTextBoxWrapped(rect TextR, wchar_t *Text, u32 TextLen) -{ - if (TextLen <= TextR.W) - { - tb_printf(TextR.X, TextR.Y, 0, 0, "%ls", Text); - tb_present(); - global.cursor_x = TextR.X + TextLen; - global.cursor_y = TextR.Y; - return; - } - - u32 SearchIndex = TextR.W; - u32 PrevIndex = 0; - u32 Y = TextR.Y; - - while (SearchIndex < TextLen) - { - while (Text[SearchIndex] != ' ') - { - SearchIndex--; - if (SearchIndex == PrevIndex) - { - SearchIndex += TextR.W; - break; - } - } - - // Wrap - wchar_t BreakChar = Text[SearchIndex]; - Text[SearchIndex] = 0; - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - tb_present(); - Text[SearchIndex] = BreakChar; - - if (Y + 1 == TextR.Y + TextR.H) - { - global.cursor_y = Y; - global.cursor_x = TextR.X + (SearchIndex - PrevIndex); - return; - } - Y++; - - if (BreakChar == L' ') - { - SearchIndex++; - } - - PrevIndex = SearchIndex; - SearchIndex += TextR.W; - } - - // This happens when SearchIndex exceeds TextLen but there is still some - // text left to print. We can assume that the text will fit because otherwise it would have - // been wrapped a second time and the loop would have returned. - tb_printf(TextR.X, Y, 0, 0, "%ls", Text + PrevIndex); - // NOTE: this sets the cursor position correctly - - global.cursor_y = Y; - global.cursor_x = TextR.X + TextLen - PrevIndex; -} - -#endif // UI_H - -// Check if coordinate (X,Y) is in rect boundaries -bool -IsInRect(rect Rect, s32 X, s32 Y) -{ - if ((X >= Rect.X && X <= Rect.X + Rect.W) && - (Y >= Rect.Y && Y <= Rect.Y + Rect.H)) return true; - return false; -} - -// 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; -} |