Renamed every 'str' fiend and variable to 's'. Implemented edit_init.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
135
tim.h
@@ -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) {
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user