aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaymaekers Luca <raymaekers.luca@gmail.com>2024-12-07 19:17:33 +0100
committerRaymaekers Luca <luca@spacehb.net>2025-01-07 18:51:21 +0100
commit0574f5a7c5159a2ae1d7d2182cec982509947db9 (patch)
tree2a9678fd39bcf8ead03c7ee979caf668f2f17892
parent4aec2e1331650cabb966bbf24ed83f76cbbe9912 (diff)
checkpoint
-rw-r--r--.gitignore4
-rw-r--r--README.md13
-rw-r--r--archived/input_box.c70
-rw-r--r--archived/network_compression.c154
-rw-r--r--archived/ui_checkmark.c87
-rw-r--r--archived/ui_meter.c108
-rw-r--r--archived/ui_selection.c71
-rw-r--r--archived/wrap.c2
-rw-r--r--arena.h53
-rw-r--r--box.c456
-rw-r--r--box_test.c27
-rwxr-xr-xbuild.sh9
-rw-r--r--chatty.c138
-rw-r--r--chatty.h73
-rw-r--r--chatty2.c90
-rw-r--r--gdb_scripts12
-rw-r--r--protocol.h9
-rw-r--r--scroll.md63
-rw-r--r--scrollandwrapped.c225
-rw-r--r--server.c93
-rw-r--r--test.h217
-rw-r--r--tests.c37
-rw-r--r--types.h15
-rw-r--r--ui.h730
-rw-r--r--ui_box.h472
-rw-r--r--ui_wrapped.c82
-rw-r--r--utf8toASCII.c163
27 files changed, 2452 insertions, 1021 deletions
diff --git a/.gitignore b/.gitignore
index 5ee8943..dbd414c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
-chatty
-send
-server
+build/*
external/keyboard
_id
diff --git a/README.md b/README.md
index faf9800..565fc2e 100644
--- a/README.md
+++ b/README.md
@@ -9,12 +9,13 @@ The idea is the following:
- authentication
## client
-- [ ] bug: when connecting two clients of the same account
-- [ ] bug: wrapping does not work and displays nothing if there is no screen space
-- [ ] bug: reconnect does not work when server does not know id
-- [ ] convert tabs to spaces
-- [ ] bug: when using lots of markup characters
-- [ ] newline support
+- [ ] BUG: text is not appearing after typing
+- [ ] BUG: when connecting two clients of the same account
+- [ ] BUG: wrapping does not work and displays nothing if there is no screen space
+- [ ] BUG: reconnect does not work when server does not know id
+- [ ] TODO: Convert tabs to spaces
+- [ ] BUG: when using lots of markup characters
+- [ ] TODO: Newline support
- [ ] resizable box
## server
diff --git a/archived/input_box.c b/archived/input_box.c
new file mode 100644
index 0000000..466c03d
--- /dev/null
+++ b/archived/input_box.c
@@ -0,0 +1,70 @@
+#define Assert(expr) if (!(expr)) { \
+ tb_shutdown(); \
+ raise(SIGTRAP); \
+}
+
+#define TB_IMPL
+#include "termbox2.h"
+#undef TB_IMPL
+
+#define TEXTBOX_MAX_INPUT 255
+
+#define UI_IMPL
+#include "ui.h"
+
+#define ARENA_IMPL
+#include "arena.h"
+
+#include <locale.h>
+
+int
+main(void)
+{
+ struct tb_event ev = {0};
+
+ u32 InputLen = 0;
+ wchar_t Input[TEXTBOX_MAX_INPUT] = {0};
+ rect TextBox = {0, 0, 32, 5};
+ rect TextR = {
+ 2, 1,
+ TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH,
+ TextBox.H - 2*TEXTBOX_BORDER_WIDTH
+ };
+ // Used for scrolling the text. Text before TextOffset will not be printed.
+ u32 TextOffset = 0;
+ // Position in input based on cursor position.
+ u32 InputPos = 0;
+
+ Assert(setlocale(LC_ALL, ""));
+ tb_init();
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+ global.cursor_x = TextR.X;
+ global.cursor_y = TextR.Y;
+
+ DrawBox(TextBox, 0);
+
+ while (ev.key != TB_KEY_CTRL_C)
+ {
+ DrawTextBoxWrapped(TextR, Input + TextOffset, InputLen - TextOffset);
+
+ InputPos = TextOffset + (global.cursor_x - TextR.X) + (global.cursor_y - TextR.Y) * TextR.W;
+ Assert(InputPos <= InputLen);
+
+ tb_present();
+ tb_poll_event(&ev);
+
+ u32 Ret = TextBoxKeypress(ev, TextR, Input, &InputLen, InputPos, &TextOffset);
+
+ u32 ShouldInsert = (!Ret) && (ev.ch && InputLen < TEXTBOX_MAX_INPUT);
+ // Insert new character in Input at InputPos
+ if (ShouldInsert)
+ {
+ TextBoxInsert(Input, InputPos, InputLen++, ev.ch);
+ TextBoxScrollRight(TextR, &TextOffset);
+ }
+ }
+
+
+ tb_shutdown();
+ return 0;
+}
diff --git a/archived/network_compression.c b/archived/network_compression.c
new file mode 100644
index 0000000..eef9e3c
--- /dev/null
+++ b/archived/network_compression.c
@@ -0,0 +1,154 @@
+#include <strings.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+
+size_t
+GetMaximumCompressedOutputSize(size_t FileSize)
+{
+ // TODO: figure out equation
+ return FileSize*2 + 256;
+}
+
+u32
+RLECompress(size_t InSize, u8* In, size_t MaxOutSize, u8* OutBase)
+{
+ u8* Out = OutBase;
+
+#define MAX_LITERAL_COUNT 255
+#define MAX_RUN_COUNT 255
+ u32 LiteralCount = 0;
+ u8 Literals[MAX_LITERAL_COUNT] = {0};
+
+ u8 *InEnd = In + InSize;
+ while(In < InEnd)
+ {
+ u8 StartingValue = In[0];
+ size_t Run = 1; // first one is the character itself
+ while((Run < (InEnd - In)) &&
+ (Run < MAX_RUN_COUNT) &&
+ (In[Run] == StartingValue))
+ {
+ ++Run;
+ }
+
+ if ((Run > 1) ||
+ (LiteralCount == MAX_LITERAL_COUNT)) // stop doing runs when there is no
+ // space left in the buffer.
+ {
+ // Encode a literal/run pair
+ u8 LiteralCount8 = (u8)LiteralCount;
+ assert(LiteralCount8 == LiteralCount);
+ *Out++ = LiteralCount8;
+
+ for(u32 LiteralIndex = 0;
+ LiteralIndex < LiteralCount;
+ ++LiteralIndex)
+ {
+ *Out++ = Literals[LiteralIndex];
+ }
+ LiteralCount = 0;
+
+ u8 Run8 = (u8)Run;
+ assert(Run8 == Run);
+ *Out++ = Run8;
+
+ *Out++ = StartingValue;
+
+ In += Run;
+ }
+ else
+ {
+ // Buffer literals, you have to because we are encoding them in pairs
+ Literals[LiteralCount++] = StartingValue;
+ ++In;
+ }
+
+ }
+#undef MAX_LITERAL_COUNT
+#undef MAX_RUN_COUNT
+
+ assert(In == InEnd);
+
+ size_t OutSize = Out - OutBase;
+ assert(OutSize <= MaxOutSize);
+
+ return OutSize;
+}
+
+void
+RLEDecompress(size_t InSize, u8* In, size_t OutSize, u8* Out)
+{
+ u8 *InEnd = In + InSize;
+ while(In < InEnd)
+ {
+ // TODO: I think Casey made a mistake and this should be an u8
+ u8 LiteralCount = *In++;
+ while(LiteralCount--)
+ {
+ *Out++ = *In++;
+ }
+
+ // Alternate to Run
+ u8 RepCount = *In++;
+ u8 RepValue = *In++;
+ while(RepCount--)
+ {
+ *Out++ = RepValue;
+ }
+ }
+
+ assert(In == InEnd);
+}
+
+int main(int Argc, char* Argv[]) {
+
+ if (Argc < 2)
+ {
+ fprintf(stderr, "Usage: %s [compress|decompress]\n", Argv[0]);
+ return 1;
+ }
+
+ char* Command = Argv[1];
+
+ if (!strcmp(Command, "compress"))
+ {
+ u32* Message = (u32*)L"abcabcbbbbbbb";
+ size_t MessageSize = wcslen((const wchar_t*)Message) * 4;
+
+ size_t OutBufferSize = GetMaximumCompressedOutputSize(MessageSize);
+ u8 OutBuffer[OutBufferSize + 8];
+ bzero(OutBuffer, OutBufferSize + 8);
+ *(u32*)OutBuffer = MessageSize;
+
+ size_t CompressedSize = RLECompress(MessageSize, (u8*)Message, OutBufferSize, OutBuffer + 8);
+
+ s32 OutFile = open("test.compressed", O_WRONLY | O_CREAT, 0600);
+
+ write(OutFile, OutBuffer, CompressedSize);
+
+ fprintf(stdout, "%lu -> %lu bytes\n", MessageSize, CompressedSize);
+ }
+ else if (!strcmp(Command, "decompress"))
+ {
+ fprintf(stderr, "Not implemented yet.\n");
+ }
+ else
+ fprintf(stderr, "Unknown command: '%s'\n", Command);
+
+ return 0;
+}
diff --git a/archived/ui_checkmark.c b/archived/ui_checkmark.c
new file mode 100644
index 0000000..df7e507
--- /dev/null
+++ b/archived/ui_checkmark.c
@@ -0,0 +1,87 @@
+#define TB_IMPL
+#include "../chatty/external/termbox2.h"
+
+int
+main(void)
+{
+ struct tb_event ev = {0};
+
+ typedef struct {
+ int X, Y;
+ int Checked;
+ } Checkmark;
+
+#define NUM_CHECKMARKS 4
+ int Y = 0;
+ Checkmark Marks[NUM_CHECKMARKS] = {
+ {0, Y++, 0},
+ {0, Y++, 0},
+ {0, Y++, 1},
+ {0, Y++, 0}
+ };
+ Y++;
+
+ int Selected = 0;
+
+ tb_init();
+
+ int Quit = 0;
+ while (!Quit)
+ {
+ tb_clear();
+
+ for (int CheckmarkIndex = 0;
+ CheckmarkIndex < NUM_CHECKMARKS;
+ CheckmarkIndex++)
+ {
+ Checkmark Mark = Marks[CheckmarkIndex];
+ if (Mark.Checked)
+ {
+ tb_printf(Mark.X, Mark.Y, 0, 0, "[x]");
+ }
+ else
+ {
+ tb_printf(Mark.X, Mark.Y, 0, 0, "[ ]");
+ }
+ }
+ Checkmark Mark = Marks[Selected];
+ if (Mark.Checked)
+ {
+ tb_set_cell(Mark.X + 1, Mark.Y, L'x', TB_UNDERLINE, 0);
+ }
+ else
+ {
+ tb_set_cell(Mark.X + 1, Mark.Y, L' ', TB_UNDERLINE, 0);
+ }
+
+ int BaseY = Y;
+ tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "next");
+ tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "previous");
+ tb_printf(0, BaseY, TB_BOLD, 0, "c"); tb_printf(2, BaseY++, 0, 0, "toggle");
+ tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit");
+
+ tb_present();
+
+ tb_poll_event(&ev);
+ if (ev.ch == 'q')
+ {
+ Quit = 1;
+ }
+ else if (ev.ch == 'j')
+ {
+ if (Selected == NUM_CHECKMARKS - 1) Selected = 0;
+ else Selected++;
+ }
+ else if (ev.ch == 'k')
+ {
+ if (Selected) Selected--;
+ else Selected = NUM_CHECKMARKS - 1;
+ }
+ else if (ev.ch == 'c')
+ {
+ Marks[Selected].Checked = !Marks[Selected].Checked;
+ }
+ }
+
+ tb_shutdown();
+}
diff --git a/archived/ui_meter.c b/archived/ui_meter.c
new file mode 100644
index 0000000..8636ba4
--- /dev/null
+++ b/archived/ui_meter.c
@@ -0,0 +1,108 @@
+#define TB_IMPL
+#include "../chatty/external/termbox2.h"
+
+#include <locale.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define Assert(expr) \
+ if (!(expr)) \
+ { \
+ tb_shutdown(); \
+ raise(SIGTRAP); \
+ }
+
+#define METER_WIDTH 4
+
+static char ***EnvironmentPath = 0;
+
+void
+draw_meter(int X, int Y, int Height, int MaxLevel, int Level)
+{
+ int FillColor = 0;
+
+ Height -= 2; // substract top and bottom border
+
+ tb_printf(X, Y, 0, 0, "%ls", L"┌──┐");
+ tb_printf(X, Y + Height + 1, 0, 0, "%ls", L"└ ┘");
+
+ if (Level == MaxLevel)
+ {
+ FillColor = TB_GREEN;
+ tb_printf(X + 1, Y + Height + 1, 0, 0, "00");
+ }
+ else
+ {
+ FillColor = TB_CYAN;
+ tb_printf(X + 1, Y + Height + 1, 0, 0, "%02d", Level);
+ }
+
+ int FilledLevel = (Level * Height)/MaxLevel;
+
+ for (int LevelIndex = 0; LevelIndex < FilledLevel; LevelIndex++)
+ {
+ int YLevel = Y + (Height - LevelIndex);
+ tb_set_cell(X + 1, YLevel, L'▒', FillColor, FillColor);
+ tb_set_cell(X + 2, YLevel, L'▒', FillColor, FillColor);
+ }
+
+ for (int YOffset = 0; YOffset < Height; YOffset++)
+ {
+ tb_set_cell(X + 0, Y + YOffset + 1, L'│', 0, 0);
+ tb_set_cell(X + 3, Y + YOffset + 1, L'│', 0, 0);
+ }
+
+}
+
+int
+main(int Argc, char *Args[], char *Envp[])
+{
+ EnvironmentPath = &Envp;
+
+ struct tb_event ev = {0};
+ Assert(setlocale(LC_ALL, ""));
+ int Quit = 0;
+ int Level = 0;
+ int LevelStep = 10;
+ int MeterMaxLevel = 100;
+ int MeterHeight = 10 + 2;
+
+ tb_init();
+ tb_set_input_mode(TB_INPUT_ESC | TB_INPUT_MOUSE);
+
+ while (!Quit)
+ {
+ tb_clear();
+
+ int Y = 0;
+ draw_meter(1, Y, MeterHeight, MeterMaxLevel, Level);
+ Y += MeterHeight + 1;;
+
+ tb_print(0, Y, TB_BOLD, 0, "j"); tb_print(2, Y++, 0, 0, "decrease");
+ tb_print(0, Y, TB_BOLD, 0, "k"); tb_print(2, Y++, 0, 0, "increase");
+ tb_print(0, Y, TB_BOLD, 0, "q"); tb_print(2, Y++, 0, 0, "to quit");
+
+ tb_present();
+
+ tb_poll_event(&ev);
+
+ if (ev.ch == 'q' || ev.key == TB_KEY_CTRL_C) Quit = 1;
+ else if ((ev.ch == 'j' || ev.key == TB_KEY_MOUSE_WHEEL_DOWN) &&
+ Level > 0)
+ {
+ if (LevelStep > Level) Level = 0;
+ else Level -= LevelStep;
+ }
+ else if ((ev.ch == 'k' || ev.key == TB_KEY_MOUSE_WHEEL_UP) &&
+ Level < MeterMaxLevel)
+ {
+ if (Level + LevelStep > MeterMaxLevel) Level = MeterMaxLevel;
+ else Level += LevelStep;
+ }
+
+ }
+
+ tb_shutdown();
+ return 0;
+}
diff --git a/archived/ui_selection.c b/archived/ui_selection.c
new file mode 100644
index 0000000..3e45ee2
--- /dev/null
+++ b/archived/ui_selection.c
@@ -0,0 +1,71 @@
+#define TB_IMPL
+#include "../chatty/external/termbox2.h"
+
+#include <signal.h>
+
+#define Assert(expr) if (!(expr)) raise(SIGTRAP)
+
+typedef struct {
+ int X, Y;
+} Position;
+
+int
+main(void)
+{
+ int Selected = 0;
+ int NumChoices = 3;
+ int Y = 0;
+
+ Position Positions[NumChoices];
+ Positions[0] = (Position){1, Y};
+ Positions[1] = (Position){5, Y};
+ Positions[2] = (Position){9, Y};
+ Y += 2;
+
+ struct tb_event ev = {0};
+ int Color = TB_GREEN;
+
+ tb_init();
+
+ int Quit = 0;
+ while (!Quit)
+ {
+ tb_clear();
+
+ int BaseY = Y;
+ tb_printf(0, BaseY, TB_BOLD, 0, "j"); tb_printf(2, BaseY++, 0, 0, "select next");
+ tb_printf(0, BaseY, TB_BOLD, 0, "k"); tb_printf(2, BaseY++, 0, 0, "select previous");
+ tb_printf(0, BaseY, TB_BOLD, 0, "s"); tb_printf(2, BaseY++, 0, 0, "change color");
+ tb_printf(0, BaseY, TB_BOLD, 0, "q"); tb_printf(2, BaseY++, 0, 0, "quit");
+
+ // Draw a box at position
+ for (int PositionsIndex = 0; PositionsIndex < NumChoices; PositionsIndex++)
+ {
+ Assert(Positions[PositionsIndex].X > 0);
+ tb_printf(Positions[PositionsIndex].X - 1, Positions[PositionsIndex].Y, Color, 0, "[ ]");
+ }
+ // Draw mark in selected box
+ tb_printf(Positions[Selected].X, Positions[Selected].Y, Color, 0, "x");
+
+ tb_present();
+ tb_poll_event(&ev);
+
+ if (ev.ch == 'q') Quit = 1;
+ else if (ev.ch == 'j' && Selected < NumChoices - 1) Selected++;
+ else if (ev.ch == 'k' && Selected) Selected--;
+ else if (ev.ch == 's')
+ {
+ switch (Selected)
+ {
+ case 0: Color = TB_GREEN; break;
+ case 1: Color = TB_BLUE; break;
+ case 2: Color = TB_RED; break;
+ default: Assert(0); break;
+ }
+ }
+ }
+
+ tb_shutdown();
+
+ return 0;
+}
diff --git a/archived/wrap.c b/archived/wrap.c
index 57506b8..2317c6e 100644
--- a/archived/wrap.c
+++ b/archived/wrap.c
@@ -33,7 +33,7 @@ wrap(u8* Text, u32 Len, u32 XLimit, u32 YLimit)
// break
t = Text[X];
- *(Text + X) = '\0';
+ Text[X] = '\0';
tb_printf(0, Y, 0, 0, "%s", Text + PrevX);
Text[X] = t;
Y++;
diff --git a/arena.h b/arena.h
new file mode 100644
index 0000000..f3d21d7
--- /dev/null
+++ b/arena.h
@@ -0,0 +1,53 @@
+#ifndef ARENA_H
+#define ARENA_H
+
+#include <sys/mman.h>
+#include <stdint.h>
+#include "types.h"
+
+// Arena Allocator
+typedef struct {
+ void* addr;
+ u64 size;
+ u64 pos;
+} Arena;
+#define PushArray(arena, type, count) (type*)ArenaPush((arena), sizeof(type) * (count))
+#define PushArrayZero(arena, type, count) (type*)ArenaPushZero((arena), sizeof(type) * (count))
+#define PushStruct(arena, type) PushArray((arena), (type), 1)
+#define PushStructZero(arena, type) PushArrayZero((arena), (type), 1)
+
+void ArenaAlloc(Arena* arena, u64 size);
+void ArenaRelease(Arena* arena);
+void* ArenaPush(Arena* arena, u64 size);
+
+#endif // ARENA_H
+
+#ifdef ARENA_IMPL
+
+// Returns arena in case of success, or 0 if it failed to alllocate the memory
+void
+ArenaAlloc(Arena* arena, u64 size)
+{
+ arena->addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ Assert(arena->addr != MAP_FAILED);
+ arena->pos = 0;
+ arena->size = size;
+}
+
+void
+ArenaRelease(Arena* arena)
+{
+ munmap(arena->addr, arena->size);
+}
+
+void*
+ArenaPush(Arena* arena, u64 size)
+{
+ u8* mem;
+ mem = (u8*)arena->addr + arena->pos;
+ arena->pos += size;
+ Assert(arena->pos <= arena->size);
+ return mem;
+}
+
+#endif // ARENA_IMPL
diff --git a/box.c b/box.c
deleted file mode 100644
index 3c22aad..0000000
--- a/box.c
+++ /dev/null
@@ -1,456 +0,0 @@
-#define MAX_INPUT_LEN 128
-#define DEBUG
-
-#define TB_IMPL
-#include "external/termbox2.h"
-#undef TB_IMPL
-
-#define CHATTY_IMPL
-#include "chatty.h"
-#undef CHATTY_IMPL
-
-#include "ui.h"
-
-#include <locale.h>
-
-#ifdef DEBUG
-#define Assert(expr) if (!(expr)) \
- { \
- tb_shutdown(); \
- *(u8*)0 = 0; \
- }
-#else
-#define Assert(expr) ;
-#endif
-
-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;
-
-// Draw box Rect with optional Chars characters
-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 (u32 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 (u32 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 (u32 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);
-}
-
-
-// Delete a character in Text at Pos
-void
-Delete(wchar_t* Text, u64 Pos)
-{
- memmove((u8*)(Text + Pos),
- (u8*)(Text + Pos + 1),
- (MAX_INPUT_LEN - Pos - 1) * sizeof(*Text));
-}
-
-// Check if coordinate (X,Y) is in rect boundaries
-Bool
-IsInRect(rect Rect, u32 X, u32 Y)
-{
- if ((X >= Rect.X && X <= Rect.X + Rect.W) &&
- (Y >= Rect.Y && Y <= Rect.Y + Rect.H)) return True;
- return False;
-}
-
-void
-ScrollLeft(rect Text, u32 *TextOffset, u32 Left)
-{
- u32 TextSurface = Text.H * Text.W;
-
- if (global.cursor_x - Left >= Text.X)
- {
- global.cursor_x -= Left;
- }
- else
- {
- global.cursor_x += Text.W - 1;
-
- if (global.cursor_y > Text.Y)
- {
- global.cursor_y--;
- }
- else
- {
- global.cursor_y = Text.Y + Text.H - 1;
- if (*TextOffset < TextSurface)
- {
- *TextOffset = 0;
- }
- else
- {
- *TextOffset -= TextSurface;
- }
- }
- }
-}
-
-int
-main(void)
-{
- assert(setlocale(LC_ALL, ""));
- struct tb_event ev = {0};
-
- tb_init();
-
- u32 InputLen = 0;
- wchar_t Input[MAX_INPUT_LEN] = {0};
-
-#if 1
- wchar_t *t = L"This is some text that no one would want to know, but you could.";
- u32 Len = wcslen(t);
- for (u32 At = 0; At < Len; At++) Input[At] = t[At];
- InputLen = Len;
-#endif
-
- bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
-
- rect TextBox = {0, 0, 32, 4};
-
-#define BOX_PADDING_X 1
-#define BOX_BORDER_WIDTH 1
- rect Text = {
- 2, 1,
- TextBox.W - 2*BOX_PADDING_X - 2*BOX_BORDER_WIDTH,
- TextBox.H - 2*BOX_BORDER_WIDTH
- };
-#undef BOX_PADDING_X
-#undef BOX_BORDER_WIDTH
- u32 TextSurface = Text.W * Text.H;
-
- global.cursor_x = Text.X;
- global.cursor_y = Text.Y;
-
- u32 TextOffset = 0;
- u32 InputPos = 0;
-
- while (ev.key != TB_KEY_CTRL_C)
- {
-
- tb_clear();
-
- DrawBox(TextBox, 0);
-
- // Draw the text right of the cursor
- // NOTE: the cursor is assumed to be in the box
- Assert(IsInRect(Text, global.cursor_x, global.cursor_y));
- u32 AtX = Text.X, AtY = Text.Y;
- u32 At = TextOffset;
- while (AtY < Text.Y + Text.H)
- {
- if (At < InputLen)
- {
- tb_printf(AtX++, AtY, 0, 0, "%lc", Input[At++]);
- }
- else
- {
- // Erase previous text
- tb_printf(AtX++, AtY, 0, 0, " ");
- }
-
- if (AtX == Text.X + Text.W) { AtY++; AtX = Text.X; }
- }
-
- // Position in input based on cursor position
- InputPos = TextOffset + (global.cursor_x - Text.X) + (global.cursor_y - Text.Y) * Text.W;
-
-#ifdef DEBUG
- tb_printf(TextBox.X, TextBox.Y, 0, 0,
- "%2d,%-2d +%d #%-2d %d/%d",
- global.cursor_x, global.cursor_y,
- TextOffset, InputPos,
- InputLen, MAX_INPUT_LEN);
-#endif
-
- tb_present();
-
- tb_poll_event(&ev);
-
- if (ev.mod & TB_MOD_CTRL)
- {
- switch (ev.key)
- {
- // Delete character backwards
- case TB_KEY_CTRL_8:
- // case TB_KEY_BACKSPACE2:
- {
- if (InputPos == 0) break;
- Delete(Input, InputPos - 1);
- InputLen--;
- // TODO: scrolling
-
- ScrollLeft(Text, &TextOffset, 1);
-
- } break;
- // Delete character forwards
- case TB_KEY_CTRL_D:
- {
- if (InputPos == InputLen) break;
- Delete(Input, InputPos);
- InputLen--;
- // Delete(Input, Position)
- } break;
- // Delete Word backwards
- case TB_KEY_CTRL_W:
- {
- u32 At = InputPos;
- // Find character to stop on
- while (At && is_whitespace(Input[At - 1])) At--;
- while (At && !is_whitespace(Input[At - 1])) At--;
-
- s32 NDelete = InputPos - At;
- memmove(Input + At, Input + InputPos, (InputLen - InputPos) * sizeof(Input[At]));
- InputLen -= NDelete;
-#ifdef DEBUG
- Input[InputLen] = 0;
-#endif
-
- // Update cursor position
- if (global.cursor_x - NDelete >= Text.X)
- {
- global.cursor_x -= NDelete;
- }
- else
- {
- global.cursor_x += Text.W - NDelete;
-
- if (global.cursor_y > Text.Y)
- {
- global.cursor_y--;
- }
- else
- {
- // Scroll
- global.cursor_y = Text.Y + Text.H - 1;
- TextOffset -= TextSurface;
- }
- }
-
- Assert(IsInRect(Text, global.cursor_x, global.cursor_y));
-
- } break;
- // Delete until start of line
- case TB_KEY_CTRL_U:
- {
- memmove(Input, Input + InputPos, (InputLen - InputPos) * sizeof(*Input));
- InputLen -= InputPos;
-#ifdef DEBUG
- Input[InputLen] = 0;
-#endif
- global.cursor_x = Text.X;
- global.cursor_y = Text.Y;
- TextOffset = 0;
- // memmove until first character
- } break;
- // Delete until end of input
- case TB_KEY_CTRL_K: break;
- // Move to start
- case TB_KEY_CTRL_A: global.cursor_x = Text.X; break;
- // Move to end
- case TB_KEY_CTRL_E:
- {
- if (global.cursor_x == Text.X + Text.W - 1) break;
-
- if (InputPos + Text.W > InputLen)
- {
- // Put the cursor on the last character
- global.cursor_x = Text.X + (InputLen - TextOffset) % (Text.W);
- }
- else
- {
- global.cursor_x = Text.X + Text.W - 1;
- }
- } break;
- // Move backwards by one character
- case TB_KEY_CTRL_B: break;
- // Move forwards by one character
- case TB_KEY_CTRL_F: break;
- // Move backwards by word
- case TB_KEY_ARROW_LEFT: break;
- // Move forwards by word
- case TB_KEY_ARROW_RIGHT: break;
- }
-
- }
- // Insert new character in Input at InputPos
- else if (ev.ch && InputLen < MAX_INPUT_LEN)
- {
- if (InputPos < InputLen)
- {
- memmove(Input + InputPos,
- Input + InputPos - 1,
- (InputLen - InputPos + 1) * sizeof(*Input));
- }
- Input[InputPos] = ev.ch;
- InputLen++;
-
- if (global.cursor_x == Text.X + Text.W - 1)
- {
- if (global.cursor_y == Text.Y + Text.H - 1)
- {
- TextOffset += Text.W;
- }
- else
- {
- global.cursor_y++;
- }
- global.cursor_x = Text.X;
- }
- else
- {
- global.cursor_x++;
- }
- }
- else
- {
- switch (ev.key)
- {
- case TB_KEY_ARROW_UP:
- {
- if (global.cursor_y == Text.Y)
- {
- if (TextOffset == 0)
- {
- global.cursor_x = Text.X;
-
- break;
- }
-
- TextOffset -= TextSurface;
- global.cursor_y = Text.Y + Text.H - 1;
- }
- else
- {
- global.cursor_y--;
- }
- } break;
- case TB_KEY_ARROW_DOWN:
- {
- if (InputPos + Text.W > InputLen)
- {
- // Put the cursor on the last character
- global.cursor_x = Text.X + (InputLen - TextOffset) % (Text.W);
- global.cursor_y = Text.Y + (InputLen - TextOffset) / Text.W;
-
- break;
- }
-
- if (global.cursor_y == Text.Y + Text.H - 1)
- {
- TextOffset += TextSurface;
- global.cursor_y = Text.Y;
- }
- else
- {
- global.cursor_y++;
- }
- } break;
-
- // Move character left or scroll
- case TB_KEY_ARROW_LEFT:
- {
- if (InputPos == 0) break;
-
- // If text is on the first character of the box scroll backwards
- if (global.cursor_x == Text.X &&
- global.cursor_y == Text.Y)
- {
-
- global.cursor_x = Text.X + Text.W - 1;
- global.cursor_y = Text.Y + Text.H - 1;
-
- // Scroll
- TextOffset -= TextSurface;
-
- break;
- }
-
- global.cursor_x--;
- if (global.cursor_x < Text.X)
- {
- global.cursor_x = Text.X + Text.W - 1;
- global.cursor_y--;
- }
-
- } break;
- // Move character right or scroll
- case TB_KEY_ARROW_RIGHT:
- {
- if (InputPos == InputLen) break;
-
- // 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;
-
- // Scroll
- TextOffset += TextSurface;
-
- break;
- }
-
- global.cursor_x++;
- if (global.cursor_x == Text.X + Text.W)
- {
- global.cursor_x = Text.X;
- global.cursor_y++;
- }
- } break;
- }
- }
-
- }
-
- tb_shutdown();
- return 0;
-}
diff --git a/box_test.c b/box_test.c
deleted file mode 100644
index 57663f9..0000000
--- a/box_test.c
+++ /dev/null
@@ -1,27 +0,0 @@
-#include <stdio.h>
-#include <stdbool.h>
-
-#define Assert(expr) if (!(expr)) return false;
-
-char
-DrawingTest()
-{
- Assert(2 == 3);
-
- return true;
-}
-
-char
-FooTest()
-{
- return false;
-}
-
-
-int
-main(int Argc, char* Argv[])
-{
- char *i(void);
- i = &DrawingTest;
-
-}
diff --git a/build.sh b/build.sh
index 9d69974..50572d0 100755
--- a/build.sh
+++ b/build.sh
@@ -1,6 +1,7 @@
#!/bin/sh
set -x
-gcc external/keyboard.c
-gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o chatty chatty.c
-gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o server server.c
-gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o send send.c
+# gcc external/keyboard.c
+gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -o build/chatty chatty.c
+gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o build/server server.c
+# gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o build/send send.c
+gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -I . -o build/input_box archived/input_box.c
diff --git a/chatty.c b/chatty.c
index d14475a..3300ce0 100644
--- a/chatty.c
+++ b/chatty.c
@@ -1,8 +1,8 @@
#define TB_IMPL
#include "external/termbox2.h"
+#undef TB_IMPL
#include <arpa/inet.h>
-#include <assert.h>
#include <locale.h>
#include <poll.h>
#include <pthread.h>
@@ -22,12 +22,28 @@
// Number of spaces inserted when pressing Tab/Ctrl+I
#define TAB_WIDTH 4
+#ifndef Assert
+#ifdef DEBUG
+#define Assert(expr) if (!(expr)) \
+ { \
+ tb_shutdown(); \
+ raise(SIGTRAP); \
+ }
+#else
+#define Assert(expr) ;
+#endif // DEBUG
+#endif // Assert
+
#define CHATTY_IMPL
#include "chatty.h"
-#undef CHATTY_IMPL
#include "protocol.h"
+
+#define TEXTBOX_MAX_INPUT MAX_INPUT_LEN
#include "ui.h"
+#define ARENA_IMPL
+#include "arena.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)
FDS_TTY,
@@ -64,7 +80,7 @@ void
popup(u32 fg, u32 bg, u8* text)
{
u32 len = strlen((char*)text);
- assert(len > 0);
+ Assert(len > 0);
tb_print(global.width / 2 - len / 2, global.height / 2, fg, bg, (char*)text);
}
@@ -96,7 +112,7 @@ add_user_info(Arena* clientsArena, s32 fd, u64 id)
header.id = user.ID;
IDMessage message = {id};
s32 nsend = sendAnyMessage(fd, header, &message);
- assert(nsend != -1);
+ Assert(nsend != -1);
// Wait for response
IntroductionMessage introduction_message;
@@ -107,7 +123,7 @@ add_user_info(Arena* clientsArena, s32 fd, u64 id)
memcpy(client->Author, introduction_message.author, AUTHOR_LEN);
client->ID = id;
- loggingf("Got " USER_FMT "\n", USER_ARG((*client)));
+ LoggingF("Got " USER_FMT "\n", USER_ARG((*client)));
return client;
}
@@ -136,11 +152,11 @@ authenticate(User* user, s32 fd)
HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID);
IDMessage message = {user->ID};
s32 nsend = sendAnyMessage(fd, header, &message);
- assert(nsend != -1);
+ Assert(nsend != -1);
ErrorMessage error_message;
s32 nrecv = recvAnyMessageType(fd, &header, &error_message, HEADER_TYPE_ERROR);
- assert(nrecv != -1);
+ Assert(nrecv != -1);
// TODO: handle not found
if (nrecv == 0)
return 0;
@@ -157,11 +173,11 @@ authenticate(User* user, s32 fd)
IntroductionMessage message;
memcpy(message.author, user->Author, AUTHOR_LEN);
s32 nsend = sendAnyMessage(fd, header, &message);
- assert(nsend != -1);
+ Assert(nsend != -1);
IDMessage id_message;
s32 nrecv = recvAnyMessageType(fd, &header, &id_message, HEADER_TYPE_ID);
- assert(nrecv != -1);
+ Assert(nrecv != -1);
user->ID = id_message.id;
return 1;
}
@@ -180,7 +196,7 @@ thread_reconnect(void* fds_ptr)
s32 unifd, bifd;
struct pollfd* fds = fds_ptr;
struct timespec t = { 0, Miliseconds(300) }; // 300 miliseconds
- loggingf("Trying to reconnect\n");
+ LoggingF("Trying to reconnect\n");
while (1)
{
// timeout
@@ -189,18 +205,18 @@ thread_reconnect(void* fds_ptr)
bifd = get_connection(&address);
if (bifd == -1)
{
- loggingf("errno: %d\n", errno);
+ LoggingF("errno: %d\n", errno);
continue;
}
unifd = get_connection(&address);
if (unifd == -1)
{
- loggingf("errno: %d\n", errno);
+ LoggingF("errno: %d\n", errno);
close(bifd);
continue;
}
- loggingf("Reconnect succeeded (%d, %d), authenticating\n", unifd, bifd);
+ LoggingF("Reconnect succeeded (%d, %d), authenticating\n", unifd, bifd);
if (authenticate(&user, bifd) &&
authenticate(&user, unifd))
@@ -211,7 +227,7 @@ thread_reconnect(void* fds_ptr)
close(bifd);
close(unifd);
- loggingf("Failed, retrying...\n");
+ LoggingF("Failed, retrying...\n");
}
fds[FDS_BI].fd = bifd;
@@ -230,10 +246,10 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len)
int CommandPipe[2];
int Error = pipe(CommandPipe);
- assert(Error != -1);
+ Assert(Error != -1);
int Pid = fork();
- assert(Pid != -1);
+ Assert(Pid != -1);
// Run command in child
if (!Pid)
@@ -269,7 +285,7 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len)
close(CommandPipe[1]);
Result.NumRead = read(CommandPipe[0], OutputBuffer, Len);
- assert(Result.NumRead != -1);
+ Assert(Result.NumRead != -1);
return Result;
}
@@ -283,33 +299,43 @@ DisplayChat(Arena* ScratchArena,
Arena* ClientsArena, struct pollfd* fds,
wchar_t Input[], u32 InputLen)
{
-#define MIN_TEXT_WIDTH_FOR_WRAPPING 20
- u32 BoxHeight = GetInputBoxMinimumHeight();
- u32 MinBoxWidth = GetInputBoxMinimumWidth();
-
- u32 BoxWidth = global.width - 1;
- u32 InputBoxTextWidth = BoxWidth - MinBoxWidth + 2;
+ rect TextBox = {
+ 1, 0, global.width - 2, 3,
+ };
+ u32 FreeHeight = global.height - TextBox.H;
+ TextBox.Y = FreeHeight;
- if (InputLen >= InputBoxTextWidth &&
- InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING)
+#define MIN_TEXT_WIDTH_FOR_WRAPPING 20
+ s32 MinBoxWidth = TEXTBOX_MIN_WIDTH;
+ s32 InputBoxTextWidth = TextBox.W - MinBoxWidth + 2;
+ bool ShouldIncreaseSize = (
+ (s32)InputLen >= InputBoxTextWidth &&
+ InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING
+ );
+ if (ShouldIncreaseSize)
{
- BoxHeight++;
+ TextBox.H++;
}
-
- u32 FreeHeight = global.height - BoxHeight;
-
#undef MIN_TEXT_WIDTH_FOR_WRAPPING
- if (global.height < BoxHeight || global.width < MinBoxWidth)
+ rect TextR = {
+ TextBox.X + 2, TextBox.Y + 1,
+ TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH,
+ TextBox.H - 2*TEXTBOX_BORDER_WIDTH
+ };
+
+ if (global.height < TextBox.H || global.width < TextBox.W)
{
tb_hide_cursor();
return;
}
+
bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+ global.cursor_x = TextR.X;
+ global.cursor_y = TextR.Y;
+ DrawBox(TextBox, 0);
- InputBox(0, FreeHeight, BoxWidth, BoxHeight,
- Input, InputLen,
- True);
+ // InputBox(TextBox, Input, InputLen, True);
// Print vertical bar
s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2;
@@ -335,7 +361,7 @@ DisplayChat(Arena* ScratchArena,
// Used to go to the next message in MessagesArena by incrementing with the messages' size.
u8* MessageAddress = MessagesArena->addr;
- assert(MessageAddress != 0);
+ Assert(MessageAddress != 0);
// Skip messages if there is not enough space to display them all
u32 MessagesOffset = (MessagesNum > FreeHeight) ? MessagesNum - FreeHeight : 0;
@@ -361,7 +387,7 @@ DisplayChat(Arena* ScratchArena,
break;
default:
// unhandled message type
- assert(0);
+ Assert(0);
}
}
@@ -379,10 +405,10 @@ DisplayChat(Arena* ScratchArena,
User* client = get_user_by_id(ClientsArena, header->id);
if (!client)
{
- loggingf("User not known, requesting from server\n");
+ LoggingF("User not known, requesting from server\n");
client = add_user_info(ClientsArena, fds[FDS_BI].fd, header->id);
}
- assert(client);
+ Assert(client);
switch (header->type)
{
@@ -480,7 +506,7 @@ main(int argc, char** argv)
}
u32 arg_len = strlen(argv[1]);
- assert(arg_len <= AUTHOR_LEN - 1);
+ Assert(arg_len <= AUTHOR_LEN - 1);
memcpy(user.Author, argv[1], arg_len);
user.Author[arg_len] = '\0';
@@ -506,8 +532,8 @@ main(int argc, char** argv)
pthread_t thr_rec; // thread for reconnecting to server when disconnected
#ifdef LOGGING
- logfd = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
- assert(logfd != -1);
+ LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ Assert(LogFD != -1);
#else
logfd = 2; // stderr
#endif
@@ -531,7 +557,7 @@ main(int argc, char** argv)
// File for storing the user's ID.
u32 idfile = open(ID_FILE, O_RDWR | O_CREAT, 0600);
s32 nread = read(idfile, &user.id, sizeof(user.id));
- assert(nread != -1);
+ Assert(nread != -1);
#endif
/* Authentication */
{
@@ -539,25 +565,25 @@ main(int argc, char** argv)
bifd = get_connection(&address);
if (bifd == -1)
{
- loggingf("errno: %d\n", errno);
+ LoggingF("errno: %d\n", errno);
return 1;
}
unifd = get_connection(&address);
if (unifd == -1)
{
- loggingf("errno: %d\n", errno);
+ LoggingF("errno: %d\n", errno);
return 1;
}
- loggingf("(%d,%d)\n", bifd, unifd);
+ LoggingF("(%d,%d)\n", bifd, unifd);
if (!authenticate(&user, bifd) ||
!authenticate(&user, unifd))
{
- loggingf("errno: %d\n", errno);
+ LoggingF("errno: %d\n", errno);
return 1;
}
else
{
- loggingf("Authenticated (%d,%d)\n", bifd, unifd);
+ LoggingF("Authenticated (%d,%d)\n", bifd, unifd);
}
fds[FDS_BI].fd = bifd;
fds[FDS_UNI].fd = unifd;
@@ -568,10 +594,10 @@ main(int argc, char** argv)
write(idfile, &user.id, sizeof(user.id));
#endif
- loggingf("Got ID: %lu\n", user.ID);
+ LoggingF("Got ID: %lu\n", user.ID);
// for wide character printing
- assert(setlocale(LC_ALL, ""));
+ Assert(setlocale(LC_ALL, ""));
// init
tb_init();
@@ -588,7 +614,7 @@ main(int argc, char** argv)
{
err = poll(fds, FDS_MAX, TIMEOUT_POLL);
// ignore resize events and use them to redraw the screen
- assert(err != -1 || errno == EINTR);
+ Assert(err != -1 || errno == EINTR);
tb_clear();
@@ -597,24 +623,24 @@ main(int argc, char** argv)
// got data from server
HeaderMessage header;
nrecv = recv(fds[FDS_UNI].fd, &header, sizeof(header), 0);
- assert(nrecv != -1);
+ Assert(nrecv != -1);
// Server disconnects
if (nrecv == 0)
{
// close diconnected server's socket
err = close(fds[FDS_UNI].fd);
- assert(err == 0);
+ Assert(err == 0);
fds[FDS_UNI].fd = -1; // ignore
// start trying to reconnect in a thread
err = pthread_create(&thr_rec, 0, &thread_reconnect, (void*)fds);
- assert(err == 0);
+ Assert(err == 0);
}
else
{
if (header.version != PROTOCOL_VERSION)
{
- loggingf("Header received does not match version\n");
+ LoggingF("Header received does not match version\n");
continue;
}
@@ -631,12 +657,12 @@ main(int argc, char** argv)
case HEADER_TYPE_PRESENCE:;
PresenceMessage* message = ArenaPush(&MessagesArena, sizeof(*message));
nrecv = recv(fds[FDS_UNI].fd, message, sizeof(*message), 0);
- assert(nrecv != -1);
- assert(nrecv == sizeof(*message));
+ Assert(nrecv != -1);
+ Assert(nrecv == sizeof(*message));
MessagesNum++;
break;
default:
- loggingf("Got unhandled message: %s\n", headerTypeString(header.type));
+ LoggingF("Got unhandled message: %s\n", headerTypeString(header.type));
break;
}
}
diff --git a/chatty.h b/chatty.h
index 0833299..5891b94 100644
--- a/chatty.h
+++ b/chatty.h
@@ -34,53 +34,18 @@
#define global_variable
#define internal static
-typedef uint8_t u8;
-typedef uint16_t u16;
-typedef uint32_t u32;
-typedef uint64_t u64;
-typedef int8_t s8;
-typedef int16_t s16;
-typedef int32_t s32;
-typedef int64_t s64;
-typedef enum {
- False = 0,
- True = 1
-} Bool;
+#include "types.h"
-// Arena Allocator
-typedef struct {
- void* addr;
- u64 size;
- u64 pos;
-} Arena;
-#define PushArray(arena, type, count) (type*)ArenaPush((arena), sizeof(type) * (count))
-#define PushArrayZero(arena, type, count) (type*)ArenaPushZero((arena), sizeof(type) * (count))
-#define PushStruct(arena, type) PushArray((arena), (type), 1)
-#define PushStructZero(arena, type) PushArrayZero((arena), (type), 1)
-
-u32 wstrlen(u32* str);
-void loggingf(char* format, ...);
-void ArenaAlloc(Arena* arena, u64 size);
-void ArenaRelease(Arena* arena);
-void* ArenaPush(Arena* arena, u64 size);
+void Loggingf(char* format, ...);
#endif // CHATTY_H
#ifdef CHATTY_IMPL
-global_variable s32 logfd;
-
-u32
-wstrlen(u32* str)
-{
- u32 i = 0;
- while (str[i] != 0)
- i++;
- return i;
-}
+global_variable s32 LogFD;
void
-loggingf(char* format, ...)
+LoggingF(char* format, ...)
{
char buf[LOGMESSAGE_MAX];
va_list args;
@@ -96,36 +61,10 @@ loggingf(char* format, ...)
u8 timestamp[LOG_LEN];
struct tm* ltime = localtime((time_t*)&t);
strftime((char*)timestamp, LOG_LEN, LOG_FMT, ltime);
- write(logfd, timestamp, LOG_LEN - 1);
+ write(LogFD, timestamp, LOG_LEN - 1);
- write(logfd, buf, n);
+ write(LogFD, buf, n);
}
-// Returns arena in case of success, or 0 if it failed to alllocate the memory
-void
-ArenaAlloc(Arena* arena, u64 size)
-{
- arena->addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
- assert(arena->addr != MAP_FAILED);
- arena->pos = 0;
- arena->size = size;
-}
-
-void
-ArenaRelease(Arena* arena)
-{
- munmap(arena->addr, arena->size);
-}
-
-void*
-ArenaPush(Arena* arena, u64 size)
-{
- u8* mem;
- mem = (u8*)arena->addr + arena->pos;
- arena->pos += size;
- assert(arena->pos <= arena->size);
- return mem;
-}
-
#endif // CHATTY_IMPL
diff --git a/chatty2.c b/chatty2.c
new file mode 100644
index 0000000..5a00fee
--- /dev/null
+++ b/chatty2.c
@@ -0,0 +1,90 @@
+/* Macro's */
+
+#define TB_IMPL
+#include "external/termbox2.h"
+#undef TB_IMPL
+
+#define DEBUG
+#define MAX_INPUT_LEN 255
+
+#define CHATTY_IMPL
+#include "ui.h"
+#undef CHATTY_IMPL
+#include "protocol.h"
+
+#include <locale.h>
+
+#ifdef DEBUG
+#define Assert(expr) \
+ if (!(expr)) \
+ { \
+ tb_shutdown(); \
+ raise(SIGTRAP); \
+ }
+#else
+#define Assert(expr) ;
+#endif
+
+int
+main(int Argc, char *Argv[])
+{
+ struct tb_event ev;
+ rect TextBox = {0, 0, 24, 4};
+ rect TextR = {
+ TextBox.X + TEXTBOX_BORDER_WIDTH + TEXTBOX_PADDING_X,
+ TextBox.Y + TEXTBOX_BORDER_WIDTH,
+ TextBox.W - TEXTBOX_BORDER_WIDTH * 2 - TEXTBOX_PADDING_X * 2,
+ TextBox.H - TEXTBOX_BORDER_WIDTH * 2
+ };
+ wchar_t Input[MAX_INPUT_LEN] = {0};
+ u32 InputLen = 0;
+ u32 InputOffset = 0;
+ u32 InputPos = 0;
+ u32 DidParseKey = 0;
+
+ Assert(setlocale(LC_ALL, ""));
+ tb_init();
+ global.cursor_x = TextR.X;
+ global.cursor_y = TextR.Y;
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+
+ while (ev.key != TB_KEY_CTRL_C)
+ {
+ tb_clear();
+
+ DrawBox(TextBox, 0);
+ TextBoxDraw(TextR, Input + InputOffset, InputLen);
+
+ InputPos = InputOffset + (global.cursor_x - TextR.X) + (global.cursor_y - TextR.Y) * TextR.W;
+ Assert(InputPos <= InputLen);
+
+ tb_present();
+
+ tb_poll_event(&ev);
+
+ // TODO: Handle resize event
+
+ // Intercept keys
+ if (ev.key == TB_KEY_CTRL_M)
+ {
+ tb_printf(26, 0, 0, 0, "sent.");
+ continue;
+ }
+ else
+ {
+ DidParseKey = TextBoxKeypress(ev, TextR,
+ Input, &InputLen, InputPos, &InputOffset);
+ }
+
+ u32 ShouldInsert = (!DidParseKey) && (ev.ch && InputLen < MAX_INPUT_LEN);
+ if (ShouldInsert)
+ {
+ TextBoxInsert(Input, InputPos, InputLen++, ev.ch);
+ ScrollRight(TextR, &InputOffset);
+ }
+
+ // tb_clear();
+ }
+
+ tb_shutdown();
+}
diff --git a/gdb_scripts b/gdb_scripts
new file mode 100644
index 0000000..97acf23
--- /dev/null
+++ b/gdb_scripts
@@ -0,0 +1,12 @@
+define tbx
+ tui ena
+ b 126
+ continue
+ disp SearchIndex
+ disp PrevIndex
+ disp Text[TextOffset + SearchIndex]
+ disp Text + TextOffset + PrevIndex
+ disp BreakOffset
+ disp YOffset
+end
+
diff --git a/protocol.h b/protocol.h
index d35126c..0c5b04a 100644
--- a/protocol.h
+++ b/protocol.h
@@ -1,7 +1,8 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
-#include "chatty.h"
+#include "arena.h"
+#include "types.h"
/// Protocol
// - every message has format Header + Message
@@ -212,7 +213,7 @@ recvTextMessage(Arena* msgsArena, u32 fd)
nrecv = recv(fd, (u8*)&message->text, text_size, 0);
assert(nrecv != -1);
- assert(nrecv == message->len * sizeof(*message->text));
+ assert(nrecv == (s32)(message->len * sizeof(*message->text)));
return message;
}
@@ -331,7 +332,7 @@ sendAnyMessage(u32 fd, HeaderMessage header, void* anyMessage)
s32 nsend_total;
s32 nsend = send(fd, &header, sizeof(header), 0);
if (nsend == -1) return nsend;
- loggingf("sendAnyMessage (%d)|sending "HEADER_FMT"\n", fd, HEADER_ARG(header));
+ LoggingF("sendAnyMessage (%d)|sending "HEADER_FMT"\n", fd, HEADER_ARG(header));
assert(nsend == sizeof(header));
nsend_total = nsend;
@@ -359,7 +360,7 @@ sendAnyMessage(u32 fd, HeaderMessage header, void* anyMessage)
anyMessage = &message->text;
} break;
default:
- loggingf("sendAnyMessage (%d)|Cannot send %s\n", fd, headerTypeString(header.type));
+ LoggingF("sendAnyMessage (%d)|Cannot send %s\n", fd, headerTypeString(header.type));
return -1;
}
diff --git a/scroll.md b/scroll.md
new file mode 100644
index 0000000..f36e774
--- /dev/null
+++ b/scroll.md
@@ -0,0 +1,63 @@
+# The scrolling problem:
+If the text is wrapped on whitespace this means that when we scroll up we do not go
+backwards by text width. It is important to update the text correctly because we want
+the complete text to always look the same so that when the user can see what they expect
+to see (in this case the previous text).
+
+# 1. Find based on skipped characters
+New position could be
+`InputPos - (TextWidth - Skipped)`
+
+This means that the place where the wrapping is done must somehow return the number of
+skipped characters.
+
+Or we can run the wrapping algorithm on the current line to check if wrapping would
+have been done and how much characters it would have skipped.
+
+# 2. Wrapping as is:
+Searching the text backwards for whitespace works good for an existsing text. But we
+have a text that we append to a lot, so it might be more interesting to add wrapping
+when adding characters.
+
+When we add a character at the end of the line and it is not a space *wrap*.
+
+Problem: What if we are in the middle of the text.
+1. Check the last character
+2. Is it a space
+ y) ignore
+ n) search backwards for space and wrap there
+
+Problem: How to print
+Have the wrapping algorithms on adding a character and printing be the same, which
+means that if we wrapped after printing a character we should print it correctly.
+
+# 3. Wrapping with special characters:
+To create special characters (formatting, wrapping, etc.) and preserving unicode we can
+implement special characters by making our text into a special linked list structure.
+
+> Example
+```c
+typedef enum {
+ NodeText = 0,
+ NodeBold,
+ NodeWrap
+} node_type;
+
+typedef stuct {
+ node_type Type;
+ wchar_t *Text;
+ text_node *Prev;
+ text_node *Next;
+} text_node;
+
+text_node First = { NodeText, L"Hello Foo!", 0, 0 };
+text_node Second = { NodeWrap, 0, &NodeText, 0 };
+First.Next = &Second;
+```
+# Notes
+- We should add a special (colored?) char to show the text is wrapped because that can
+ be hard to tell when spaces are same as empty.
+- What if we end up on a wrapping char?
+- Implement 2 for wrapping
+- Implement 1 for cursor movement
+- Implement 3 for formatting later and instead use text start and finish
diff --git a/scrollandwrapped.c b/scrollandwrapped.c
new file mode 100644
index 0000000..3cd3a42
--- /dev/null
+++ b/scrollandwrapped.c
@@ -0,0 +1,225 @@
+#define TB_IMPL
+#include "external/termbox2.h"
+#undef TB_IMPL
+
+#define MAX_INPUT_LEN 255
+#define DEBUG
+
+#define CHATTY_IMPL
+#include "ui.h"
+
+#undef Assert
+#define Assert(expr) \
+ if (!(expr)) \
+ { \
+ tb_shutdown(); \
+ raise(SIGTRAP); \
+ }
+
+void
+Right(rect TextR, s32 *TextOffset)
+{
+ if (global.cursor_x == TextR.X + TextR.W - 1)
+ {
+ if (global.cursor_y == TextR.Y + TextR.H - 1)
+ {
+ *TextOffset += TextR.W;
+ }
+ else
+ {
+ global.cursor_y++;
+ }
+ global.cursor_x = TextR.X;
+ }
+ else
+ {
+ global.cursor_x++;
+ }
+}
+
+void
+Left(rect TextR, s32 *TextOffset)
+{
+ if (global.cursor_x == TextR.X)
+ {
+ if (global.cursor_y == TextR.Y)
+ {
+ *TextOffset -= TextR.W;
+ }
+ else
+ {
+ global.cursor_y--;
+ }
+ global.cursor_x = TextR.X + TextR.W - 1;
+ }
+ else
+ {
+ global.cursor_x--;
+ }
+
+}
+
+void
+PrintString(s32 X, s32 Y, s32 FG, s32 BG, wchar_t *Text, s32 TextLen)
+{
+ for (s32 At = 0;
+ At < TextLen;
+ At++)
+ {
+#ifdef DEBUG
+ tb_set_cell(X + At, Y, Text[At], TB_BLACK, TB_CYAN);
+ tb_present();
+#else
+ tb_set_cell(X + At, Y, Text[At], FG, BG);
+#endif
+ }
+}
+
+int
+main(int Argc, char* Argv[])
+{
+ wchar_t Text[MAX_INPUT_LEN] = {0};
+ s32 TextLen = 0;
+ s32 TextPos = 0;
+ s32 TextOffset = 0;
+ rect BoxR = {0, 0, 24, 4};
+ rect TextR = TEXTBOXFROMBOX(BoxR);
+ struct tb_event ev = {0};
+
+ Assert(setlocale(LC_ALL, ""));
+ tb_init();
+
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+ global.cursor_x = TextR.X;
+ global.cursor_y = TextR.Y;
+
+#if 0
+ {
+ wchar_t DummyText[] = L"This is some dummy text m"
+ "eant to be wrapped sever"
+ "al times so I can try ou"
+ "t how scrolling works";
+ s32 Len = wcslen(DummyText);
+ wcsncpy(Text, DummyText, Len);
+ TextLen = Len;
+ }
+#endif
+
+ while (ev.key != TB_KEY_CTRL_C)
+ {
+ tb_clear();
+ DrawBox(BoxR, 0);
+
+#if 1
+ {
+ s32 SearchIndex = TextR.W;
+ s32 PrevIndex = 0;
+ s32 BreakOffset = 0;
+ s32 YOffset = 0;
+
+ if (TextLen - TextOffset <= TextR.W)
+ {
+ PrintString(TextR.X, TextR.Y, 0, 0, Text + TextOffset, TextLen);
+ }
+ else
+ {
+ while (SearchIndex < TextLen - TextOffset)
+ {
+ while (Text[SearchIndex] != ' ')
+ {
+ SearchIndex--;
+ if (SearchIndex == PrevIndex)
+ {
+ SearchIndex += TextR.W;
+ break;
+ }
+ }
+
+ BreakOffset = (PrevIndex == SearchIndex) ? TextR.W : SearchIndex - PrevIndex;
+ PrintString(TextR.X, TextR.Y + YOffset, 0, 0,
+ Text + TextOffset + PrevIndex, BreakOffset);
+
+ YOffset++;
+ if (YOffset == TextR.H)
+ {
+ break;
+ }
+
+ PrevIndex = SearchIndex;
+ SearchIndex += TextR.W;
+ }
+
+ if (YOffset < TextR.H)
+ {
+ PrintString(TextR.X, TextR.Y + YOffset, 0, 0,
+ Text + TextOffset + PrevIndex,
+ TextLen - TextOffset - PrevIndex);
+ }
+ }
+
+
+ }
+#endif
+
+ tb_printf(BoxR.X, BoxR.Y, 0, 0,
+ "#%d/%d +%d",
+ TextPos, TextLen,
+ TextOffset);
+ tb_printf(BoxR.X, BoxR.Y + BoxR.H, 0, 0,
+ "'%lc'/'%lc'",
+ ev.ch ? ev.ch : L'|', Text[TextPos] ? ev.ch : L'|');
+
+ tb_present();
+ tb_poll_event(&ev);
+ switch (ev.key)
+ {
+ case TB_KEY_ARROW_RIGHT:
+ {
+ if (TextPos == TextLen) break;
+ TextPos++;
+ Right(TextR, &TextOffset);
+ continue;
+ } break;
+ case TB_KEY_ARROW_LEFT:
+ {
+ if (TextPos == 0) break;
+ TextPos--;
+ Left(TextR, &TextOffset);
+ continue;
+ } break;
+ case TB_KEY_CTRL_8:
+ {
+ if (TextPos == 0) break;
+ TextPos--;
+ TextLen--;
+ memmove(Text + TextPos,
+ Text + TextPos + 1,
+ (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text));
+ Left(TextR, &TextOffset);
+ continue;
+ } break;
+ }
+
+ if (!ev.mod && ev.ch && TextLen < MAX_INPUT_LEN)
+ {
+ if (TextPos < TextLen)
+ {
+ memmove(Text + TextPos + 1,
+ Text + TextPos,
+ (MAX_INPUT_LEN - TextPos - 1)*sizeof(*Text));
+ }
+ Text[TextPos] = ev.ch;
+ TextPos++;
+ TextLen++;
+
+ // Advance more if wrapping will happen
+ if (global.cursor_x == TextR.X + TextR.W - 1 &&
+ Text)
+
+ Right(TextR, &TextOffset);
+ }
+ }
+
+ tb_shutdown();
+ return 0;
+}
diff --git a/server.c b/server.c
index 27b1d4b..a6613d6 100644
--- a/server.c
+++ b/server.c
@@ -1,10 +1,3 @@
-#define CHATTY_IMPL
-#include "chatty.h"
-#undef CHATTY_IMPL
-
-#include "protocol.h"
-
-#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
@@ -16,6 +9,30 @@
#include <sys/stat.h>
#include <unistd.h>
+/* Assertion macro */
+#ifndef Assert
+#ifdef DEBUG
+#define Assert(expr) if (!(expr)) { \
+ raise(SIGTRAP); \
+}
+#else
+#define Assert(expr) if (!(expr)) { \
+ raise(SIGTRAP); \
+}
+#endif // DEBUG
+#endif // Assert
+
+/* Dependencies */
+#define CHATTY_IMPL
+#include "chatty.h"
+#undef CHATTY_IMPL
+
+#define ARENA_IMPL
+#include "arena.h"
+#undef ARENA_IMPL
+#include "protocol.h"
+
+/* Configuration options */
// timeout on polling
#define TIMEOUT 60 * 1000
// max pending connections
@@ -102,7 +119,7 @@ printTextMessage(TextMessage* message, Client* client, u8 wide)
} else {
u8 str[message->len];
wcstombs((char*)str, (wchar_t*)&message->text, message->len * sizeof(*message->text));
- loggingf("TextMessage: %s [%s] (%d)%s\n", timestamp, client->author, message->len, str);
+ LoggingF("TextMessage: %s [%s] (%d)%s\n", timestamp, client->author, message->len, str);
}
}
@@ -135,7 +152,7 @@ sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, Heade
nsend = sendAnyMessage(fd, *header, anyMessage);
assert(nsend != -1);
- loggingf("sendToOthers "CLIENT_FMT"|%d<-%s %d bytes\n", CLIENT_ARG((clients[i])), fd, headerTypeString(header->type), nsend);
+ LoggingF("sendToOthers "CLIENT_FMT"|%d<-%s %d bytes\n", CLIENT_ARG((clients[i])), fd, headerTypeString(header->type), nsend);
}
}
@@ -165,7 +182,7 @@ sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, v
else
assert(0);
assert(nsend != -1);
- loggingf("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type),
+ LoggingF("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type),
CLIENT_ARG(clients[i]),
nsend);
}
@@ -175,7 +192,7 @@ sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, v
void
disconnect(Client* client)
{
- loggingf("Disconnecting "CLIENT_FMT"\n", CLIENT_ARG((*client)));
+ LoggingF("Disconnecting "CLIENT_FMT"\n", CLIENT_ARG((*client)));
if (client->unifd && client->unifd->fd != -1)
{
close(client->unifd->fd);
@@ -215,7 +232,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
s32 nrecv = 0;
Client* client = 0;
- loggingf("authenticate (%d)|" HEADER_FMT "\n", pollfd->fd, HEADER_ARG(header));
+ LoggingF("authenticate (%d)|" HEADER_FMT "\n", pollfd->fd, HEADER_ARG(header));
/* Scenario 1: Search for existing client */
if (header.type == HEADER_TYPE_ID)
@@ -227,7 +244,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
client = getClientByID((Client*)clientsArena->addr, nclients, message.id);
if (!client)
{
- loggingf("authenticate (%d)|notfound\n", pollfd->fd);
+ LoggingF("authenticate (%d)|notfound\n", pollfd->fd);
header.type = HEADER_TYPE_ERROR;
ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_NOTFOUND);
sendAnyMessage(pollfd->fd, header, &error_message);
@@ -235,7 +252,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
}
else
{
- loggingf("authenticate (%d)|found [%s](%lu)\n", pollfd->fd, client->author, client->id);
+ LoggingF("authenticate (%d)|found [%s](%lu)\n", pollfd->fd, client->author, client->id);
header.type = HEADER_TYPE_ERROR;
ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_SUCCESS);
sendAnyMessage(pollfd->fd, header, &error_message);
@@ -258,7 +275,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
nrecv = recv(pollfd->fd, &message, sizeof(message), 0);
if (nrecv != sizeof(message))
{
- loggingf("authenticate (%d)|err: %d/%lu bytes\n", pollfd->fd, nrecv, sizeof(message));
+ LoggingF("authenticate (%d)|err: %d/%lu bytes\n", pollfd->fd, nrecv, sizeof(message));
return 0;
}
@@ -279,7 +296,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
#ifdef IMPORT_ID
write(clients_file, client, sizeof(*client));
#endif
- loggingf("authenticate (%d)|Added [%s](%lu)\n", pollfd->fd, client->author, client->id);
+ LoggingF("authenticate (%d)|Added [%s](%lu)\n", pollfd->fd, client->author, client->id);
// Send ID to new client
HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID);
@@ -292,7 +309,7 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade
return client;
}
- loggingf("authenticate (%d)|Wrong header expected %s or %s\n", pollfd->fd,
+ LoggingF("authenticate (%d)|Wrong header expected %s or %s\n", pollfd->fd,
headerTypeString(HEADER_TYPE_INTRODUCTION),
headerTypeString(HEADER_TYPE_ID));
return 0;
@@ -303,15 +320,15 @@ main(int argc, char** argv)
{
signal(SIGPIPE, SIG_IGN);
- logfd = 2;
+ LogFD = 2;
// optional logging
if (argc > 1)
{
if (*argv[1] == '-')
if (argv[1][1] == 'l')
{
- logfd = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
- assert(logfd != -1);
+ LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ assert(LogFD != -1);
}
}
@@ -338,7 +355,7 @@ main(int argc, char** argv)
err = listen(serverfd, MAX_CONNECTIONS);
assert(!err);
- loggingf("Listening on :%d\n", PORT);
+ LoggingF("Listening on :%d\n", PORT);
}
Arena clientsArena;
@@ -374,7 +391,7 @@ main(int argc, char** argv)
if (statbuf.st_size > 0)
{
ArenaPush(&clientsArena, statbuf.st_size);
- loggingf("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients));
+ LoggingF("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients));
nclients += statbuf.st_size / sizeof(*clients);
// Reset pointers on imported clients
@@ -385,7 +402,7 @@ main(int argc, char** argv)
}
}
for (u32 i = 0; i < nclients - 1; i++)
- loggingf("Imported: " CLIENT_FMT "\n", CLIENT_ARG(clients[i]));
+ LoggingF("Imported: " CLIENT_FMT "\n", CLIENT_ARG(clients[i]));
#else
clients_file = 0;
#endif
@@ -412,11 +429,11 @@ main(int argc, char** argv)
if (clientfd == -1)
{
- loggingf("Error while accepting connection (%d)\n", clientfd);
+ LoggingF("Error while accepting connection (%d)\n", clientfd);
continue;
}
else
- loggingf("New connection(%d)\n", clientfd);
+ LoggingF("New connection(%d)\n", clientfd);
// TODO: find empty space in arena (fragmentation)
if (nclients + 1 == MAX_CONNECTIONS)
@@ -426,14 +443,14 @@ main(int argc, char** argv)
sendAnyMessage(clientfd, header, &message);
if (clientfd != -1)
close(clientfd);
- loggingf("Max clients reached. Rejected connection\n");
+ LoggingF("Max clients reached. Rejected connection\n");
}
else
{
// no more space, allocate
struct pollfd* pollfd = ArenaPush(&fdsArena, sizeof(*pollfd));
pollfd->fd = clientfd;
- loggingf("Added pollfd(%d)\n", clientfd);
+ LoggingF("Added pollfd(%d)\n", clientfd);
}
}
@@ -441,14 +458,14 @@ main(int argc, char** argv)
{
if (!(fds[conn].revents & POLLIN)) continue;
if (fds[conn].fd == -1) continue;
- loggingf("Message(%d)\n", fds[conn].fd);
+ LoggingF("Message(%d)\n", fds[conn].fd);
// We received a message, try to parse the header
HeaderMessage header;
s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0);
if(nrecv == -1)
{
- loggingf("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno);
+ LoggingF("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno);
};
Client* client;
@@ -457,36 +474,36 @@ main(int argc, char** argv)
client = getClientByFD(clients, nclients, fds[conn].fd);
if (client)
{
- loggingf("Received %d/%lu bytes "CLIENT_FMT"\n", nrecv, sizeof(header), CLIENT_ARG((*client)));
+ LoggingF("Received %d/%lu bytes "CLIENT_FMT"\n", nrecv, sizeof(header), CLIENT_ARG((*client)));
disconnectAndNotify(clients, nclients, client);
}
else
{
- loggingf("Got error/disconnect from unauthenticated client\n");
+ LoggingF("Got error/disconnect from unauthenticated client\n");
close(fds[conn].fd);
fds[conn].fd = -1;
}
continue;
}
- loggingf("Received(%d): " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header));
+ LoggingF("Received(%d): " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header));
// Authentication
if (!header.id)
{
- loggingf("No client for connection(%d)\n", fds[conn].fd);
+ LoggingF("No client for connection(%d)\n", fds[conn].fd);
client = authenticate(&clientsArena, clients_file, fds + conn, header);
if (!client)
{
- loggingf("Could not initialize client (%d)\n", fds[conn].fd);
+ LoggingF("Could not initialize client (%d)\n", fds[conn].fd);
close(fds[conn].fd);
fds[conn].fd = -1;
}
/* This is the first time a message is sent, because unifd is not yet set. */
else if (!client->unifd)
{
- loggingf("Send connected message\n");
+ LoggingF("Send connected message\n");
local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE);
header.id = client->id;
PresenceMessage message = {.type = PRESENCE_TYPE_CONNECTED};
@@ -498,7 +515,7 @@ main(int argc, char** argv)
client = getClientByID(clients, nclients, header.id);
if (!client)
{
- loggingf("No client for id %d\n", fds[conn].fd);
+ LoggingF("No client for id %d\n", fds[conn].fd);
header.type = HEADER_TYPE_ERROR;
ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND);
@@ -516,7 +533,7 @@ main(int argc, char** argv)
case HEADER_TYPE_TEXT:
{
TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd);
- loggingf("Received(%d): ", fds[conn].fd);
+ LoggingF("Received(%d): ", fds[conn].fd);
printTextMessage(text_message, client, 0);
sendToOthers(clients, nclients, client, UNIFD, &header, text_message);
@@ -547,7 +564,7 @@ main(int argc, char** argv)
assert(nrecv != -1);
} break;
default:
- loggingf("Unhandled '%s' from "CLIENT_FMT"(%d)\n", headerTypeString(header.type),
+ LoggingF("Unhandled '%s' from "CLIENT_FMT"(%d)\n", headerTypeString(header.type),
CLIENT_ARG((*client)),
fds[conn].fd);
disconnectAndNotify(client, nclients, client);
diff --git a/test.h b/test.h
new file mode 100644
index 0000000..febdabd
--- /dev/null
+++ b/test.h
@@ -0,0 +1,217 @@
+// Library used for writing tests, must be included once for definitions and
+// once for the `main()` function.
+//
+// DOCUMENTATION
+// Expect(expr)
+// Macro that will return early if the expression is false, calling
+// `tb_shutdown()` to save the terminal. It will also print the expression
+// and line on which it occurred for convenience
+//
+// test_functions TestFunctions
+// Global variable containing the function (test). This array is
+// 0-terminated.
+//
+// TESTFUNC()
+// Macro for adding functions to TestFunctions array conveniently.
+//
+// tb_get_cell(x, y)
+// Function
+//
+// EXAMPLE
+// #define TEST_IMPL
+// #include "test.h"
+//
+// bool FooTest()
+// {
+// Expect(0 == 1);
+// return true;
+// }
+//
+// test_functions TestFunctions[] = {
+// TESTFUNC(FooTest),
+// {0}
+// }
+//
+// int main(int Argc, int Argv)
+// {
+// Test(Argc, Argv);
+// }
+
+#ifndef TEST_H
+#define TEST_H
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "chatty.h"
+
+/* Macro's */
+#define Expect(expr) if (!(expr)) \
+ { \
+ tb_shutdown(); \
+ printf("\n %d: %s\n", __LINE__, #expr); \
+ return false; \
+ }
+
+#define TESTFUNC(func) { func, #func }
+
+/* Types */
+typedef struct {
+ bool (*Func)(void);
+ const char * Name;
+} test_functions;
+
+typedef struct {
+ s32 X, Y, W, H;
+} rect;
+
+/* Declarations */
+struct tb_cell tb_get_cell(s32 x, s32 y);
+void RectCopy(rect Dest, rect Source);
+bool RectCmp(rect Dest, rect Source);
+bool TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen);
+bool TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg);
+
+/* Global variables */
+void (*Before)(void);
+void (*After)(void);
+
+/* Functions*/
+struct tb_cell
+tb_get_cell(s32 x, s32 y)
+{
+ return global.back.cells[global.width * y + x];
+}
+
+void
+RectCopy(rect Dest, rect Source)
+{
+ for (u32 Y = 0; Y < Source.H; Y++)
+ {
+ for (u32 X = 0; X < Source.W; X++)
+ {
+ struct tb_cell Cell = tb_get_cell(Source.X + X, Source.Y + Y);
+ tb_set_cell(Dest.X + X, Dest.Y + Y, Cell.ch, Cell.fg, Cell.bg);
+ }
+ }
+}
+
+bool
+RectCmp(rect Dest, rect Source)
+{
+ for (u32 Y = 0; Y < Source.H; Y++)
+ {
+ for (u32 X = 0; X < Source.W; X++)
+ {
+ struct tb_cell SourceCell = tb_get_cell(Source.X + X, Source.Y + Y);
+ struct tb_cell DestCell = tb_get_cell(Dest.X + X, Dest.Y + Y);
+ if (!(SourceCell.fg == DestCell.fg &&
+ SourceCell.bg == DestCell.bg &&
+ SourceCell.ch == DestCell.ch))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+TextCmp(s32 X, s32 Y, wchar_t *Text, s32 TextLen)
+{
+ for (u32 At = 0;
+ At < TextLen;
+ At++)
+ {
+ struct tb_cell DestCell = tb_get_cell(X++, Y);
+ if (DestCell.ch != Text[At]) return false;
+ }
+
+ return true;
+}
+
+bool
+TextCmpWithColor(s32 X, s32 Y, wchar_t *Text, s32 TextLen, s32 fg, s32 bg)
+{
+ for (u32 At = 0;
+ At < TextLen;
+ At++)
+ {
+ struct tb_cell DestCell = tb_get_cell(X++, Y);
+ if (DestCell.ch != Text[At] ||
+ DestCell.fg != fg ||
+ DestCell.bg != bg)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+int
+Test(test_functions *TestFunctions, int Argc, char *Argv[])
+{
+ u32 TestFunctionsLen = 0;
+ while (TestFunctions[TestFunctionsLen].Func) TestFunctionsLen++;
+
+ if (Argc > 1)
+ {
+ for (u32 Arg = 0;
+ Arg < Argc;
+ Arg++)
+ {
+ char *Function = Argv[Arg];
+
+ for (u32 TestFunc = 0;
+ TestFunc < TestFunctionsLen;
+ TestFunc++)
+ {
+ if(!strcmp(Function, TestFunctions[TestFunc].Name))
+ {
+ printf("%s ", TestFunctions[TestFunc].Name);
+
+ if (Before) Before();
+ bool Ret = TestFunctions[TestFunc].Func();
+ if (After) After();
+
+ if (Ret)
+ {
+ printf("\033[32mPASSED\033[0m\n");
+ }
+ else
+ {
+ printf("\033[31mFAILED\033[0m\n"); \
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int At = 0;
+ TestFunctions[At].Func;
+ At++)
+ {
+ printf("%s ", TestFunctions[At].Name);
+
+ if (Before) Before();
+ bool Ret = TestFunctions[At].Func();
+ if (After) After();
+
+ if (Ret)
+ {
+ printf("\033[32mPASSED\033[0m\n");
+ }
+ else
+ {
+ printf("\033[31mFAILED\033[0m\n"); \
+ }
+ }
+ }
+
+ return 0;
+}
+
+#endif // TEST_H
diff --git a/tests.c b/tests.c
new file mode 100644
index 0000000..a9a73a1
--- /dev/null
+++ b/tests.c
@@ -0,0 +1,37 @@
+#define MAX_INPUT_LEN 255
+
+#define TB_IMPL
+#include "external/termbox2.h"
+
+#include "chatty.h"
+
+#define TEST_IMPL
+#include "test.h"
+
+#define Assert(expr) if (!(expr)) *(u8*)0 = 0
+
+bool
+DrawingTest(void)
+{
+ struct tb_event ev = {0};
+
+
+
+ return true;
+}
+
+int
+main(int Argc, char* Argv[])
+{
+ test_functions TestFunctions[] = {
+ TESTFUNC(DrawingTest),
+ { 0 }
+ };
+
+ Assert(setlocale(LC_ALL, ""));
+
+ Before = (void(*)(void))tb_init;
+ After = (void(*)(void))tb_shutdown;
+
+ return(Test(TestFunctions, Argc, Argv));
+}
diff --git a/types.h b/types.h
new file mode 100644
index 0000000..7bd04c5
--- /dev/null
+++ b/types.h
@@ -0,0 +1,15 @@
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <stdbool.h>
+#include <stdint.h>
+typedef uint8_t u8;
+typedef uint16_t u16;
+typedef uint32_t u32;
+typedef uint64_t u64;
+typedef int8_t s8;
+typedef int16_t s16;
+typedef int32_t s32;
+typedef int64_t s64;
+
+#endif // TYPES_H
diff --git a/ui.h b/ui.h
index bdb4167..078a456 100644
--- a/ui.h
+++ b/ui.h
@@ -1,4 +1,14 @@
-#include "chatty.h"
+#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.
@@ -18,24 +28,535 @@ typedef struct {
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
+bool
is_whitespace(u32 ch)
{
if (ch == L' ')
- return True;
- return False;
+ return true;
+ return false;
}
// Return True if ch is a supported markdown markup character
// TODO: tilde
-Bool
+bool
is_markdown(u32 ch)
{
if (ch == L'_' ||
ch == L'*')
- return True;
- return False;
+ return true;
+ return false;
}
// Print `Text`, `Len` characters long with markdown
@@ -85,8 +606,8 @@ 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);
+ Assert(XLimit > 0);
+ Assert(YLimit > 0);
u32 i = XLimit;
u32 PrevI = 0;
@@ -148,8 +669,8 @@ tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg,
{
XLimit -= XOffset;
YLimit -= YOffset;
- assert(YLimit > 0);
- assert(XLimit > 0);
+ Assert(YLimit > 0);
+ Assert(XLimit > 0);
u32 TextIndex = XLimit;
u32 PrevTextIndex = 0;
@@ -201,8 +722,8 @@ tb_print_wrapped_with_markdown(u32 XOffset, u32 YOffset, u32 fg, u32 bg,
}
tb_printf(X++, Y, fg, bg, "%lc", Text[TextIndex]);
}
- assert(WrapPositionsIndex == WrapPositionsLen);
- assert(MDFormat.Len == MDFormatOptionsIndex);
+ Assert(WrapPositionsIndex == WrapPositionsLen);
+ Assert(MDFormat.Len == MDFormatOptionsIndex);
return WrapPositionsLen + 1;
}
@@ -296,186 +817,3 @@ preprocess_markdown(Arena* ScratchArena, wchar_t* Text, u32 Len)
return Result;
}
-
-u32 InputBoxMarginX = 1;
-u32 InputBoxPaddingX = 1;
-#define INPUT_BOX_BORDER_WIDTH 1
-#define INPUT_BOX_MIN_TEXT_WIDTH 1
-#define INPUT_BOX_MIN_TEXT_HEIGHT 1
-
-u32
-GetInputBoxMinimumWidth()
-{
- return InputBoxPaddingX * 2 +
- InputBoxMarginX * 2 +
- INPUT_BOX_BORDER_WIDTH * 2 +
- INPUT_BOX_MIN_TEXT_WIDTH;
-}
-
-u32
-GetInputBoxMinimumHeight()
-{
- return INPUT_BOX_BORDER_WIDTH * 2 +
- INPUT_BOX_MIN_TEXT_HEIGHT;
-}
-
-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)
-
- u32 MarginX = InputBoxMarginX;
- u32 PaddingX = InputBoxPaddingX;
- u32 BorderWidth = INPUT_BOX_BORDER_WIDTH;
-
- // 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;
-
- // XOffset and YOffset are needed for setting the cursor position
- u32 XOffset = 0, YOffset = 0;
- u32 TextOffset = 0;
-
- // If there is more than one line, implement vertical wrapping otherwise scroll the text
- // horizontally.
- if (TextHeight > 1)
- {
- // 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
- TextOffset = (TextLen / TextWidth - (TextHeight - 1)) * TextWidth;
- }
-
- // Print the text
- while (TextOffset < TextLen)
- {
- for (YOffset = 0;
- YOffset < TextHeight && TextOffset < TextLen;
- YOffset++)
- {
- for (XOffset = 0;
- XOffset < TextWidth && TextOffset < TextLen;
- XOffset++)
- {
- tb_printf(TextX + XOffset, TextY + YOffset, 0, 0, "%lc", Text[TextOffset]);
- TextOffset++;
- }
- }
- }
- }
- else
- {
- // Scrooll the text horizontally
- if (TextLen >= TextDisplaySize)
- {
- TextOffset = TextLen - TextWidth;
- XOffset = TextWidth;
- }
- else
- {
- XOffset = TextLen;
- }
- YOffset = 1;
- tb_printf(TextX, TextY, 0, 0, "%ls", Text + TextOffset);
- }
-
-#ifdef DEBUG
-#ifdef MAX_INPUT_LEN
- tb_printf(BoxX + 1, BoxY, 0, 0, "%d/%d [%dx%d] %dx%d %d (%d,%d)+(%d,%d)",
- TextLen, MAX_INPUT_LEN,
- BoxWidth, BoxHeight,
- TextWidth, TextHeight,
- TextOffset,
- TextX, TextY,
- XOffset, YOffset);
-#else
- tb_printf(BoxX + 1, BoxY, 0, 0, "%d/%d [%dx%d] %dx%d %d (%d,%d)+(%d,%d)",
- TextLen, TextLen,
- BoxWidth, BoxHeight,
- TextWidth, TextHeight,
- TextOffset,
- TextX, TextY,
- XOffset, YOffset);
-
-#endif
-#endif
-
- // 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 && TextHeight > 1)
- {
- // 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;
- }
- }
-}
diff --git a/ui_box.h b/ui_box.h
new file mode 100644
index 0000000..c19b9dd
--- /dev/null
+++ b/ui_box.h
@@ -0,0 +1,472 @@
+#ifndef BOX_H
+#define BOX_H
+
+// assumes MAX_INPUT_LEN to be set
+//
+// #define MAX_INPUT_LEN 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 ScrollLeft(rect Text, u32 *TextOffset);
+void ScrollRight(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);
+void TextBoxDrawText(rect TextR, wchar_t *Input, u32 InputLen, u32 TextOffset);
+
+// 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,
+ (MAX_INPUT_LEN - 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 and edit Input and updates 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 0 no key event was handled
+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 in Input in the TextR rectangle.
+// Skip printing TextOffset amount of characters.
+// InputLen is the amount of characters in Input.
+//
+// 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
+TextBoxDraw(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.
+void
+TextBoxDrawWrapped(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 // BOX_H
diff --git a/ui_wrapped.c b/ui_wrapped.c
new file mode 100644
index 0000000..67ddffe
--- /dev/null
+++ b/ui_wrapped.c
@@ -0,0 +1,82 @@
+#define MAX_INPUT_LEN 255
+
+#define TB_IMPL
+#include "external/termbox2.h"
+#undef TB_IMPL
+#define CHATTY_IMPL
+#include "ui.h"
+#undef CHATTY_IMPL
+
+void
+DrawBoxTest(rect Box, wchar_t *DummyText)
+{
+ u32 TextLen = wcslen(DummyText);
+
+ wchar_t Text[TextLen];
+ bzero(Text, sizeof(Text));
+ wcsncpy(Text, DummyText, TextLen + 1); // copy n*ull terminator
+
+ rect TextR = TEXTBOXFROMBOX(Box);
+ // fill the cursor for reference
+ for (s32 Y = 0; Y < TextR.H; Y++)
+ for (s32 X = 0; X < TextR.W; X++)
+ tb_printf(TextR.X + X, TextR.Y + Y, 0, TB_BLUE, " ");
+
+ DrawBox(Box, 0);
+ tb_present();
+ TextBoxDrawWrapped(TextR, Text, TextLen);
+ tb_printf(0, TextR.Y - 1, 0, 0,
+ "%d (%d, %d) ~(%d, %d)",
+ TextLen,
+ global.cursor_x, global.cursor_y,
+ global.cursor_x - TextR.X, global.cursor_y - TextR.Y);
+ tb_printf(global.cursor_x, global.cursor_y, 0, TB_RED, " ");
+}
+
+struct tb_cell
+tb_get_cell(s32 X, s32 Y)
+{
+ return global.back.cells[global.width * Y + X];
+}
+
+
+int
+main(int Argc, char* Argv[])
+{
+ struct tb_event ev;
+ rect Box = {0, 0, 24, 4};
+
+ setlocale(LC_ALL, "");
+
+ tb_init();
+ bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]);
+
+ /* Text that does not fit */
+ DrawBoxTest(Box, L"This is some dummy text meant to be wrapped multiple times.");
+ Box.Y += Box.H;
+
+ /* Text that fits */
+ DrawBoxTest(Box, L"That is some.");
+ Box.Y += Box.H;
+
+ /* Text ending on a space */
+ DrawBoxTest(Box, L"There is something. ");
+ Box.Y += Box.H;
+
+ /* Text that fits the surface */
+ DrawBoxTest(Box, L"This is something. This is something.");
+ Box.Y += Box.H;
+
+ /* Text that wraps once */
+ DrawBoxTest(Box, L"This is something I do not.");
+
+ rect NewBox = Box;
+ NewBox.X += Box.W + 2;
+ DrawBox(NewBox, 0);
+
+ tb_present();
+ tb_poll_event(&ev);
+
+ tb_shutdown();
+ return 0;
+}
diff --git a/utf8toASCII.c b/utf8toASCII.c
deleted file mode 100644
index 988a69b..0000000
--- a/utf8toASCII.c
+++ /dev/null
@@ -1,163 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdio.h>
-#include <locale.h>
-#include <assert.h>
-#include <wchar.h>
-#include <stdint.h>
-
-typedef uint8_t u8;
-typedef uint16_t u16;
-typedef uint32_t u32;
-typedef uint64_t u64;
-typedef int8_t s8;
-typedef int16_t s16;
-typedef int32_t s32;
-typedef int64_t s64;
-
-static size_t
-UTF8Compress(size_t InSize, wchar_t* In, size_t OutSize, u8* OutBase)
-{
- wchar_t* InEnd = (wchar_t*)((u8*)In + InSize);
- u8* Out = OutBase;
-
-#define MAX_LITERAL_COUNT 255
- u8 ASCIILiterals[MAX_LITERAL_COUNT] = {0};
- u8 ASCIILiteralsCount = 0;
- wchar_t UTF8Literals[MAX_LITERAL_COUNT] = {0};
- u8 UTF8LiteralsCount = 0;
-
- while (In < InEnd)
- {
- wchar_t CurrentChar = In[0];
-
- // Check consecutive ascii characters
- while(CurrentChar == (u8)CurrentChar &&
- ASCIILiteralsCount < MAX_LITERAL_COUNT)
- {
- ASCIILiterals[ASCIILiteralsCount++] = (u8)CurrentChar;
- CurrentChar = *++In;
- }
-
- while(CurrentChar != (u8)CurrentChar &&
- UTF8LiteralsCount < MAX_LITERAL_COUNT)
- {
- UTF8Literals[UTF8LiteralsCount++] = CurrentChar;
- CurrentChar = *++In;
- }
-
- // Encode ASCII/UTF8 pair
- *Out++ = ASCIILiteralsCount;
- for (u8 ch = 0;
- ch < ASCIILiteralsCount;
- ch++)
- {
- *Out = ASCIILiterals[ch];
- Out += sizeof(ASCIILiterals[ch]);
- }
- ASCIILiteralsCount = 0;
-
- *Out++ = UTF8LiteralsCount;
- for (u8 ch = 0;
- ch < UTF8LiteralsCount;
- ch++)
- {
- *(wchar_t*)Out = UTF8Literals[ch];
- Out += sizeof(UTF8Literals[ch]);
- }
- UTF8LiteralsCount = 0;
-
- }
-#undef MAX_LITERAL_COUNT
- assert(In == InEnd);
-
- return Out - OutBase;
-}
-
-static void
-PrintCompressedUTF8(u8* In, size_t InSize)
-{
- u8* InEnd = In + InSize;
-
- while (In < InEnd)
- {
- u8 ASCIICount = *In++;
- wprintf(L"%dA(\"", ASCIICount);
- while(ASCIICount--)
- {
- wprintf(L"%c", *In);
- In += sizeof(u8);
- }
- wprintf(L"\") ");
-
- u8 UTF8Count = *In++;
- wprintf(L"%dU(\"", UTF8Count);
- while(UTF8Count--)
- {
- wprintf(L"%lc", *(wchar_t*)In);
- In += sizeof(wchar_t);
- }
- wprintf(L"\") ");
- }
- wprintf(L"\n");
-
- assert(In == InEnd);
-}
-
-static void
-UTF8Decompress(size_t InSize, u8* In, size_t OutSize, wchar_t* Out)
-{
- u8* InEnd = In + InSize;
-
- while (In < InEnd)
- {
- u8 ASCIICount = *In++;
- while(ASCIICount--)
- {
- *Out++ = *In++;
- }
-
- u8 UTF8Count = *In++;
- while(UTF8Count--)
- {
- *Out++ = *(wchar_t*)In;
- In += sizeof(wchar_t);
- }
- }
- assert(In == InEnd);
-}
-
-// Size is the size of the UTF8 string in bytes. "aaa" would be 12.
-size_t
-UTF8GetMaximumCompressedSize(size_t Size)
-{
- // The largest would be if there was only one unicode point in which case we store 0 for ascii 1
- // for unicode and the raw codepoint. 1 + 1 + 4 * CodepointNum
- return Size + 2;
-}
-
-int
-main(int Argc, char* Argv[]) {
- assert(setlocale(LC_ALL, "") != 0);
-
- wchar_t* InBuf = L"text│tt│";
- size_t InSize = wcslen(InBuf) * 4;
-
- size_t OutSize = UTF8GetMaximumCompressedSize(InSize);
- u8 OutBuf[OutSize];
-
- size_t CompressedSize = UTF8Compress(InSize, InBuf, OutSize, OutBuf);
-
- fwprintf(stderr, L"Raw string: \"%ls\"\n", InBuf);
- fwprintf(stderr, L"Compressed %lu bytes -> %lu bytes.\n", InSize, CompressedSize);
-
- size_t DecompressedSize = InSize;
- wchar_t *DecompressedBuffer = malloc(DecompressedSize);
-
- UTF8Decompress(CompressedSize, OutBuf, DecompressedSize, DecompressedBuffer);
- fwprintf(stderr, L"Decompressed: \"%ls\"\n", DecompressedBuffer);
-
- PrintCompressedUTF8(OutBuf, CompressedSize);
-
- return 0;
-}