squash
This commit is contained in:
50
example/ask.c
Normal file
50
example/ask.c
Normal file
@@ -0,0 +1,50 @@
|
||||
// Display a yes/no dialog with a message. Returns with 0 when yes was clicked.
|
||||
// syntax: ./ask "message"
|
||||
|
||||
#include "../tim.h"
|
||||
|
||||
// colors
|
||||
#define CTXT 0xf // text black, white
|
||||
#define CFR 0x8 // frame black, gray
|
||||
#define CYES 0xa000f // yes green, black, white
|
||||
#define CNO 0x9000f // no red, black white
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2 || strcmp(argv[1], "-h") == 0) {
|
||||
printf("syntax: %s message\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// get text properties
|
||||
struct text msg = scan_str(argv[1]);
|
||||
|
||||
while (tim_run(0)) {
|
||||
// calculate size of message box
|
||||
int w = MAX(msg.width + 4, 24);
|
||||
int h = MAX(msg.lines + 6, 7);
|
||||
|
||||
scope (A, A, w, h) {
|
||||
// draw frame around entire scope
|
||||
frame(0, 0, ~0, ~0, CFR);
|
||||
|
||||
// draw message
|
||||
label(argv[1], A, 1, msg.width, msg.lines, CTXT);
|
||||
|
||||
// draw 'yes' button, return 0 when clicked
|
||||
if (button("Yes", 2, ~1, A, A, CYES)) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// draw 'no' button, return 1 when clicked
|
||||
if (button("No ", ~2, ~1, A, A, CNO)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// return with 1 when q or esc is pressed
|
||||
if (is_key_press('q') || is_key_press(ESCAPE_KEY)) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
example/hello.c
Normal file
14
example/hello.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "../tim.h" // one header, no lib
|
||||
int main(void) { //
|
||||
while (tim_run(0)) { // event loop
|
||||
scope (A, A, 24, 8) { // centered scope
|
||||
uint64_t c = 0x0a060f; // three colors
|
||||
frame(0, 0, ~0, ~0, c); // draw frame for scope
|
||||
label("Greetings!", A, 2, A, A, c); // label in top center
|
||||
if (button("OK", A, ~1, 8, A, c)) // button in bottom center
|
||||
return 0; // exit on button click
|
||||
if (is_key_press('q')) //
|
||||
return 0; // exit on 'q' press
|
||||
} //
|
||||
} //
|
||||
} // automatic cleanup
|
||||
130
example/snek.c
Normal file
130
example/snek.c
Normal file
@@ -0,0 +1,130 @@
|
||||
// Simple game of snake to show how to do animation and draw cells.
|
||||
|
||||
#include "../tim.h"
|
||||
|
||||
#define FG 0x10
|
||||
#define BG 0xdd
|
||||
#define BTN (FG << 16 | BG << 8 | FG)
|
||||
|
||||
#define NEW 0
|
||||
#define RUN 1
|
||||
#define PAUSE 2
|
||||
#define OVER 3
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
};
|
||||
int64_t xy;
|
||||
} point;
|
||||
|
||||
static struct {
|
||||
int state; // game state (NEW RUN PAUSE OVER)
|
||||
int64_t tick; // updates every 10 ms
|
||||
int len; // snake length
|
||||
point body[200]; // snake body
|
||||
point food; // food position
|
||||
point look; // active direction
|
||||
} snek;
|
||||
|
||||
static void start(void) {
|
||||
memset(snek.body, -1, sizeof(snek.body));
|
||||
snek.len = 2;
|
||||
snek.body[0] = (point){{1, tim.h / 2}};
|
||||
snek.food = (point){{tim.w / 8, tim.h / 2}};
|
||||
snek.look = (point){{1, 0}};
|
||||
}
|
||||
|
||||
static void game(void) {
|
||||
// update game state about every 10 ms
|
||||
int64_t tick = time_us() / 100000;
|
||||
if (snek.tick != tick) {
|
||||
snek.tick = tick;
|
||||
// move one unit
|
||||
memmove(snek.body + 1, snek.body, sizeof(snek.body) - sizeof(point));
|
||||
snek.body[0].x = snek.body[1].x + snek.look.x;
|
||||
snek.body[0].y = snek.body[1].y + snek.look.y;
|
||||
// self crash
|
||||
bool crash = false;
|
||||
for (int i = 1; i < snek.len; i++) {
|
||||
crash |= snek.body[0].xy == snek.body[i].xy;
|
||||
}
|
||||
// border crash
|
||||
crash |= snek.body[0].x < 0 || snek.body[0].x >= tim.w / 2 ||
|
||||
snek.body[0].y < 0 || snek.body[0].y >= tim.h;
|
||||
snek.state = crash ? OVER : snek.state;
|
||||
// food
|
||||
if (snek.food.xy == snek.body[0].xy) {
|
||||
snek.len = MIN(snek.len + 2, ARRAY_SIZE(snek.body));
|
||||
snek.food.x = rand() % (tim.w / 2 - 2) + 1;
|
||||
snek.food.y = rand() % (tim.h - 2) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// draw
|
||||
if (tim.event.type == DRAW_EVENT) {
|
||||
// food
|
||||
draw_chr(cell(" ", 0, 0xc5), snek.food.x * 2 + 0, snek.food.y);
|
||||
draw_chr(cell(" ", 0, 0xc5), snek.food.x * 2 + 1, snek.food.y);
|
||||
// snek
|
||||
struct cell s = cell(" ", 0, 0);
|
||||
for (int i = 0; i < snek.len; i++) {
|
||||
s.bg = (i / 2) % 2 ? 0xe3 : 0xea;
|
||||
int x = snek.body[i].x * 2;
|
||||
int y = snek.body[i].y;
|
||||
draw_chr(s, x + 0, y);
|
||||
draw_chr(s, x + 1, y);
|
||||
}
|
||||
}
|
||||
|
||||
// user input
|
||||
if (tim.event.type == KEY_EVENT) {
|
||||
int key = tim.event.key;
|
||||
if ((key == RIGHT_KEY || key == 'd') && snek.look.x != -1) {
|
||||
snek.look = (point){{1, 0}};
|
||||
} else if ((key == LEFT_KEY || key == 'a') && snek.look.x != 1) {
|
||||
snek.look = (point){{-1, 0}};
|
||||
} else if ((key == DOWN_KEY || key == 's') && snek.look.y != -1) {
|
||||
snek.look = (point){{0, 1}};
|
||||
} else if ((key == UP_KEY || key == 'w') && snek.look.y != 1) {
|
||||
snek.look = (point){{0, -1}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void menu(void) {
|
||||
scope(A, A, 20, 13) {
|
||||
char* lbl = snek.state == OVER ? "GAME OVER" : "SNEK - THE GAME";
|
||||
char* btn = snek.state == PAUSE ? "Resume" : "Play";
|
||||
label(lbl, A, 0, A, A, BTN);
|
||||
if (button(btn, A, 2, 20, 5, BTN) || is_key_press(ENTER_KEY)) {
|
||||
if (snek.state != PAUSE) {
|
||||
start();
|
||||
}
|
||||
snek.state = RUN;
|
||||
}
|
||||
if (button("Exit", A, 8, 20, 5, BTN) || is_key_press(ESCAPE_KEY)) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// draw every 10 ms
|
||||
while (tim_run(10)) {
|
||||
struct cell bg = cell(" ", 0, BG);
|
||||
draw_lot(bg, 0, 0, tim.w, tim.h);
|
||||
|
||||
if (snek.state == RUN) {
|
||||
game();
|
||||
} else {
|
||||
menu();
|
||||
}
|
||||
|
||||
if (is_key_press(ESCAPE_KEY)) {
|
||||
snek.state = PAUSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
188
readme
Normal file
188
readme
Normal file
@@ -0,0 +1,188 @@
|
||||
* about **********************************************************************
|
||||
|
||||
tim.h is an immediate mode toolkit for creating simple terminal guis
|
||||
|
||||
* quick start ****************************************************************
|
||||
|
||||
#include "tim.h" // one header, no lib
|
||||
int main(void) { //
|
||||
while (tim_run(0)) { // event loop
|
||||
scope (A, A, 24, 8) { // centered 28x8 scope
|
||||
uint64_t c = 0x0a060f; // three colors
|
||||
frame(0, 0, ~0, ~0, c); // draw frame for scope
|
||||
label("Greetings!", A, 2, A, A, c); // label in top center
|
||||
if (button("OK", A, ~1, 8, A, c)) // button in bottom center
|
||||
return 0; // exit on button click
|
||||
if (is_key_press('q')) // ctrl-c is masked
|
||||
return 0; // exit on 'q' press
|
||||
} //
|
||||
} //
|
||||
} // automatic cleanup
|
||||
|
||||
* layout *********************************************************************
|
||||
|
||||
The terminal's columns (x) and rows (y) are addressed by their coordinates,
|
||||
the origin is in the top left corner.
|
||||
|
||||
Scopes are the primary layout mechanism. They are used to group and place
|
||||
multiple elements. Scopes can be nested.
|
||||
The root scope is the full terminal screen. The scope macro is constructed
|
||||
with a for loop, so statements like break or return inside the scope block
|
||||
will probably give you a bad time.
|
||||
|
||||
Most elements take x/y/w/h arguments to control placement. All positions are
|
||||
given in relation the element's parent scope.
|
||||
|
||||
Automatic (A) width and height are either based on the element's content, or
|
||||
take the full available space from parent.
|
||||
|
||||
arg | value | placement
|
||||
-----|-------|---------------------------------
|
||||
x | n | n columns to left
|
||||
x | ~n | n columns to right
|
||||
x | A | center horizontally
|
||||
y | n | n rows to top
|
||||
y | ~n | n rows to bottom
|
||||
y | A | center vertically
|
||||
w | n | n columns wide
|
||||
w | ~n | fit width to n columns to right
|
||||
w | A | automatic width
|
||||
h | n | n rows high
|
||||
h | ~n | fit height n rows to bottom
|
||||
h | A | automatic height
|
||||
|
||||
* 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.
|
||||
|
||||
For example 0x08040f encodes three colors. When used with a button the text
|
||||
is white (0f), the background is blue (04), and the frame is gray (08).
|
||||
|
||||
The terminal should support xterm-256 colors. The TERM variable is ignored.
|
||||
The lower 16 colors vary across different terminals, so the upper 240 colors
|
||||
should be used if consistency is important.
|
||||
|
||||
xterm-256 color chart
|
||||
https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
|
||||
|
||||
* 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.
|
||||
The event is stored in tim.event.
|
||||
Some elements need to consume events, for example edit consumes the key
|
||||
event when focused in order to prevent other key handlers on acting on them.
|
||||
|
||||
event | cause
|
||||
-------------|-----------------------
|
||||
DRAW_EVENT | input, timeout, resize
|
||||
KEY_EVENT | key press
|
||||
MOUSE_EVENT | mouse click
|
||||
VOID_EVENT | consumed event
|
||||
|
||||
* elements *******************************************************************
|
||||
|
||||
frame (x, y, w, h, color)
|
||||
|
||||
Draw an empty frame and fill area.
|
||||
|
||||
x/y/w/h see layout documentation
|
||||
color background, frame
|
||||
|
||||
label (str, x, y, w, h, color)
|
||||
|
||||
Draw text label. Automatic width and height are supported. Strings
|
||||
exceeding width or height are clipped.
|
||||
|
||||
str zero terminated string
|
||||
x/y/w/h see layout documentation
|
||||
color background, text
|
||||
|
||||
button (str, x, y, w, h, color) -> bool
|
||||
|
||||
Draw button. Automatic width and height are supported. Strings exceeding
|
||||
width or height are clipped. Returns true when clicked.
|
||||
|
||||
str zero terminated string
|
||||
x/y/w/h see layout documentation
|
||||
color frame, background, text
|
||||
|
||||
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.
|
||||
|
||||
state pointer to persistent edit state struct
|
||||
x/y/w see layout documentation
|
||||
color f rame, background, text
|
||||
|
||||
check (str, state, x, y, w, color) -> bool
|
||||
|
||||
Draw check box. State determines how the box is checked. [x] when state
|
||||
is non-zero, [ ] when state is zero, [-] when state is -1. A mouse click
|
||||
toggles the state between one and zero and returns true.
|
||||
|
||||
str zero terminated string
|
||||
state pointer to persistent state variable
|
||||
x/y/w see layout documentation
|
||||
color check, background, text
|
||||
|
||||
radio (str, state, v, x, y, w, color) -> bool
|
||||
|
||||
Draw radio box. If state equals v, the box is selected. Radios are
|
||||
grouped through a shared state. Within that group, each v must be unique.
|
||||
A mouse click assigns v to state and returns true.
|
||||
|
||||
str zero terminated string
|
||||
state pointer to persistent state variable
|
||||
v unique state value
|
||||
x/y/w see layout documentation
|
||||
color radio, background, text
|
||||
|
||||
* functions ******************************************************************
|
||||
|
||||
tim_run (fps) -> bool
|
||||
|
||||
Process events and render frame. Blocks until input is received or the
|
||||
next frame is due. First call also initializes the terminal. When fps is
|
||||
zero the function blocks until input is received. Key and mouse events
|
||||
are immediately followed by a draw event, so the actual fps can be
|
||||
significantly greater than requested. Always returns true. To reset the
|
||||
terminal after a crash, run "reset".
|
||||
The Ctrl-C interrupt is masked, so make sure to put an exit condition
|
||||
like this at the end of the main loop:
|
||||
|
||||
if (is_key_press(ESCAPE_KEY))
|
||||
exit(0);
|
||||
|
||||
fps frames per second
|
||||
|
||||
is_key_press (key) -> bool
|
||||
|
||||
Returns true if key was pressed.
|
||||
|
||||
key char literal or one of the KEY constants, see constants
|
||||
|
||||
time_us () -> int64
|
||||
|
||||
Returns monotonic clock value in microseconds. Not affected by summer
|
||||
time or leap seconds.
|
||||
|
||||
* useful links ***************************************************************
|
||||
|
||||
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
https://learn.microsoft.com/en-us/windows/console/
|
||||
|
||||
* 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
|
||||
handle these consistently and there is no portable way to reliably
|
||||
determine character width. The renderer can deal with some of the problems
|
||||
caused by this, but results may vary.
|
||||
- Decomposed (NFD) UTF-8 is not supported and will cause havoc
|
||||
- Zero width code points are not supported
|
||||
- Windows cmd.exe resize events may be delayed
|
||||
|
||||
30
test/color.c
Normal file
30
test/color.c
Normal file
@@ -0,0 +1,30 @@
|
||||
// Shows xterm-256 color palette.
|
||||
|
||||
#include "../tim.h"
|
||||
|
||||
static void foo(int x, int y, int c) {
|
||||
char buf[16] = {0};
|
||||
sprintf(buf, " %02x ", c);
|
||||
draw_str(buf, x * 4, y, 4, 0, c);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
while (tim_run(0)) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
foo(i % 8, i / 8, i);
|
||||
}
|
||||
for (int i = 0; i < 108; i++) {
|
||||
foo(i % 6, i / 6 + 3, i + 16);
|
||||
}
|
||||
for (int i = 0; i < 108; i++) {
|
||||
foo(i % 6 + 7, i / 6 + 3, i + 124);
|
||||
}
|
||||
for (int i = 0; i < 24; i++) {
|
||||
foo(i % 12, i / 12 + 22, i + 232);
|
||||
}
|
||||
if (is_key_press('q') || is_key_press(ESCAPE_KEY)) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
70
test/string.c
Normal file
70
test/string.c
Normal file
@@ -0,0 +1,70 @@
|
||||
// Test string functions.
|
||||
|
||||
#include "../tim.h"
|
||||
|
||||
#define U(s) (uint8_t*)(""s), (sizeof(s) - 1)
|
||||
#define TEST(t) printf("\33[3%s\33[0m %s\n", (t) ? "2mpass" : "1mfail", #t)
|
||||
|
||||
int main(void) {
|
||||
(void)tim_run;
|
||||
|
||||
TEST(ztrlen(NULL) == 0);
|
||||
TEST(ztrlen("") == 0);
|
||||
TEST(ztrlen("$") == 1);
|
||||
TEST(ztrlen("£") == 2);
|
||||
TEST(ztrlen("€") == 3);
|
||||
TEST(ztrlen("𐍈") == 4);
|
||||
|
||||
TEST(bsr8(128) == 0);
|
||||
TEST(bsr8(64) == 1);
|
||||
TEST(bsr8(1) == 7);
|
||||
TEST(bsr8(0) == 8);
|
||||
|
||||
TEST(utfchr(NULL) == 0);
|
||||
TEST(utfchr("") == 0);
|
||||
TEST(utfchr("$") == 0x24);
|
||||
TEST(utfchr("£") == 0xA3);
|
||||
TEST(utfchr("И") == 0x418);
|
||||
TEST(utfchr("ह") == 0x939);
|
||||
TEST(utfchr("€") == 0x20AC);
|
||||
TEST(utfchr("한") == 0xD55C);
|
||||
TEST(utfchr("𐍈") == 0x10348);
|
||||
|
||||
TEST(utflen(NULL) == 0);
|
||||
TEST(utflen("") == 0);
|
||||
TEST(utflen("$") == 1);
|
||||
TEST(utflen("$$") == 2);
|
||||
TEST(utflen("$£") == 2);
|
||||
TEST(utflen("$€𐍈") == 3);
|
||||
|
||||
TEST(utfpos(NULL, 0) == 0);
|
||||
TEST(utfpos("äbc", 0) == 0);
|
||||
TEST(utfpos("äbc", 1) == 2);
|
||||
TEST(utfpos("äbc", 2) == 3);
|
||||
TEST(utfpos("äbc", 9) == 4);
|
||||
|
||||
TEST(scan_str(NULL).lines == 0);
|
||||
TEST(scan_str("").lines == 0);
|
||||
TEST(scan_str("abc").lines == 1);
|
||||
TEST(scan_str("a\no").lines == 2);
|
||||
TEST(scan_str("a").width == 1);
|
||||
TEST(scan_str("äß\no").width == 2);
|
||||
|
||||
struct line ln = {.str = "foo\nbar"};
|
||||
TEST(next_line(&ln) == true);
|
||||
TEST(!memcmp(ln.line, "foo", ln.size));
|
||||
TEST(next_line(&ln) == true);
|
||||
TEST(!memcmp(ln.line, "bar", ln.size));
|
||||
TEST(next_line(&ln) == false);
|
||||
|
||||
TEST(is_wide_perhaps(NULL, 0) == false);
|
||||
TEST(is_wide_perhaps(U("")) == false);
|
||||
TEST(is_wide_perhaps(U("$")) == false);
|
||||
TEST(is_wide_perhaps(U("£")) == false);
|
||||
TEST(is_wide_perhaps(U("ह")) == false);
|
||||
TEST(is_wide_perhaps(U("€")) == true);
|
||||
TEST(is_wide_perhaps(U("┌")) == false);
|
||||
TEST(is_wide_perhaps(U("한")) == true);
|
||||
TEST(is_wide_perhaps(U("𐍈")) == true);
|
||||
}
|
||||
|
||||
119
test/test.c
Normal file
119
test/test.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "../tim.h"
|
||||
|
||||
static inline void test_screen(struct event* e) {
|
||||
static struct event me;
|
||||
static struct event ke;
|
||||
static int render_us;
|
||||
char buf[64];
|
||||
|
||||
ke = (e->type == KEY_EVENT) ? *e : ke;
|
||||
me = (e->type == MOUSE_EVENT) ? *e : me;
|
||||
|
||||
// positioning
|
||||
label("+", 0, 0, A, A, 0xf);
|
||||
label("+", ~0, 0, A, A, 0xf);
|
||||
label("+", 0, ~0, A, A, 0xf);
|
||||
label("+", ~0, ~0, A, A, 0xf);
|
||||
label("+", A, A, A, A, 0xf);
|
||||
label("-", 0, A, A, A, 0xf);
|
||||
label("-", ~0, A, A, A, 0xf);
|
||||
label("|", A, 0, A, A, 0xf);
|
||||
label("|", A, ~0, A, A, 0xf);
|
||||
|
||||
// some information
|
||||
sprintf(buf, "screen: %dx%d", tim.w, tim.h);
|
||||
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));
|
||||
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]);
|
||||
label(buf, 2, 4, A, A, 0xf);
|
||||
|
||||
// lower right
|
||||
render_us += tim.render_us;
|
||||
sprintf(buf, "%d µs (Ø %d µs)", tim.render_us, render_us / MAX(tim.frame, 1));
|
||||
label(buf, ~2, ~2, A, A, 0xf);
|
||||
sprintf(buf, "%d cells (%.0f %%)", tim.w * tim.h, 100.0 * tim.w * tim.h / MAX_CELLS);
|
||||
label(buf, ~2, ~1, A, A, 0xf);
|
||||
sprintf(buf, "%d bytes (%.0f %%)", tim.buf_size, 100.0 * tim.buf_size / MAX_BUF);
|
||||
label(buf, ~2, ~0, A, A, 0xf);
|
||||
|
||||
// multi line label
|
||||
label("multi\nliñe\nlabël", 24, 1, A, A, 0xf);
|
||||
|
||||
// colors
|
||||
scope (1, 5, 16, 5) {
|
||||
frame(0, 0, ~0, ~0, 0xf);
|
||||
label(" Red ", 1, 1, 7, A, 0x0900);
|
||||
label(" ", 8, 1, 7, A, 0xc400);
|
||||
label(" Green ", 1, 2, 7, A, 0x0a00);
|
||||
label(" ", 8, 2, 7, A, 0x2e00);
|
||||
label(" Blue ", 1, 3, 7, A, 0x0c00);
|
||||
label(" ", 8, 3, 7, A, 0x1500);
|
||||
}
|
||||
|
||||
// button
|
||||
static uint64_t bc = 0x100;
|
||||
if (button("Click Me", 17, 5, 16, 5, bc)) {
|
||||
bc = (bc + 0x100) & 0xff00;
|
||||
}
|
||||
|
||||
// edit
|
||||
static struct edit ed1 = {.str = "Edit 1"};
|
||||
static struct edit ed2 = {};
|
||||
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);
|
||||
|
||||
// checkbox
|
||||
static int chk[2] = {-1, 1};
|
||||
check("Check 1", &chk[0], 1, 18, A, 0xa000f);
|
||||
check("Check 2", &chk[1], 14, 18, A, 0xa000f);
|
||||
|
||||
// radiobox
|
||||
static int rad = 0;
|
||||
radio("Radio 1", &rad, 1, 1, 19, A, 0xa000f);
|
||||
radio("Radio 2", &rad, 2, 14, 19, A, 0xa000f);
|
||||
radio("Radio 3", &rad, 3, 1, 20, A, 0xa000f);
|
||||
radio("Radio 4", &rad, 4, 14, 20, A, 0xa000f);
|
||||
|
||||
// scope nesting
|
||||
scope(~1, 1, 20, 10) {
|
||||
scope(0, 0, 10, 5) {
|
||||
frame(0, 0, ~0, ~0, 0x9);
|
||||
}
|
||||
scope(~0, 0, 10, 5) {
|
||||
frame(0, 0, ~0, ~0, 0xa);
|
||||
}
|
||||
scope(~0, ~0, 10, 5) {
|
||||
frame(0, 0, ~0, ~0, 0xb);
|
||||
}
|
||||
scope(0, ~0, 10, 5) {
|
||||
frame(0, 0, ~0, ~0, 0xc);
|
||||
}
|
||||
}
|
||||
|
||||
// funny characters
|
||||
scope (~1, ~3, 11, 5) {
|
||||
frame(0, 0, ~0, ~0, 0xf);
|
||||
label("123456789", 1, 1, 9, A, 0x0f05);
|
||||
label("$£ह€𐍈6789", 1, 2, A, A, 0x0f05);
|
||||
label("圍棋56789", 1, 3, A, A, 0x0f05);
|
||||
}
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
while (tim_run(1.5)) {
|
||||
test_screen(&tim.event);
|
||||
if (is_key_press('q') || is_key_press(ESCAPE_KEY)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
test/width.c
Normal file
69
test/width.c
Normal file
@@ -0,0 +1,69 @@
|
||||
// Test character width.
|
||||
|
||||
#include <assert.h>
|
||||
#include "../tim.h"
|
||||
|
||||
static int cp_to_utf8(int32_t cp, char* s) {
|
||||
assert(cp > 0 && cp < 0x110000);
|
||||
|
||||
if (cp < 0x80) {
|
||||
s[0] = cp;
|
||||
return 1;
|
||||
} else if (cp < 0x800) {
|
||||
s[0] = (cp >> 6) | 0xc0;
|
||||
s[1] = (cp & 0x3f) | 0x80;
|
||||
return 2;
|
||||
} else if (cp < 0x10000) {
|
||||
s[0] = (cp >> 12) | 0xe0;
|
||||
s[1] = ((cp >> 6) & 0x3f) | 0x80;
|
||||
s[2] = (cp & 0x3f) | 0x80;
|
||||
return 3;
|
||||
} else {
|
||||
s[0] = (cp >> 18) | 0xf0;
|
||||
s[1] = ((cp >> 12) & 0x3f) | 0x80;
|
||||
s[2] = ((cp >> 6) & 0x3f) | 0x80;
|
||||
s[3] = (cp & 0x3f) | 0x80;
|
||||
return 4;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cursor_pos() {
|
||||
write(STDOUT_FILENO, S("\33[6n"));
|
||||
char buf[64] = {0};
|
||||
int n = read(STDIN_FILENO, buf, 64);
|
||||
if (n < 6 || buf[0] != '\33' || buf[n - 1] != 'R') {
|
||||
return -1;
|
||||
}
|
||||
int r = atoi(buf + 2);
|
||||
int c = atoi(buf + 4 + (r > 9));
|
||||
return c;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
assert(argc == 2);
|
||||
(void)tim_run;
|
||||
|
||||
FILE* f = fopen(argv[1], "w");
|
||||
assert(f);
|
||||
|
||||
init_terminal();
|
||||
|
||||
for (int i = 32; i < 0x110000; i++) {
|
||||
write(STDOUT_FILENO, S("\33[0;0H"));
|
||||
char buf[5] = {0};
|
||||
int n = cp_to_utf8(i, buf);
|
||||
write(STDOUT_FILENO, buf, n);
|
||||
int w = cursor_pos() - 1;
|
||||
if (w) {
|
||||
fprintf(f, "u+%06x %d %s\n", i, w, buf);
|
||||
} else {
|
||||
fprintf(f, "u+%06x %d\n", i, w);
|
||||
}
|
||||
}
|
||||
|
||||
reset_terminal();
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
Reference in New Issue
Block a user