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
Demo video: https://asciinema.org/a/zn3p0dsVCOQOzwY1S9gDfyaxQ
* quick start ****************************************************************
## quick start
#include "tim.h" // one header, no lib
int main(void) { //
@@ -20,7 +20,7 @@ int main(void) { //
} // atexit cleanup
} //
* layout *********************************************************************
## layout
The terminal's columns (x) and rows (y) are addressed by their coordinates,
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.
* colors *********************************************************************
## 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.
@@ -71,7 +71,7 @@ should be used if consistency is important.
xterm-256 color chart
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
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
VOID_EVENT | consumed event
* elements *******************************************************************
## elements
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
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
focused by mouse click. Escape or return relinquish focus. Returns true
when return is pressed.
Draw text edit. Output is stored in state.s.
Receives input events when focused by mouse click or by setting focus manually.
Escape or return relinquish focus.
Returns key id if received key input.
state pointer to persistent edit state struct
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
color radio, background, text
* functions ******************************************************************
## functions
tim_run (fps) -> bool
@@ -177,12 +178,12 @@ time_us () -> int64
Returns monotonic clock value in microseconds. Not affected by summer
time or leap seconds.
* useful links ***************************************************************
## useful links
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
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 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
- Windows cmd.exe resize events may be delayed
* compatibility **************************************************************
## compatibility
emulator | support | remarks
------------------|---------|----------------------------------
@@ -222,7 +223,7 @@ https://learn.microsoft.com/en-us/windows/console/
XTerm | full | XTerm is law
Zutty | full |
* license ********************************************************************
## license
MIT License

View File

@@ -51,7 +51,7 @@ int main(void) {
TEST(scan_str("a").width == 1);
TEST(scan_str("äß\no").width == 2);
struct line ln = {.str = "foo\nbar"};
struct line ln = {.s = "foo\nbar"};
TEST(next_line(&ln) == true);
TEST(!memcmp(ln.line, "foo", ln.size));
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);
sprintf(buf, "frame : [%c] %d", ": "[tim.frame & 1], tim.frame);
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);
sprintf(buf, "mouse : [%d] %d:%d", me.key, me.x, me.y);
label(buf, 2, 3, A, A, 0xf);
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);
// lower right
@@ -63,13 +63,13 @@ static inline void test_screen(struct event* e) {
}
// edit
static struct edit ed1 = {.str = "Edit 1"};
static struct edit ed1 = {.s = "Edit 1"};
static struct edit ed2 = {0};
edit(&ed1, 1, 10, 32, 0xff00ff);
sprintf(buf, "cursor: %d length: %d", ed1.cursor, ed1.length);
label(buf, 2, 13, A, A, 0xf);
edit(&ed2, 1, 14, 32, 0xff00ff);
label(ed2.str, 2, 17, A, A, 0xf);
label(ed2.s, 2, 17, A, A, 0xf);
// checkbox
static int chk[2] = {-1, 1};

135
tim.h
View File

@@ -117,9 +117,10 @@
//
// edit (state, x, y, w, color) -> bool
//
// Draw text edit. Output is stored in state.str. Receives input events when
// focused by mouse click. Escape or return relinquish focus. Returns true
// when return is pressed.
// Draw text edit. Output is stored in state.s.
// Receives input events when focused by mouse click or by setting focus manually.
// Escape or return relinquish focus.
// Returns key id if received key input.
//
// state pointer to persistent edit state struct
// x/y/w see layout documentation
@@ -357,7 +358,7 @@ struct text {
};
struct line {
const char* str; // input and parse state
const char* s; // input and parse state
const char* line; // line strings, not terminated
int size; // line size in bytes
int width; // line width in glyph
@@ -368,13 +369,14 @@ struct event {
int32_t key; // used by KEY_EVENT and MOUSE_EVENT
int x; // 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 {
int cursor; // cursor position (utf8)
int length; // string length (utf8)
char str[256]; // zero terminated buffer
int capacity; // buffer size
char* s; // zero terminated buffer
};
struct state {
@@ -382,7 +384,7 @@ struct state {
int h; // screen height
int frame; // frame counter
struct event event; // current event
uintptr_t focus; // focused element
void* focus; // focused element
int loop_stage; // loop stage
bool resized; // screen was resized
int scope; // current scope
@@ -436,8 +438,12 @@ struct state tim = {
// like strlen, returns 0 on NULL or int overflow
static inline int ztrlen(const char* s) {
size_t n = s ? strlen(s) : 0;
return MAX((int)n, 0);
if(s == NULL)
return 0;
int n = strlen(s);
if(n < 0)
n = 0;
return n;
}
// bit scan reverse, count leading zeros
@@ -494,8 +500,9 @@ static int utfpos(const char* s, int pos) {
}
// scan string for width and lines
static struct text scan_str(const char* str) {
const char* s = str ? str : "";
static struct text scan_str(const char* s) {
if(s == NULL)
s = "";
struct text t = {
.width = 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
static bool next_line(struct line* l) {
if (!l->str || !l->str[0]) {
if (!l->s || !l->s[0]) {
return false;
}
l->line = l->str;
l->line = l->s;
l->size = 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->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;
}
@@ -563,7 +570,8 @@ static void signal_handler(int signal) {
static void update_screen_size(void) {
struct winsize 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 h = ws.ws_row;
@@ -601,9 +609,9 @@ static void reset_terminal(void) {
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) {
char* s = e->str;
char* s = e->s;
if (n == 1 || s[0] != 27) {
// regular key press
@@ -694,7 +702,7 @@ static void read_event(int timeout_ms) {
if (pfd[1].revents & POLLIN) {
// 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)) {
return;
}
@@ -727,7 +735,8 @@ static void update_screen_size(void) {
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO 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 h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
@@ -839,7 +848,7 @@ static void read_event(int timeout_ms) {
return;
}
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);
return;
}
@@ -1076,16 +1085,16 @@ static inline void frame(int x, int y, int w, int h, uint64_t color) {
// text label
// str : text - supports multiple lines
// 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) {
if (tim.event.type == DRAW_EVENT) {
struct text s = scan_str(str);
w = (w == A) ? s.width : w;
h = (h == A) ? s.lines : h;
struct text t = scan_str(s);
w = (w == A) ? t.width : w;
h = (h == A) ? t.lines : h;
struct rect r = abs_xywh(x, y, w, h);
struct cell c = cell(" ", color, color >> 8);
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++) {
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 ***********************************************************************/
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);
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 cur = utfpos(e->str, e->cursor);
memmove(e->str + cur + src_size, e->str + cur, dst_size - cur);
memmove(e->str + cur, s, src_size);
e->str[dst_size + src_size + 1] = 0;
int cur = utfpos(e->s, e->cursor);
memmove(e->s + cur + src_size, e->s + cur, dst_size - cur);
memmove(e->s + cur, s, src_size);
e->s[dst_size + src_size] = 0;
e->length += len;
e->cursor += len;
}
}
static void edit_delete(struct edit* e) {
int size = ztrlen(e->str);
int cur = utfpos(e->str, e->cursor);
int len = utfpos(e->str + cur, 1);
int size = ztrlen(e->s);
int cur = utfpos(e->s, e->cursor);
int len = utfpos(e->s + cur, 1);
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;
}
}
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)) {
// take focus
tim.focus = (uintptr_t)e;
return false;
tim.focus = e;
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
return false;
return 0;
}
tim.event.type = VOID_EVENT; // consume event
switch (tim.event.key) {
case ESCAPE_KEY:
case ENTER_KEY:
tim.focus = 0; // release focus
return true;
break;
case DELETE_KEY:
edit_delete(e);
break;
@@ -1174,40 +1185,42 @@ static bool edit_event(struct edit* e, struct rect r) {
case END_KEY:
e->cursor = e->length;
break;
case ESCAPE_KEY:
tim.focus = 0; // release focus
break;
default:
if (tim.event.key >= ' ') {
edit_insert(e, tim.event.str);
edit_insert(e, tim.event.s);
}
break;
}
return false;
return tim.event.key;
}
// text edit - value in state
// e : persistent edit state
// color: frame, background, text
static inline bool edit(struct edit* e, int x, int y, int w, uint64_t color) {
struct rect r = abs_xywh(x, y, w, 3);
static inline void edit_init(struct edit* e, int capacity, const char* initial_content){
e->length = utflen(initial_content);
e->cursor = utflen(initial_content);
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;
}
// uninitialized edit state
if (e->str[0] && e->cursor == 0 && e->length == 0) {
e->length = utflen(e->str);
e->cursor = e->length;
}
/// 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);
if (tim.event.type == DRAW_EVENT) {
draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8);
if (tim.focus == (uintptr_t)e) {
char* str = e->str + utfpos(e->str, e->cursor - r.w + 4);
if (tim.focus == e) {
char* s = e->s + utfpos(e->s, e->cursor - r.w + 4);
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);
} 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);
}
}