Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Ignore Claude-related configuration and local files
.claude/
!*.claudeignore
.claudeignore
.claude-settings.json
.claude-cache/
137 changes: 137 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

### Build and Run

#### Linux/Unix/macOS
```bash
# Build all demos
make

# Run demos to test changes
./demo_basic # Test basic UI elements and events
./demo_bounce # Test animation and rendering
./demo_drag # Test mouse interaction

# Clean build artifacts
make clean
```

#### Windows (MSVC)
```cmd
# Build all demos (from Developer Command Prompt, use native cmd not Cygwin)
nmake /f Makefile.nmake

# Or build individually with cl.exe
cl /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /D_WIN32 /Fe"demos\demo_basic.exe" demos\demo_basic.c

# Run demos (built in demos directory)
demos\demo_basic.exe
demos\demo_bounce.exe
demos\demo_drag.exe

# Clean build artifacts
nmake /f Makefile.nmake clean
```

### Development
```bash
# Debug build (modify Makefile CFLAGS to use -Og -pipe -g instead of -O3)
make

# Compile single demo for testing
cc -O3 -pipe demos/demo_basic.c -lm -o demo_basic
```

## Architecture

### Single-Header Library Design
The entire library is contained in `tuibox.h` (679 lines), with Windows compatibility provided by `tuibox_win.h`. This includes:
- Core UI management structures (`ui_t`, `ui_box_t`, `ui_evt_t`)
- Embedded vec.h for dynamic arrays
- ANSI escape sequence handling
- Mouse and keyboard event processing

### Core Data Flow
```
User Input → ui_loop() → ui_update() → Event Callbacks → ui_draw() → Terminal Output
```

### Key Structures
- **ui_t**: Main UI container managing terminal state, boxes, and events
- **ui_box_t**: Individual UI elements with position, render cache, and callbacks
- `draw`: Rendering function pointer
- `onclick`/`onhover`: Event callbacks
- `cache`: Rendered content cache
- `watch`: Dirty flag pointer for cache invalidation

### Rendering System
- Uses ANSI escape sequences directly (no ncurses dependency)
- Caching system: Elements only re-render when their `watch` value changes
- Supports 24-bit truecolor
- Coordinate system: 1-indexed, top-left origin

### Event Handling
- Mouse events via terminal escape sequences (`\033[<`)
- Keyboard events through `ui_key()` registration
- Callbacks receive box pointer and can access custom data via `data1`/`data2`

## Important Patterns

### Adding UI Elements
Always use `ui_add()` with proper callbacks:
```c
ui_add(&u, x, y, width, height, screen_id, render_func, click_handler, hover_handler, data);
```

### Cache Management
Elements with a `watch` pointer only re-render when the watched value changes. Set `watch = NULL` for always-redraw behavior.

### Screen Management
Multiple screens supported via `screen` field. Switch screens by setting `u.screen`.

### Memory Management
- `ui_new()` initializes and enters raw mode
- `ui_free()` **must** be called to restore terminal state
- Dynamic arrays (vec_t) handle box/event storage automatically

## Testing Changes
Test modifications using the three demo programs:
1. `demo_basic` - Verify basic UI rendering and events work
2. `demo_bounce` - Check animation and custom render loops
3. `demo_drag` - Ensure mouse tracking and dragging function correctly

## Critical Notes
- Terminal must support ANSI escape sequences and mouse reporting
- Always call `ui_free()` before exit to restore terminal
- Coordinates are 1-indexed (terminal convention)
- `UI_CENTER_X`/`UI_CENTER_Y` constants (-1) trigger auto-centering
- Windows support requires Windows 10+ for full ANSI escape sequence support
- On Windows, use Developer Command Prompt for nmake or ensure cl.exe is in PATH

## Non-blocking Input Support
The library supports both blocking and non-blocking input modes:

### Usage
```c
ui_t u;
ui_new(0, &u);

/* Default: blocking input (read() waits for input) */
/* Use in ui_loop for event-driven programs */

/* Enable non-blocking input (read() returns immediately if no input) */
ui_set_nonblocking(&u, 1);
/* Use in animation loops or custom input polling */

/* Restore blocking mode */
ui_set_nonblocking(&u, 0);
```

### Platform Implementation
- **Unix/Linux/macOS**: Uses `fcntl()` with `O_NONBLOCK` flag on `stdin`
- **Windows**: Uses `GetNumberOfConsoleInputEvents()` to check input availability
- **Cross-platform**: Same API works on both platforms
24 changes: 23 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,30 @@ LIBS=-lm
CFLAGS=-O3 -pipe
DEBUGCFLAGS=-Og -pipe -g

.PHONY: tuibox
.PHONY: tuibox clean debug help
tuibox:
$(CC) demos/demo_basic.c -o demos/demo_basic $(LIBS) $(CFLAGS)
$(CC) demos/demo_bounce.c -o demos/demo_bounce $(LIBS) $(CFLAGS)
$(CC) demos/demo_drag.c -o demos/demo_drag $(LIBS) $(CFLAGS)

# Debug build
debug:
$(CC) demos/demo_basic.c -o demos/demo_basic $(LIBS) $(DEBUGCFLAGS)
$(CC) demos/demo_bounce.c -o demos/demo_bounce $(LIBS) $(DEBUGCFLAGS)
$(CC) demos/demo_drag.c -o demos/demo_drag $(LIBS) $(DEBUGCFLAGS)

# Clean build artifacts
clean:
rm -f demos/demo_basic demos/demo_bounce demos/demo_drag
rm -f demos/*.o *.o

# Help target
help:
@echo "Available targets:"
@echo " all - Build all demo programs (default)"
@echo " debug - Build with debug symbols"
@echo " clean - Remove all build artifacts"
@echo " help - Show this help message"
@echo ""
@echo "Usage:"
@echo " make [target]"
48 changes: 48 additions & 0 deletions Makefile.nmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Makefile for nmake (Microsoft Visual C++)
# Usage: nmake /f Makefile.nmake

CC = cl
CFLAGS = /O2 /W3 /D_CRT_SECURE_NO_WARNINGS /D_WIN32
DEBUGFLAGS = /Od /Zi /W3 /D_CRT_SECURE_NO_WARNINGS /D_WIN32

all: demos\demo_basic.exe demos\demo_bounce.exe demos\demo_drag.exe

demos\demo_basic.exe: demos\demo_basic.c tuibox.h tuibox_win.h
$(CC) $(CFLAGS) /Fe$@ demos\demo_basic.c

demos\demo_bounce.exe: demos\demo_bounce.c tuibox.h tuibox_win.h
$(CC) $(CFLAGS) /Fe$@ demos\demo_bounce.c

demos\demo_drag.exe: demos\demo_drag.c tuibox.h tuibox_win.h
$(CC) $(CFLAGS) /Fe$@ demos\demo_drag.c

debug: demos\debug_basic.exe demos\debug_bounce.exe demos\debug_drag.exe

demos\debug_basic.exe: demos\demo_basic.c tuibox.h tuibox_win.h
$(CC) $(DEBUGFLAGS) /Fe$@ demos\demo_basic.c

demos\debug_bounce.exe: demos\demo_bounce.c tuibox.h tuibox_win.h
$(CC) $(DEBUGFLAGS) /Fe$@ demos\demo_bounce.c

demos\debug_drag.exe: demos\demo_drag.c tuibox.h tuibox_win.h
$(CC) $(DEBUGFLAGS) /Fe$@ demos\demo_drag.c

clean:
-del demos\*.exe 2>NUL
-del demos\*.obj 2>NUL
-del demos\*.pdb 2>NUL
-del demos\*.ilk 2>NUL
-del *.exe 2>NUL
-del *.obj 2>NUL
-del *.pdb 2>NUL
-del *.ilk 2>NUL

help:
@echo Available targets:
@echo all - Build all demo programs (default)
@echo debug - Build with debug symbols
@echo clean - Remove all build artifacts
@echo help - Show this help message
@echo.
@echo Usage:
@echo nmake /f Makefile.nmake [target]
38 changes: 26 additions & 12 deletions demos/demo_basic.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,31 @@ void text(ui_box_t *b, char *out){
}

void draw(ui_box_t *b, char *out){
int x, y, len = 0, max = MAXCACHESIZE;
int x, y, len = 0;
const int max_len = MAXCACHESIZE - 100; /* Leave safety buffer */

sprintf(out, "");
for(y=0;y<b->h;y++){
for(x=0;x<b->w;x++){
out[0] = '\0';
for(y=0; y<b->h && len < max_len; y++){
for(x=0; x<b->w && len < max_len; x++){
/* Truecolor string to generate gradient */
len += sprintf(out + len, "\x1b[48;2;%i;%i;%im ", (int)round(255 * ((double)x / (double)b->w)), (int)round(255 * ((double)y / (double)b->h)), (int)round(255 * ((double)x * (double)y / ((double)b->w * (double)b->h))));

if(len + 1024 > max){
out = realloc(out, (max *= 2));
}
int written = sprintf(out + len, "\x1b[48;2;%i;%i;%im ",
(int)round(255 * ((double)x / (double)b->w)),
(int)round(255 * ((double)y / (double)b->h)),
(int)round(255 * ((double)x * (double)y / ((double)b->w * (double)b->h))));

if (len + written >= max_len) break;
len += written;
}
if (len + 10 < max_len) { /* Check space for reset sequence */
strcpy(out + len, "\x1b[0m\n");
len += 5; /* Length of "\x1b[0m\n" */
} else {
break; /* No more space */
}
strcat(out + len, "\x1b[0m\n");
}

/* Ensure null termination */
out[len] = '\0';
}

/* Function that runs on box click */
Expand All @@ -52,10 +63,11 @@ int main(){
/* Initialize UI data structures */
ui_new(0, &u);

/* Add new UI elements to screen 0 */
/* Add new UI elements to screen 0 - limit size to prevent buffer overflow */
ui_add(
1, 1,
u.ws.ws_col, u.ws.ws_row,
(u.ws.ws_col > 80) ? 80 : u.ws.ws_col,
(u.ws.ws_row > 24) ? 24 : u.ws.ws_row,
0,
NULL, 0,
draw,
Expand Down Expand Up @@ -85,5 +97,7 @@ int main(){
ui_update(&u);
}

/* This should never be reached due to ui_key("q", stop, &u), but just in case */
ui_free(&u);
return 0;
}
32 changes: 28 additions & 4 deletions demos/demo_bounce.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
*/

#include <math.h>

#ifdef _WIN32
#include <windows.h>
#include <signal.h>
#define usleep(x) Sleep((x)/1000)
#else
#include <signal.h>
#include <unistd.h>
#endif

#include "../tuibox.h"

ui_t u;

void draw(ui_box_t *b, char *out){
int x, y;
char tmp[256];

sprintf(out, "\x1b[48;2;255;255;255m");
for(y=0;y<b->h;y++){
Expand All @@ -22,7 +29,8 @@ void draw(ui_box_t *b, char *out){
}
}

void stop(){
void stop(int sig){
(void)sig; // Suppress unused parameter warning
ui_free(&u);
exit(0);
}
Expand Down Expand Up @@ -50,13 +58,27 @@ int main(){
);
b = ui_get(id, &u);

#ifndef _WIN32
signal(SIGTERM, stop);
signal(SIGQUIT, stop);
#endif
signal(SIGINT, stop);

/* Register an event on the q key to exit */
ui_key("q", stop, &u);

/* Enable non-blocking input for animation loop */
ui_set_nonblocking(&u, 1);

for(;;){
b->x = round((double)b->x + vx);
b->y = round((double)b->y + vy);
/* Check for input (non-blocking) */
char buf[64];
int n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0) {
_ui_update(buf, n, &u);
}
b->x = (int)round((double)b->x + vx);
b->y = (int)round((double)b->y + vy);

vx += ax;
vy += ay;
Expand All @@ -81,5 +103,7 @@ int main(){
usleep(10000);
}

/* This should never be reached due to ui_key("q", stop, &u), but just in case */
ui_free(&u);
return 0;
}
2 changes: 1 addition & 1 deletion demos/demo_drag.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void draw(ui_box_t *b, char *out){
int x, y;
char tmp[256];

sprintf(out, "");
out[0] = '\0';
for(y=0;y<b->h;y++){
for(x=0;x<b->w;x++){
/* Truecolor string to generate gradient */
Expand Down
Loading