Renamed every 'str' fiend and variable to 's'. Implemented edit_init.

This commit is contained in:
2026-01-09 00:46:42 +05:00
parent 11589d4623
commit b725182544
4 changed files with 95 additions and 81 deletions

View File

@@ -1,9 +1,9 @@
* about ********************************************************************** ## about
tim.h is a portable library to create simple terminal applications tim.h is a portable library to create simple terminal applications
Demo video: https://asciinema.org/a/zn3p0dsVCOQOzwY1S9gDfyaxQ Demo video: https://asciinema.org/a/zn3p0dsVCOQOzwY1S9gDfyaxQ
* quick start **************************************************************** ## quick start
#include "tim.h" // one header, no lib #include "tim.h" // one header, no lib
int main(void) { // int main(void) { //
@@ -20,7 +20,7 @@ int main(void) { //
} // atexit cleanup } // atexit cleanup
} // } //
* layout ********************************************************************* ## layout
The terminal's columns (x) and rows (y) are addressed by their coordinates, The terminal's columns (x) and rows (y) are addressed by their coordinates,
the origin is in the top left corner. the origin is in the top left corner.
@@ -56,7 +56,7 @@ take the full available space from parent.
The layout automatically adopts to terminal window resize events. The layout automatically adopts to terminal window resize events.
* colors ********************************************************************* ## colors
Most elements have a uint64 color argument which holds up to eight colors. Most elements have a uint64 color argument which holds up to eight colors.
Typically byte 0 is the text color and byte 1 is the background color. Typically byte 0 is the text color and byte 1 is the background color.
@@ -71,7 +71,7 @@ should be used if consistency is important.
xterm-256 color chart xterm-256 color chart
https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
* events ********************************************************************* ## events
tim_run blocks until it observes an event. Mouse and key events are always tim_run blocks until it observes an event. Mouse and key events are always
immediately followed by a draw event in order to make changes visible. immediately followed by a draw event in order to make changes visible.
@@ -88,7 +88,7 @@ The current event is stored in tim.event.
MOUSE_EVENT | mouse click MOUSE_EVENT | mouse click
VOID_EVENT | consumed event VOID_EVENT | consumed event
* elements ******************************************************************* ## elements
frame (x, y, w, h, color) frame (x, y, w, h, color)
@@ -115,11 +115,12 @@ button (str, x, y, w, h, color) -> bool
x/y/w/h see layout documentation x/y/w/h see layout documentation
color frame, background, text color frame, background, text
edit (state, x, y, w, color) -> bool edit (state, x, y, w, color) -> int
Draw text edit. Output is stored in state.str. Receives input events when Draw text edit. Output is stored in state.s.
focused by mouse click. Escape or return relinquish focus. Returns true Receives input events when focused by mouse click or by setting focus manually.
when return is pressed. Escape or return relinquish focus.
Returns key id if received key input.
state pointer to persistent edit state struct state pointer to persistent edit state struct
x/y/w see layout documentation x/y/w see layout documentation
@@ -148,7 +149,7 @@ radio (str, state, v, x, y, w, color) -> bool
x/y/w see layout documentation x/y/w see layout documentation
color radio, background, text color radio, background, text
* functions ****************************************************************** ## functions
tim_run (fps) -> bool tim_run (fps) -> bool
@@ -177,12 +178,12 @@ time_us () -> int64
Returns monotonic clock value in microseconds. Not affected by summer Returns monotonic clock value in microseconds. Not affected by summer
time or leap seconds. time or leap seconds.
* useful links *************************************************************** ## useful links
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
https://learn.microsoft.com/en-us/windows/console/ https://learn.microsoft.com/en-us/windows/console/
* bugs *********************************************************************** ## bugs
- Double buffering is still new, set ENABLE_DBUF to 0 if you see glitches - Double buffering is still new, set ENABLE_DBUF to 0 if you see glitches
- Double width characters like 彁 are not fully supported. Terminals do not - Double width characters like 彁 are not fully supported. Terminals do not
@@ -193,7 +194,7 @@ https://learn.microsoft.com/en-us/windows/console/
- Zero width code points are not supported - Zero width code points are not supported
- Windows cmd.exe resize events may be delayed - Windows cmd.exe resize events may be delayed
* compatibility ************************************************************** ## compatibility
emulator | support | remarks emulator | support | remarks
------------------|---------|---------------------------------- ------------------|---------|----------------------------------
@@ -222,7 +223,7 @@ https://learn.microsoft.com/en-us/windows/console/
XTerm | full | XTerm is law XTerm | full | XTerm is law
Zutty | full | Zutty | full |
* license ******************************************************************** ## license
MIT License MIT License

View File

@@ -51,7 +51,7 @@ int main(void) {
TEST(scan_str("a").width == 1); TEST(scan_str("a").width == 1);
TEST(scan_str("äß\no").width == 2); TEST(scan_str("äß\no").width == 2);
struct line ln = {.str = "foo\nbar"}; struct line ln = {.s = "foo\nbar"};
TEST(next_line(&ln) == true); TEST(next_line(&ln) == true);
TEST(!memcmp(ln.line, "foo", ln.size)); TEST(!memcmp(ln.line, "foo", ln.size));
TEST(next_line(&ln) == true); TEST(next_line(&ln) == true);

View File

@@ -25,12 +25,12 @@ static inline void test_screen(struct event* e) {
label(buf, 2, 0, A, A, 0xf); label(buf, 2, 0, A, A, 0xf);
sprintf(buf, "frame : [%c] %d", ": "[tim.frame & 1], tim.frame); sprintf(buf, "frame : [%c] %d", ": "[tim.frame & 1], tim.frame);
label(buf, 2, 1, A, A, 0xf); label(buf, 2, 1, A, A, 0xf);
sprintf(buf, "key : [%d] %s", ke.key, ke.str + (ke.key < 32)); sprintf(buf, "key : [%d] %s", ke.key, ke.s + (ke.key < 32));
label(buf, 2, 2, A, A, 0xf); label(buf, 2, 2, A, A, 0xf);
sprintf(buf, "mouse : [%d] %d:%d", me.key, me.x, me.y); sprintf(buf, "mouse : [%d] %d:%d", me.key, me.x, me.y);
label(buf, 2, 3, A, A, 0xf); label(buf, 2, 3, A, A, 0xf);
sprintf(buf, "input : %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx", sprintf(buf, "input : %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
e->str[0], e->str[1], e->str[2], e->str[3], e->str[4], e->str[5], e->str[6], e->str[7]); e->s[0], e->s[1], e->s[2], e->s[3], e->s[4], e->s[5], e->s[6], e->s[7]);
label(buf, 2, 4, A, A, 0xf); label(buf, 2, 4, A, A, 0xf);
// lower right // lower right
@@ -63,13 +63,13 @@ static inline void test_screen(struct event* e) {
} }
// edit // edit
static struct edit ed1 = {.str = "Edit 1"}; static struct edit ed1 = {.s = "Edit 1"};
static struct edit ed2 = {0}; static struct edit ed2 = {0};
edit(&ed1, 1, 10, 32, 0xff00ff); edit(&ed1, 1, 10, 32, 0xff00ff);
sprintf(buf, "cursor: %d length: %d", ed1.cursor, ed1.length); sprintf(buf, "cursor: %d length: %d", ed1.cursor, ed1.length);
label(buf, 2, 13, A, A, 0xf); label(buf, 2, 13, A, A, 0xf);
edit(&ed2, 1, 14, 32, 0xff00ff); edit(&ed2, 1, 14, 32, 0xff00ff);
label(ed2.str, 2, 17, A, A, 0xf); label(ed2.s, 2, 17, A, A, 0xf);
// checkbox // checkbox
static int chk[2] = {-1, 1}; static int chk[2] = {-1, 1};

135
tim.h
View File

@@ -117,9 +117,10 @@
// //
// edit (state, x, y, w, color) -> bool // edit (state, x, y, w, color) -> bool
// //
// Draw text edit. Output is stored in state.str. Receives input events when // Draw text edit. Output is stored in state.s.
// focused by mouse click. Escape or return relinquish focus. Returns true // Receives input events when focused by mouse click or by setting focus manually.
// when return is pressed. // Escape or return relinquish focus.
// Returns key id if received key input.
// //
// state pointer to persistent edit state struct // state pointer to persistent edit state struct
// x/y/w see layout documentation // x/y/w see layout documentation
@@ -357,7 +358,7 @@ struct text {
}; };
struct line { struct line {
const char* str; // input and parse state const char* s; // input and parse state
const char* line; // line strings, not terminated const char* line; // line strings, not terminated
int size; // line size in bytes int size; // line size in bytes
int width; // line width in glyph int width; // line width in glyph
@@ -368,13 +369,14 @@ struct event {
int32_t key; // used by KEY_EVENT and MOUSE_EVENT int32_t key; // used by KEY_EVENT and MOUSE_EVENT
int x; // used by MOUSE_EVENT int x; // used by MOUSE_EVENT
int y; // used by MOUSE_EVENT int y; // used by MOUSE_EVENT
char str[32]; // string representation of key char s[32]; // string representation of key
}; };
struct edit { struct edit {
int cursor; // cursor position (utf8) int cursor; // cursor position (utf8)
int length; // string length (utf8) int length; // string length (utf8)
char str[256]; // zero terminated buffer int capacity; // buffer size
char* s; // zero terminated buffer
}; };
struct state { struct state {
@@ -382,7 +384,7 @@ struct state {
int h; // screen height int h; // screen height
int frame; // frame counter int frame; // frame counter
struct event event; // current event struct event event; // current event
uintptr_t focus; // focused element void* focus; // focused element
int loop_stage; // loop stage int loop_stage; // loop stage
bool resized; // screen was resized bool resized; // screen was resized
int scope; // current scope int scope; // current scope
@@ -436,8 +438,12 @@ struct state tim = {
// like strlen, returns 0 on NULL or int overflow // like strlen, returns 0 on NULL or int overflow
static inline int ztrlen(const char* s) { static inline int ztrlen(const char* s) {
size_t n = s ? strlen(s) : 0; if(s == NULL)
return MAX((int)n, 0); return 0;
int n = strlen(s);
if(n < 0)
n = 0;
return n;
} }
// bit scan reverse, count leading zeros // bit scan reverse, count leading zeros
@@ -494,8 +500,9 @@ static int utfpos(const char* s, int pos) {
} }
// scan string for width and lines // scan string for width and lines
static struct text scan_str(const char* str) { static struct text scan_str(const char* s) {
const char* s = str ? str : ""; if(s == NULL)
s = "";
struct text t = { struct text t = {
.width = 0, .width = 0,
.lines = (s[0] != 0), .lines = (s[0] != 0),
@@ -514,17 +521,17 @@ static struct text scan_str(const char* str) {
// iterate through lines, false when end is reached // iterate through lines, false when end is reached
static bool next_line(struct line* l) { static bool next_line(struct line* l) {
if (!l->str || !l->str[0]) { if (!l->s || !l->s[0]) {
return false; return false;
} }
l->line = l->str; l->line = l->s;
l->size = 0; l->size = 0;
l->width = 0; l->width = 0;
for (const char* s = l->str; s[0] && s[0] != '\n'; s++) { for (const char* s = l->s; s[0] && s[0] != '\n'; s++) {
l->size += 1; l->size += 1;
l->width += (s[0] & 192) != 128 && (uint8_t)s[0] > 31; l->width += (s[0] & 192) != 128 && (uint8_t)s[0] > 31;
} }
l->str += l->size + !!l->str[l->size]; l->s += l->size + !!l->s[l->size];
return true; return true;
} }
@@ -563,7 +570,8 @@ static void signal_handler(int signal) {
static void update_screen_size(void) { static void update_screen_size(void) {
struct winsize ws = {0}; struct winsize ws = {0};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != 0) {
return; printf("ERROR: can't get console buffer size\n");
exit(1);
} }
int w = ws.ws_col; int w = ws.ws_col;
int h = ws.ws_row; int h = ws.ws_row;
@@ -601,9 +609,9 @@ static void reset_terminal(void) {
write_str(S("\33[?1049l")); // exit alternate buffer write_str(S("\33[?1049l")); // exit alternate buffer
} }
// parse input stored in e->str // parse input stored in e->s
static bool parse_input(struct event* restrict e, int n) { static bool parse_input(struct event* restrict e, int n) {
char* s = e->str; char* s = e->s;
if (n == 1 || s[0] != 27) { if (n == 1 || s[0] != 27) {
// regular key press // regular key press
@@ -694,7 +702,7 @@ static void read_event(int timeout_ms) {
if (pfd[1].revents & POLLIN) { if (pfd[1].revents & POLLIN) {
// received input // received input
int n = read(STDIN_FILENO, e->str, sizeof(e->str) - 1); int n = read(STDIN_FILENO, e->s, sizeof(e->s) - 1);
if (parse_input(e, n)) { if (parse_input(e, n)) {
return; return;
} }
@@ -727,7 +735,8 @@ static void update_screen_size(void) {
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; CONSOLE_SCREEN_BUFFER_INFO csbi = {0};
if (GetConsoleScreenBufferInfo(hout, &csbi) == 0) { if (GetConsoleScreenBufferInfo(hout, &csbi) == 0) {
return; printf("ERROR: can't get console buffer size\n");
exit(1);
} }
int w = csbi.srWindow.Right - csbi.srWindow.Left + 1; int w = csbi.srWindow.Right - csbi.srWindow.Left + 1;
int h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; int h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
@@ -839,7 +848,7 @@ static void read_event(int timeout_ms) {
return; return;
} }
e->key = chr; e->key = chr;
WideCharToMultiByte(CP_UTF8, 0, &chr, 1, e->str, sizeof(e->str), WideCharToMultiByte(CP_UTF8, 0, &chr, 1, e->s, sizeof(e->s),
NULL, NULL); NULL, NULL);
return; return;
} }
@@ -1076,16 +1085,16 @@ static inline void frame(int x, int y, int w, int h, uint64_t color) {
// text label // text label
// str : text - supports multiple lines // str : text - supports multiple lines
// color: background, text // color: background, text
static inline void label(const char* str, int x, int y, int w, int h, static inline void label(const char* s, int x, int y, int w, int h,
uint64_t color) { uint64_t color) {
if (tim.event.type == DRAW_EVENT) { if (tim.event.type == DRAW_EVENT) {
struct text s = scan_str(str); struct text t = scan_str(s);
w = (w == A) ? s.width : w; w = (w == A) ? t.width : w;
h = (h == A) ? s.lines : h; h = (h == A) ? t.lines : h;
struct rect r = abs_xywh(x, y, w, h); struct rect r = abs_xywh(x, y, w, h);
struct cell c = cell(" ", color, color >> 8); struct cell c = cell(" ", color, color >> 8);
draw_lot(c, r.x, r.y, r.w, r.h); draw_lot(c, r.x, r.y, r.w, r.h);
struct line l = {.str = str, .line = ""}; struct line l = {.s = s, .line = ""};
for (int i = 0; next_line(&l); i++) { for (int i = 0; next_line(&l); i++) {
draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg); draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg);
} }
@@ -1113,46 +1122,48 @@ static inline bool button(const char* txt, int x, int y, int w, int h,
/* edit ***********************************************************************/ /* edit ***********************************************************************/
static void edit_insert(struct edit* e, const char* s) { static void edit_insert(struct edit* e, const char* s) {
int dst_size = ztrlen(e->str); int dst_size = ztrlen(e->s);
int src_size = ztrlen(s); int src_size = ztrlen(s);
if (dst_size + src_size + 1 < (int)sizeof(e->str)) { if (dst_size + src_size < e->capacity) {
int len = utflen(s); // usually 1, except when smashing keys int len = utflen(s); // usually 1, except when smashing keys
int cur = utfpos(e->str, e->cursor); int cur = utfpos(e->s, e->cursor);
memmove(e->str + cur + src_size, e->str + cur, dst_size - cur); memmove(e->s + cur + src_size, e->s + cur, dst_size - cur);
memmove(e->str + cur, s, src_size); memmove(e->s + cur, s, src_size);
e->str[dst_size + src_size + 1] = 0; e->s[dst_size + src_size] = 0;
e->length += len; e->length += len;
e->cursor += len; e->cursor += len;
} }
} }
static void edit_delete(struct edit* e) { static void edit_delete(struct edit* e) {
int size = ztrlen(e->str); int size = ztrlen(e->s);
int cur = utfpos(e->str, e->cursor); int cur = utfpos(e->s, e->cursor);
int len = utfpos(e->str + cur, 1); int len = utfpos(e->s + cur, 1);
if (size - cur > 0) { if (size - cur > 0) {
memmove(e->str + cur, e->str + cur + len, size - cur); memmove(e->s + cur, e->s + cur + len, size - cur);
e->length -= 1; e->length -= 1;
} }
} }
static bool edit_event(struct edit* e, struct rect r) { /// @return key id or 0
static int edit_event(struct edit* e, struct rect r) {
if (is_click_over(r)) { if (is_click_over(r)) {
// take focus // take focus
tim.focus = (uintptr_t)e; tim.focus = e;
return false; return 0;
} }
if (tim.focus != (uintptr_t)e || tim.event.type != KEY_EVENT) { if (tim.focus != e || tim.event.type != KEY_EVENT) {
// not focused or no key press // not focused or no key press
return false; return 0;
} }
tim.event.type = VOID_EVENT; // consume event tim.event.type = VOID_EVENT; // consume event
switch (tim.event.key) { switch (tim.event.key) {
case ESCAPE_KEY:
case ENTER_KEY: case ENTER_KEY:
tim.focus = 0; // release focus tim.focus = 0; // release focus
return true; break;
case DELETE_KEY: case DELETE_KEY:
edit_delete(e); edit_delete(e);
break; break;
@@ -1174,40 +1185,42 @@ static bool edit_event(struct edit* e, struct rect r) {
case END_KEY: case END_KEY:
e->cursor = e->length; e->cursor = e->length;
break; break;
case ESCAPE_KEY:
tim.focus = 0; // release focus
break;
default: default:
if (tim.event.key >= ' ') { if (tim.event.key >= ' ') {
edit_insert(e, tim.event.str); edit_insert(e, tim.event.s);
} }
break; break;
} }
return false; return tim.event.key;
} }
// text edit - value in state static inline void edit_init(struct edit* e, int capacity, const char* initial_content){
// e : persistent edit state e->length = utflen(initial_content);
// color: frame, background, text e->cursor = utflen(initial_content);
static inline bool edit(struct edit* e, int x, int y, int w, uint64_t color) { e->capacity = capacity;
e->s = malloc(capacity + 1);
int byte_len = strlen(initial_content);
memcpy(e->s, initial_content, byte_len);
e->s[byte_len] = 0;
}
/// text edit - value in state
/// @param e persistent edit state, use edit_init() to create new state
/// @param color frame, background, text
/// @return key id or 0
static inline int edit(struct edit* e, int x, int y, int w, uint64_t color) {
struct rect r = abs_xywh(x, y, w, 3); struct rect r = abs_xywh(x, y, w, 3);
// uninitialized edit state
if (e->str[0] && e->cursor == 0 && e->length == 0) {
e->length = utflen(e->str);
e->cursor = e->length;
}
if (tim.event.type == DRAW_EVENT) { if (tim.event.type == DRAW_EVENT) {
draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8);
if (tim.focus == (uintptr_t)e) { if (tim.focus == e) {
char* str = e->str + utfpos(e->str, e->cursor - r.w + 4); char* s = e->s + utfpos(e->s, e->cursor - r.w + 4);
int cur = MIN(r.w - 4, e->cursor); int cur = MIN(r.w - 4, e->cursor);
draw_str(str, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); draw_str(s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8);
draw_invert(r.x + cur + 2, r.y + 1, 1); draw_invert(r.x + cur + 2, r.y + 1, 1);
} else { } else {
draw_str(e->str, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); draw_str(e->s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8);
} }
} }