Compare commits

18 Commits

Author SHA1 Message Date
ee6375f553 finished scroll_view 2026-01-16 19:34:06 +05:00
cbd1cc0cf1 two separate controls - panel and scroll_view 2026-01-16 17:45:34 +05:00
f4ed55a495 draw only inside current scope 2026-01-16 16:32:19 +05:00
f650e568d6 added scroll bar to tim_scroll 2026-01-14 22:11:18 +05:00
2a685dfcd0 added note about "ssty sane" 2026-01-13 18:41:22 +05:00
f8af7480d3 fixed uninitialized buffers 2026-01-13 18:31:38 +05:00
b2c4a90bea fixed tim_reset_terminal in unix.c 2026-01-13 18:19:41 +05:00
6d0190c9c0 comment about colors 2026-01-13 12:43:13 +05:00
58276638a7 changed .vscode/launch.json 2026-01-13 11:17:14 +05:00
3fb220ff54 tim_draw_lot -> tim_fill 2026-01-12 22:57:31 +05:00
7a3bde6321 removed malloc from TimEditState_construct 2026-01-12 22:51:33 +05:00
c5328cb9ed added scroll list 2026-01-12 22:02:50 +05:00
717c049265 added mouse wheel support 2026-01-12 21:54:15 +05:00
75d894b1bd tim_button_noborder 2026-01-12 18:34:13 +05:00
1aa33128e9 tim_event_consume() 2026-01-12 15:51:07 +05:00
6f8f2a54c0 added .masked and destructor for EditState 2026-01-09 13:31:08 +05:00
d417b5bbd5 fixed unicode input on windows 2026-01-09 11:12:34 +05:00
1ce7090bd6 replaced confusing u64 joinded colors with struct TimStyle 2026-01-09 09:45:00 +05:00
23 changed files with 761 additions and 254 deletions

41
.vscode/launch.json vendored Executable file
View File

@@ -0,0 +1,41 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) test | Build and debug",
"type": "cppdbg",
"request": "launch",
"cwd": "${workspaceFolder}/bin",
"program": "${workspaceFolder}/bin/tcp-chat",
"windows": {
"program": "${workspaceFolder}/bin/tcp-chat.exe",
"externalConsole": true
},
"preLaunchTask": "build_exec_dbg",
"stopAtEntry": false,
"externalConsole": false,
"internalConsoleOptions": "neverOpen",
"MIMode": "gdb",
"miDebuggerPath": "gdb"
},
{
"name": "(gdb) test | Just debug",
"type": "cppdbg",
"request": "launch",
"cwd": "${workspaceFolder}/bin",
"program": "${workspaceFolder}/bin/tcp-chat",
"windows": {
"program": "${workspaceFolder}/bin/tcp-chat.exe",
"externalConsole": true
},
"preLaunchTask": "build_exec_dbg",
"stopAtEntry": false,
"externalConsole": false,
"internalConsoleOptions": "neverOpen",
"MIMode": "gdb",
"miDebuggerPath": "gdb"
}
]
}

31
.vscode/tasks.json vendored Executable file
View File

@@ -0,0 +1,31 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build_exec_dbg",
"detail": "build project with debug symbols",
"type": "cppbuild",
"command": "bash",
"args": [
"-c",
"cbuild build_static_lib_dbg test"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": ["$gcc"],
"group": {
"kind": "build"
},
"presentation": {
"echo": false,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true
}
}
]
}

BIN
256colors.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -3,12 +3,6 @@
#include "tim.h" #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
i32 main(i32 argc, char** argv) { i32 main(i32 argc, char** argv) {
if (argc < 2 || strcmp(argv[1], "-h") == 0) { if (argc < 2 || strcmp(argv[1], "-h") == 0) {
printf("syntax: %s message\n", argv[0]); printf("syntax: %s message\n", argv[0]);
@@ -18,6 +12,11 @@ i32 main(i32 argc, char** argv) {
// get text properties // get text properties
TimText msg = tim_scan_str(argv[1]); TimText msg = tim_scan_str(argv[1]);
TimStyle style_frame = { .brd = TimColor16_DarkGray };
TimStyle style_text = { .fg = TimColor16_White };
TimStyle style_yes = { .brd = TimColor16_Green, .fg = style_text.fg };
TimStyle style_no = { .brd = TimColor16_Red, .fg = style_yes.fg };
while (tim_run(0)) { while (tim_run(0)) {
// calculate size of message box // calculate size of message box
i32 w = MAX(msg.width + 4, 28); i32 w = MAX(msg.width + 4, 28);
@@ -25,18 +24,18 @@ i32 main(i32 argc, char** argv) {
tim_scope(A, A, w, h) { tim_scope(A, A, w, h) {
// draw frame around entire scope // draw frame around entire scope
tim_frame(0, 0, ~0, ~0, CFR); tim_frame(0, 0, ~0, ~0, style_frame);
// draw message // draw message
tim_label(argv[1], A, 1, msg.width, msg.lines, CTXT); tim_label(argv[1], A, 1, msg.width, msg.lines, style_text);
// draw 'yes' button, return 0 when clicked // draw 'yes' button, return 0 when clicked
if (tim_button("[y] Yes", 2, ~1, A, A, CYES) || tim_is_key_press('y')) { if (tim_button("[y] Yes", 2, ~1, A, A, style_yes) || tim_is_key_press('y')) {
exit(0); exit(0);
} }
// draw 'no' button, return 1 when clicked // draw 'no' button, return 1 when clicked
if (tim_button("[n] No", ~2, ~1, A, A, CNO) || tim_is_key_press('n')) { if (tim_button("[n] No", ~2, ~1, A, A, style_no) || tim_is_key_press('n')) {
exit(1); exit(1);
} }
@@ -46,4 +45,5 @@ i32 main(i32 argc, char** argv) {
} }
} }
} }
return 0;
} }

View File

@@ -3,7 +3,11 @@
i32 main(void) { i32 main(void) {
while (tim_run(0)) { // init state and start event loop while (tim_run(0)) { // init state and start event loop
tim_scope(A, A, 24, 8) { // centered 24x8 scope tim_scope(A, A, 24, 8) { // centered 24x8 scope
u64 c = 0x0a060f; // three colors TimStyle c = { // three colors
.brd= TimColor16_Green,
.bg = TimColor16_DarkCyan,
.fg = TimColor16_White
};
tim_frame(0, 0, ~0, ~0, c); // draw frame for scope tim_frame(0, 0, ~0, ~0, c); // draw frame for scope
tim_label("Greetings!", A, 2, A, A, c); // label in top center tim_label("Greetings!", A, 2, A, A, c); // label in top center
if (tim_button("OK", A, ~1, 8, A, c)) // button in bottom center if (tim_button("OK", A, ~1, 8, A, c)) // button in bottom center
@@ -12,4 +16,5 @@ i32 main(void) {
return 0; // exit on 'q' press return 0; // exit on 'q' press
} }
} // atexit() cleanup } // atexit() cleanup
return 0;
} }

View File

@@ -6,7 +6,6 @@
#define FG 0x10 #define FG 0x10
#define BG 0xdd #define BG 0xdd
#define BTN (FG << 16 | BG << 8 | FG)
#define NEW 0 #define NEW 0
#define RUN 1 #define RUN 1
@@ -81,7 +80,7 @@ static void game(void) {
} }
// user input // user input
if (tim->event.type == KEY_EVENT) { if (tim->event.type == TimEvent_Key) {
i32 key = tim->event.key; i32 key = tim->event.key;
if ((key == TimKey_Right || key == 'd') && snek.look.x != -1) { if ((key == TimKey_Right || key == 'd') && snek.look.x != -1) {
snek.look = (point){{1, 0}}; snek.look = (point){{1, 0}};
@@ -99,17 +98,18 @@ static void game(void) {
} }
static void menu(void) { static void menu(void) {
TimStyle style_button = (TimStyle){ .brd= FG, .bg = BG, .fg = FG };
tim_scope(A, A, 20, 13) { tim_scope(A, A, 20, 13) {
char* lbl = snek.state == OVER ? "GAME OVER" : "SNEK - THE GAME"; char* lbl = snek.state == OVER ? "GAME OVER" : "SNEK - THE GAME";
char* btn = snek.state == PAUSE ? "Resume" : "Play"; char* btn = snek.state == PAUSE ? "Resume" : "Play";
tim_label(lbl, A, 0, A, A, BTN); tim_label(lbl, A, 0, A, A, style_button);
if (tim_button(btn, A, 2, 20, 5, BTN) || tim_is_key_press(TimKey_Enter)) { if (tim_button(btn, A, 2, 20, 5, style_button) || tim_is_key_press(TimKey_Enter)) {
if (snek.state != PAUSE) { if (snek.state != PAUSE) {
start(); start();
} }
snek.state = RUN; snek.state = RUN;
} }
if (tim_button("Exit", A, 8, 20, 5, BTN) || tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { if (tim_button("Exit", A, 8, 20, 5, style_button) || tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) {
exit(0); exit(0);
} }
} }
@@ -119,13 +119,13 @@ i32 main(void) {
// draw every 10 ms // draw every 10 ms
while (tim_run(60)) { while (tim_run(60)) {
TimCell bg = tim_cell(" ", 0, BG); TimCell bg = tim_cell(" ", 0, BG);
tim_draw_lot(bg, 0, 0, tim->w, tim->h); tim_fill(bg, 0, 0, tim->w, tim->h);
if (snek.state == RUN) { if (snek.state == RUN) {
game(); game();
} else { } else {
menu(); menu();
} }
} }
return 0;
} }

View File

@@ -16,10 +16,6 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
// fix windows.h name clash, coincidentally they have the same values
#undef TimEvent_Key // 0x0001
#undef TimEvent_Mouse // 0x0002
#ifdef _MSC_VER #ifdef _MSC_VER
// disable integer conversion warnings // disable integer conversion warnings
#pragma warning(disable:4244) #pragma warning(disable:4244)
@@ -60,42 +56,45 @@ extern "C" {
#pragma region constants #pragma region constants
#define TIM_ENABLE_DBUF 1 // double buffering #define TIM_ENABLE_DBUF 1 // double buffering
#define TIM_MAX_SCOPE 20 // max scope nesting #define TIM_MAX_SCOPE 32 // max scope nesting
#define TIM_MAX_CELLS 0x20000 // size of screen buffer #define TIM_MAX_CELLS 0x20000 // size of screen buffer
#define TIM_MAX_BUF (TIM_MAX_CELLS * 4) // size of output buffer #define TIM_MAX_BUF (TIM_MAX_CELLS * 4) // size of output buffer
#define A INT_MAX // auto center / width / height #define A INT_MAX // auto center / width / height
typedef enum TimEventType {
TimEvent_Void, // an event was consumed
TimEvent_Draw, // draw screen
TimEvent_Key, // a key was pressed
TimEvent_Mouse, // mouse button, scroll or move
} TimEventType;
enum {
TimKey_MouseButtonLeft = 1,
TimKey_Backspace = 8,
TimKey_Tab = 9,
TimKey_Enter = 13,
TimKey_Escape = 27,
TimKey_Insert = -1,
TimKey_Delete = -2,
TimKey_Home = -3,
TimKey_End = -4,
TimKey_PageUp = -5,
TimKey_PageDown = -6,
TimKey_Up = -7,
TimKey_Down = -8,
TimKey_Left = -9,
TimKey_Right = -10,
};
// key code or 32-bit unicode char
typedef i32 TimKey;
#pragma endregion #pragma endregion
#pragma region types #pragma region types
// 16 ANSI colors supported by any terminal emulator.
// It's better to use xterm256 colors istead,
// because ANSI colors look different in each terminal.
// https://www.ditig.com/256-colors-cheat-sheet
enum {
TimColor16_Black = 0x00,
TimColor16_DarkRed = 0x01,
TimColor16_DarkGreen = 0x02,
TimColor16_DarkYellow = 0x03,
TimColor16_DarkBlue = 0x04,
TimColor16_DarkMagenta = 0x05,
TimColor16_DarkCyan = 0x06,
TimColor16_Gray = 0x07,
TimColor16_DarkGray = 0x08,
TimColor16_Red = 0x09,
TimColor16_Green = 0x0a,
TimColor16_Yellow = 0x0b,
TimColor16_Blue = 0x0c,
TimColor16_Magenta = 0x0d,
TimColor16_Cyan = 0x0e,
TimColor16_White = 0x0f,
};
typedef struct TimStyle {
u8 brd; // border
u8 bg; // background
u8 fg; // foreground
} TimStyle;
typedef struct TimCell { typedef struct TimCell {
u8 fg; // foreground color u8 fg; // foreground color
u8 bg; // background color u8 bg; // background color
@@ -124,6 +123,35 @@ typedef struct TimLine {
i32 width; // line width in glyph i32 width; // line width in glyph
} TimLine; } TimLine;
typedef enum TimEventType {
TimEvent_Void, // an event was consumed
TimEvent_Draw, // draw screen
TimEvent_Key, // a key was pressed
TimEvent_Mouse, // mouse button, scroll or move
} TimEventType;
enum {
TimKey_MouseButtonLeft = 1,
TimKey_MouseScrollUp = 4,
TimKey_MouseScrollDown = 5,
TimKey_Backspace = 8,
TimKey_Tab = 9,
TimKey_Enter = 13,
TimKey_Escape = 27,
TimKey_Insert = -1,
TimKey_Delete = -2,
TimKey_Home = -3,
TimKey_End = -4,
TimKey_PageUp = -5,
TimKey_PageDown = -6,
TimKey_Up = -7,
TimKey_Down = -8,
TimKey_Left = -9,
TimKey_Right = -10,
};
// key code or 32-bit unicode char
typedef i32 TimKey;
typedef struct TimEvent { typedef struct TimEvent {
TimEventType type; TimEventType type;
TimKey key; // used by TimEvent_Key and TimEvent_Mouse TimKey key; // used by TimEvent_Key and TimEvent_Mouse
@@ -132,13 +160,41 @@ typedef struct TimEvent {
char s[32]; // string representation of key, used by TimEvent_Key char s[32]; // string representation of key, used by TimEvent_Key
} TimEvent; } TimEvent;
typedef struct TimEditState { typedef struct TimEditState {
bool masked; // if true prints '*' instead of buffer content
i32 cursor; // cursor position (utf8) i32 cursor; // cursor position (utf8)
i32 length; // string length (utf8) i32 length; // string length (utf8)
i32 capacity; // buffer size i32 capacity; // buffer size in bytes
char* s; // zero terminated buffer char* s; // zero terminated buffer
} TimEditState; } TimEditState;
typedef struct TimPanelItem {
// Size of item to know where to draw next item.
// Set to to A and items will be spread equally across panel
i32 w;
i32 h;
void* data; // is passed to draw()
void* focus_target; // is assigned to tim->focus
void (*draw)(void* data, TimRect place, bool is_selected);
} TimPanelItem;
typedef struct TimPanel {
TimPanelItem* items; // array
i32 len; // number of items
i32 cur; // index of current item
i32 spacing; // distance between items
bool is_horizontal;
} TimPanel;
typedef struct TimScrollView {
i32 offset;
i32 content_h;
void* data; // is passed to draw()
void (*draw)(void* data, TimRect place);
} TimScrollView;
typedef struct TimState { typedef struct TimState {
i32 w; // screen width i32 w; // screen width
i32 h; // screen height i32 h; // screen height
@@ -195,9 +251,17 @@ i64 tim_time_usec(void);
#pragma region scope #pragma region scope
// for some stupid reason gcc requires more than 3 levels of macros to concat _i_ with line number
#define _tim_cat2(A, B) A##B
#define _tim_scope_line(L) _tim_cat2(_i_, L)
#define _tim_scope_i _tim_scope_line(__LINE__)
// enter layout scope // enter layout scope
#define tim_scope(x, y, w, h) \ #define tim_scope(x, y, w, h) for (\
for (i32 _i = tim_enter_scope((x), (y), (w), (h)); _i; _i = tim_exit_scope()) i32 _tim_scope_i = tim_enter_scope((x), (y), (w), (h)); \
_tim_scope_i;\
_tim_scope_i = tim_exit_scope()\
) /* here goes your { code } */
// convert relative (scoped) to absolute (screen) coordinates // convert relative (scoped) to absolute (screen) coordinates
TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h); TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h);
@@ -208,51 +272,87 @@ i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h);
// exit scope and pop stack // exit scope and pop stack
i32 tim_exit_scope(void); i32 tim_exit_scope(void);
static inline TimRect tim_rect_fit(TimRect r){
if(r.x < 0)
r.x = 0;
else if(r.x >= tim->w)
r.w = 0;
else if(r.x + r.w > tim->w)
r.w = tim->w - r.x;
if(r.y < 0)
r.y = 0;
else if(r.y >= tim->h)
r.y = tim->h - 1;
else if(r.y + r.h > tim->h)
r.h = tim->h - r.y;
return r;
}
#pragma endregion #pragma endregion
#pragma region widgets #pragma region widgets
// TODO: create enum TimColor and struct TimStyle
// frame // frame
// color: background, frame // color: background, frame
void tim_frame(i32 x, i32 y, i32 w, i32 h, u64 color); void tim_frame(i32 x, i32 y, i32 w, i32 h, TimStyle style);
// text label // text label
// str : text - supports multiple lines // str : text - supports multiple lines
// color: background, text // color: background, text
void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, u64 color); void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, TimStyle style);
// button - returns true on click // button with border - returns true on click
// color: frame, background, text // color: frame, background, text
bool tim_button(cstr txt, i32 x, i32 y, i32 w, i32 h, u64 color); bool tim_button(cstr txt, i32 x, i32 y, i32 w, i32 h, TimStyle style);
// button without border - returns true on click
// color: frame, background, text
bool tim_button_noborder(cstr txt, i32 x, i32 y, i32 w, i32 h, TimStyle style);
// check box - returns true when clicked // check box - returns true when clicked
// txt : text label // txt : text label
// state: persistent state, 0 unchecked, -1 semi checked, !0: checked // state: persistent state, 0 unchecked, -1 semi checked, !0: checked
// color: check, background, text // color: check, background, text
bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, u64 color); bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, TimStyle style);
// radio button - return true when clicked // radio button - return true when clicked
// txt : text label // txt : text label
// state: persistent state, selected if *state == v // state: persistent state, selected if *state == v
// v : value // v : value
// color: radio, background, text // color: radio, background, text
bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, u64 color); bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, TimStyle style);
/// text edit - value in state /// text edit - value in state
/// @param e persistent edit state, use TimEditState_init() to create new state /// @param e persistent edit state, use TimEditState_construct() to create new state
/// @param color frame, background, text /// @param style frame, background, text
/// @return key id or 0 /// @return key id or 0
TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, u64 color); TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, TimStyle style);
void TimEditState_init(TimEditState* e, i32 capacity, cstr initial_content);
/// @param e uninitialized state
/// @param buffer an array
/// @param capacity buffer size in bytes
/// @param initial_content may be NULL
void TimEditState_construct(TimEditState* e, char* buffer, i32 capacity, cstr initial_content);
void TimEditState_insert(TimEditState* e, cstr s); void TimEditState_insert(TimEditState* e, cstr s);
void TimEditState_delete(TimEditState* e); void TimEditState_delete(TimEditState* e);
/// Panel with sequence of items. You can select an item by arrow keys or mouse click.
/// @param self persistent state
/// @param is_selected if panel is not selected, it calls items[:]->draw(is_selected=false)
/// @return current item
TimPanelItem* tim_panel(TimPanel* self, bool is_selected, i32 x, i32 y, i32 w, i32 h);
void TimPanel_selectNext(TimPanel* self);
void TimPanel_selectPrev(TimPanel* self);
///
void tim_scroll_view(TimScrollView* self, i32 x, i32 y, i32 w, i32 h, TimStyle style);
#pragma endregion #pragma endregion
@@ -262,13 +362,21 @@ void TimEditState_delete(TimEditState* e);
bool tim_is_event_key(TimEventType type, TimKey key); bool tim_is_event_key(TimEventType type, TimKey key);
// returns true if event was press of key // returns true if event was press of key
bool tim_is_key_press(TimKey key); static inline bool tim_is_key_press(TimKey key) { return tim_is_event_key(TimEvent_Key, key); }
static inline bool tim_is_mouse_scroll_up() { return tim_is_event_key(TimEvent_Mouse, TimKey_MouseScrollUp); }
static inline bool tim_is_mouse_scroll_down() { return tim_is_event_key(TimEvent_Mouse, TimKey_MouseScrollDown); }
// returns true if mouse event was over r // returns true if mouse event was over r
bool tim_is_mouse_over(TimRect r); bool tim_is_mouse_over(TimRect r);
// returns true if event is mouse left-down and over r // returns true if event is mouse left-down and over r
bool tim_is_mouse_click_over(TimRect r); bool tim_is_mouse_click_over(TimRect r);
static inline void tim_event_consume(){
tim->event.type = TimEvent_Void;
}
#pragma endregion #pragma endregion
@@ -289,8 +397,8 @@ void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w);
// draw column of cells // draw column of cells
void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h); void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h);
// fill lot (area) of cells // fill area with cells
void tim_draw_lot(TimCell cell, i32 x, i32 y, i32 w, i32 h); void tim_fill(TimCell cell, i32 x, i32 y, i32 w, i32 h);
// draw string to line, tags potential wide characters // draw string to line, tags potential wide characters
void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg); void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg);

View File

@@ -38,17 +38,19 @@ STATIC_LIB_FILE="$PROJECT.a"
# example: "-I./include -I$DEPENDENCIES_DIR/libexample" # example: "-I./include -I$DEPENDENCIES_DIR/libexample"
INCLUDE="-Iinclude -Isrc" INCLUDE="-Iinclude -Isrc"
EXEC_FILE=""
EXEC_EXT=""
# OS-specific options # OS-specific options
case "$OS" in case "$OS" in
WINDOWS) WINDOWS)
EXEC_FILE="snek.exe" EXEC_EXT=".exe"
SHARED_LIB_FILE="$PROJECT.dll" SHARED_LIB_FILE="$PROJECT.dll"
INCLUDE="$INCLUDE " INCLUDE="$INCLUDE "
# example: "-lSDL2 -lSDL2_image" # example: "-lSDL2 -lSDL2_image"
LINKER_LIBS="" LINKER_LIBS=""
;; ;;
LINUX) LINUX)
EXEC_FILE="snek"
SHARED_LIB_FILE="$PROJECT.so" SHARED_LIB_FILE="$PROJECT.so"
INCLUDE="$INCLUDE " INCLUDE="$INCLUDE "
LINKER_LIBS="" LINKER_LIBS=""
@@ -58,28 +60,76 @@ case "$OS" in
;; ;;
esac esac
TEST_C_ARGS="-O0 -g3"
# TASKS # TASKS
case "$TASK" in case "$TASK" in
# creates executable using profiling info if it exists # ./ask question?
build_exec) ask)
SRC_C="$SRC_C example/snek.c" EXEC_FILE="ask$EXEC_EXT"
# -flto applies more optimizations across object files SRC_C="example/ask.c"
# -flto=auto is needed to multithreaded copilation LINKER_LIBS="bin/tim.a"
# -fuse-linker-plugin is required to use static libs with lto C_ARGS="$TEST_C_ARGS"
# -fprofile-use enables compiler to use profiling info files to optimize executable
# -fprofile-prefix-path sets path where profiling info about objects are be saved
# -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT="" PRE_TASK_SCRIPT=""
TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT="@cbuild/default_tasks/strip_exec.sh" POST_TASK_SCRIPT=""
;; ;;
# creates executable with debug info and no optimizations # hello world
build_exec_dbg) hello)
SRC_C="$SRC_C example/snek.c" EXEC_FILE="hello$EXEC_EXT"
C_ARGS="-O0 -g3" SRC_C="example/hello.c"
LINKER_LIBS="bin/tim.a"
C_ARGS="$TEST_C_ARGS"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=""
TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT=""
;;
# snake game
snek)
EXEC_FILE="snek$EXEC_EXT"
SRC_C="example/snek.c"
LINKER_LIBS="bin/tim.a"
C_ARGS="$TEST_C_ARGS"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=""
TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT=""
;;
# test
test)
EXEC_FILE="test$EXEC_EXT"
SRC_C="test/test.c"
LINKER_LIBS="bin/tim.a"
C_ARGS="$TEST_C_ARGS"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=""
TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT=""
;;
# color
color)
EXEC_FILE="color$EXEC_EXT"
SRC_C="test/color.c"
LINKER_LIBS="bin/tim.a"
C_ARGS="$TEST_C_ARGS"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=""
TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh"
POST_TASK_SCRIPT=""
;;
# string
string)
EXEC_FILE="string$EXEC_EXT"
SRC_C="test/string.c"
LINKER_LIBS="bin/tim.a"
C_ARGS="$TEST_C_ARGS"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT="" PRE_TASK_SCRIPT=""

View File

@@ -5,6 +5,26 @@ Fork of https://codeberg.org/chuvok/tim.h
tim is a portable library to create simple terminal applications tim is a portable library to create simple terminal applications
Demo video: https://asciinema.org/a/zn3p0dsVCOQOzwY1S9gDfyaxQ Demo video: https://asciinema.org/a/zn3p0dsVCOQOzwY1S9gDfyaxQ
## build
1. Clone this repository.
```
git clone https://timerix.ddns.net/git/Timerix/tim.git
```
2. Install [cbuild](https://timerix.ddns.net/git/Timerix/cbuild/releases).
Select latest version compatible with the one in `project.config`.
Example: For `2.3.2` download latest `2.3.x`.
3. Build static library
```
cd tim
cbuild build_static_lib
```
4. Build tests and examples
```
cbuild ask hello snek test color string
```
## quick start ## quick start
See [example/hello.c](./example/hello.c) See [example/hello.c](./example/hello.c)
@@ -44,18 +64,11 @@ 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. Colors are stored as 8-bit values.
Typically byte 0 is the text color and byte 1 is the background color. Most terminals support 16 ANSI colors. You can see them in TimColor16 enum.
It's better to use xterm256 colors istead, because ANSI colors look different in each terminal.
For example 0x08040f encodes three colors. When used with a button the text https://www.ditig.com/256-colors-cheat-sheet
is white (0f), the background is blue (04), and the frame is gray (08). ![xterm-256 color chart](./256colors.jpg)
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 ## 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
@@ -66,20 +79,13 @@ event when focused in order to prevent other key handlers on acting on them.
The current event is stored in tim->event. The current event is stored in tim->event.
event | cause
-------------|-----------------------
DRAW_EVENT | input, timeout, resize
KEY_EVENT | key press
MOUSE_EVENT | mouse click
VOID_EVENT | consumed event
## elements ## elements
frame (x, y, w, h, color) frame (x, y, w, h, color)
Draw an empty frame and fill area. Draw an empty frame and fill area.
x/y/w/h see layout documentation x/y/w/h see layout documentation
color background, frame style background, frame
label (str, x, y, w, h, color) label (str, x, y, w, h, color)
@@ -88,7 +94,7 @@ label (str, x, y, w, h, color)
str zero terminated string str zero terminated string
x/y/w/h see layout documentation x/y/w/h see layout documentation
color background, text style background, text
button (str, x, y, w, h, color) -> bool button (str, x, y, w, h, color) -> bool
@@ -97,7 +103,7 @@ button (str, x, y, w, h, color) -> bool
str zero terminated string str zero terminated string
x/y/w/h see layout documentation x/y/w/h see layout documentation
color frame, background, text style frame, background, text
edit (state, x, y, w, color) -> int edit (state, x, y, w, color) -> int
@@ -108,7 +114,7 @@ edit (state, x, y, w, color) -> int
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
color f rame, background, text style f rame, background, text
check (str, state, x, y, w, color) -> bool check (str, state, x, y, w, color) -> bool
@@ -119,7 +125,7 @@ check (str, state, x, y, w, color) -> bool
str zero terminated string str zero terminated string
state pointer to persistent state variable state pointer to persistent state variable
x/y/w see layout documentation x/y/w see layout documentation
color check, background, text style check, background, text
radio (str, state, v, x, y, w, color) -> bool radio (str, state, v, x, y, w, color) -> bool
@@ -131,7 +137,7 @@ radio (str, state, v, x, y, w, color) -> bool
state pointer to persistent state variable state pointer to persistent state variable
v unique state value v unique state value
x/y/w see layout documentation x/y/w see layout documentation
color radio, background, text style radio, background, text
## functions ## functions
tim_run (fps) -> bool tim_run (fps) -> bool
@@ -166,6 +172,8 @@ 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
- If Enter key doesn't work as expected on linux, write `stty sane` to `~/.profile`.
It will change terminal settings from insane to sane xD
- 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
handle these consistently and there is no portable way to reliably handle these consistently and there is no portable way to reliably

View File

@@ -15,31 +15,41 @@ void tim_clear_cells(void) {
} }
void tim_draw_chr(TimCell cell, i32 x, i32 y) { void tim_draw_chr(TimCell cell, i32 x, i32 y) {
if (x >= 0 && x < tim->w && y >= 0 && y < tim->h) { TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
if (x >= scope.x && x < scope.x + scope.w &&
y >= scope.y && y < scope.y + scope.h)
{
tim->cells[x + y * tim->w] = cell; tim->cells[x + y * tim->w] = cell;
} }
} }
void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w) { void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w) {
if (y >= 0 && y < tim->h && w > 0) { TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
for (i32 i = MAX(x, 0); i < MIN(x + w, tim->w); i++) {
if (y >= scope.y && y < scope.y + scope.h && w > 0) {
for (i32 i = MAX(x, scope.x); i < MIN(x + w, scope.x + scope.w); i++) {
tim->cells[i + y * tim->w] = cell; tim->cells[i + y * tim->w] = cell;
} }
} }
} }
void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h) { void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h) {
if (x >= 0 && x < tim->w && h > 0) { TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
for (i32 i = MAX(y, 0); i < MIN(y + h, tim->h); i++) {
if (x >= scope.x && x < scope.x + scope.w && h > 0) {
for (i32 i = MAX(y, scope.y); i < MIN(y + h, scope.y + scope.h); i++) {
tim->cells[x + i * tim->w] = cell; tim->cells[x + i * tim->w] = cell;
} }
} }
} }
void tim_draw_lot(TimCell cell, i32 x, i32 y, i32 w, i32 h) { void tim_fill(TimCell cell, i32 x, i32 y, i32 w, i32 h) {
TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
for (i32 iy = MAX(y, 0); iy < MIN(y + h, tim->h); iy++) { for (i32 iy = MAX(y, scope.y); iy < MIN(y + h, scope.y + scope.h); iy++) {
for (i32 ix = MAX(x, 0); ix < MIN(x + w, tim->w); ix++) { for (i32 ix = MAX(x, scope.x); ix < MIN(x + w, scope.x + scope.w); ix++) {
tim->cells[ix + iy * tim->w] = cell; tim->cells[ix + iy * tim->w] = cell;
} }
} }
@@ -47,8 +57,10 @@ void tim_draw_lot(TimCell cell, i32 x, i32 y, i32 w, i32 h) {
} }
void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg) { void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg) {
if (s && y >= 0 && x < tim->w && y < tim->h ) { TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
i32 end = MIN(x + w, tim->w);
if (s && y >= 0 && x < scope.x + scope.w && y < scope.y + scope.h ) {
i32 end = MIN(x + w, scope.x + scope.w);
bool wide = false; bool wide = false;
for (i32 i = 0; s[i] && x < end; x++) { for (i32 i = 0; s[i] && x < end; x++) {
TimCell c = tim_cell(&s[i], fg, bg); TimCell c = tim_cell(&s[i], fg, bg);
@@ -63,6 +75,8 @@ void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg) {
} }
void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg) { void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg) {
if(w <= 0 || h <= 0)
return;
tim_draw_chr(tim_cell("", fg, bg), x , y); tim_draw_chr(tim_cell("", fg, bg), x , y);
tim_draw_chr(tim_cell("", fg, bg), x + w - 1, y); tim_draw_chr(tim_cell("", fg, bg), x + w - 1, y);
tim_draw_chr(tim_cell("", fg, bg), x , y + h - 1); tim_draw_chr(tim_cell("", fg, bg), x , y + h - 1);
@@ -71,12 +85,14 @@ void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg) {
tim_draw_row(tim_cell("", fg, bg), x + 1 , y + h - 1, w - 2); tim_draw_row(tim_cell("", fg, bg), x + 1 , y + h - 1, w - 2);
tim_draw_col(tim_cell("", fg, bg), x , y + 1 , h - 2); tim_draw_col(tim_cell("", fg, bg), x , y + 1 , h - 2);
tim_draw_col(tim_cell("", fg, bg), x + w - 1, y + 1 , h - 2); tim_draw_col(tim_cell("", fg, bg), x + w - 1, y + 1 , h - 2);
tim_draw_lot(tim_cell(" ", fg, bg), x + 1 , y + 1 , w - 2, h - 2); tim_fill(tim_cell(" ", fg, bg), x + 1 , y + 1 , w - 2, h - 2);
} }
void tim_draw_invert(i32 x, i32 y, i32 w) { void tim_draw_invert(i32 x, i32 y, i32 w) {
if (y >= 0 && y < tim->h && w > 0) { TimRect scope = tim_rect_fit(tim->scopes[tim->scope]);
for (i32 i = MAX(x, 0); i < MIN(x + w, tim->w); i++) {
if (y >= 0 && y < scope.y + scope.h && w > 0) {
for (i32 i = MAX(x, scope.x); i < MIN(x + w, scope.x + scope.w); i++) {
TimCell c = tim->cells[i + y * tim->w]; TimCell c = tim->cells[i + y * tim->w];
tim->cells[i + y * tim->w].fg = c.bg; tim->cells[i + y * tim->w].fg = c.bg;
tim->cells[i + y * tim->w].bg = c.fg; tim->cells[i + y * tim->w].bg = c.fg;

View File

@@ -1,13 +1,16 @@
#include "tim.h" #include "tim.h"
void TimEditState_construct(TimEditState* e, char* buf, i32 capacity, cstr initial_content){
void TimEditState_init(TimEditState* e, i32 capacity, cstr initial_content){ e->masked = false;
e->length = tim_utf8_len(initial_content); e->length = initial_content ? tim_utf8_len(initial_content) : 0;
e->cursor = tim_utf8_len(initial_content); e->cursor = e->length;
e->capacity = capacity; e->capacity = capacity;
e->s = (char*)malloc(capacity + 1); e->s = buf;
i32 byte_len = strlen(initial_content); i32 byte_len = 0;
if(e->length > 0){
byte_len = strlen(initial_content);
memcpy(e->s, initial_content, byte_len); memcpy(e->s, initial_content, byte_len);
}
e->s[byte_len] = 0; e->s[byte_len] = 0;
} }
@@ -47,7 +50,8 @@ static TimKey edit_event(TimEditState* e, TimRect r) {
// not focused or no key press // not focused or no key press
return 0; return 0;
} }
tim->event.type = TimEvent_Void; // consume event
tim_event_consume();
switch (tim->event.key) { switch (tim->event.key) {
case TimKey_Escape: case TimKey_Escape:
@@ -85,18 +89,36 @@ static TimKey edit_event(TimEditState* e, TimRect r) {
return tim->event.key; return tim->event.key;
} }
TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, u64 color) { TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, TimStyle style) {
TimRect r = tim_scope_rect_to_absolute(x, y, w, 3); TimRect r = tim_scope_rect_to_absolute(x, y, w, 3);
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
tim_draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); tim_draw_box(r.x, r.y, r.w, r.h, style.brd, style.bg);
if (tim->focus == e) {
char* s = e->s + tim_utf8_pos(e->s, e->cursor - r.w + 4); i32 s_x = r.x + 2, s_y = r.y + 1, s_w = r.w - 3, s_w_sub1 = s_w - 1;
i32 cur = MIN(r.w - 4, e->cursor); char* s = e->s;
tim_draw_str(s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); bool focused = tim->focus == e;
tim_draw_invert(r.x + cur + 2, r.y + 1, 1);
} else { if (focused) {
tim_draw_str(e->s, r.x + 2, r.y + 1, r.w - 3, color, color >> 8); // rewind long buffer so cursor is at the most right cell
s = e->s + tim_utf8_pos(e->s, e->cursor - s_w_sub1);
}
if(e->masked){
// draw '*' instead of buffer characters
TimCell cell_masked = tim_cell("*", style.fg, style.bg);
i32 w_mask = MIN(s_w, e->length);
tim_draw_row(cell_masked, s_x, s_y, w_mask);
}
else {
// draw part of buffer content
tim_draw_str(s, s_x, s_y, s_w, style.fg, style.bg);
}
if (focused) {
// invert color of last cell
i32 cur = MIN(s_w_sub1, e->cursor);
tim_draw_invert(s_x + cur, s_y, 1);
} }
} }

View File

@@ -4,10 +4,6 @@ bool tim_is_event_key(TimEventType type, TimKey key) {
return tim->event.type == type && tim->event.key == key; return tim->event.type == type && tim->event.key == key;
} }
bool tim_is_key_press(TimKey key) {
return tim_is_event_key(TimEvent_Key, key);
}
bool tim_is_mouse_over(TimRect r) { bool tim_is_mouse_over(TimRect r) {
i32 x = tim->event.x; i32 x = tim->event.x;
i32 y = tim->event.y; i32 y = tim->event.y;

View File

@@ -7,10 +7,12 @@ TimState* tim = NULL;
static void tim_init(void){ static void tim_init(void){
tim = (TimState*)malloc(sizeof(TimState)); tim = (TimState*)malloc(sizeof(TimState));
memset(tim, 0, sizeof(TimState)); memset(tim, 0, sizeof(TimState));
size_t cdb_size = (TIM_MAX_CELLS << TIM_ENABLE_DBUF); size_t cdb_size = (TIM_MAX_CELLS << TIM_ENABLE_DBUF) * sizeof(TimCell);
tim->cells_double_buf = (TimCell*)malloc(cdb_size * sizeof(TimCell)); tim->cells_double_buf = (TimCell*)malloc(cdb_size);
memset(tim->cells_double_buf, 0, cdb_size);
tim->cells = tim->cells_double_buf; tim->cells = tim->cells_double_buf;
tim->buf = (char*)malloc(TIM_MAX_BUF); tim->buf = (char*)malloc(TIM_MAX_BUF);
memset(tim->buf, 0, TIM_MAX_BUF);
} }
static void tim_deinit(void){ static void tim_deinit(void){

86
src/panel.c Executable file
View File

@@ -0,0 +1,86 @@
#include "tim.h"
void TimPanel_selectNext(TimPanel* l){
if(l->cur + 1 < l->len)
l->cur++;
}
void TimPanel_selectPrev(TimPanel* l){
if(l->cur - 1 >= 0)
l->cur--;
}
TimPanelItem* tim_panel(TimPanel* self, bool is_panel_selected, i32 x, i32 y, i32 w, i32 h){
// select item with keyboard
if(tim_is_key_press(self->is_horizontal ? TimKey_Left : TimKey_Up))
{
TimPanel_selectPrev(self);
}
else if(tim_is_key_press(self->is_horizontal ? TimKey_Right : TimKey_Down))
{
TimPanel_selectNext(self);
}
// set focus on current item
if(self->cur < self->len)
tim->focus = self->items[self->cur].focus_target;
tim_scope(x, y, w, h)
{
TimRect content_scope = tim->scopes[tim->scope];
// TODO: draw current item and as much previous items as possible in scope
TimRect item_place = { 0 };
for(i32 i = 0; i < self->len; i++){
TimPanelItem* item = &self->items[i];
item_place.w = item->w;
if(item_place.w == A){
if(self->is_horizontal){
item_place.w = content_scope.w / self->len ;
// add remaining width to the last item
if(i == self->len - 1)
item_place.w += content_scope.w % self->len;
else item_place.w -= self->spacing;
}
else {
item_place.w = content_scope.w;
}
}
item_place.h = item->h;
if(item_place.h == A){
if(self->is_horizontal){
item_place.h = content_scope.h;
}
else {
item_place.h = content_scope.h / self->len - self->spacing;
// add remaining height to the last item
if(i == self->len - 1)
item_place.h += content_scope.h % self->len;
else item_place.h -= self->spacing;
}
}
// select item with mouse click
if(tim_is_mouse_click_over(tim_scope_rect_to_absolute(item_place.x, item_place.y, item_place.w, item_place.h))){
self->cur = i;
tim->focus = item->focus_target;
}
bool is_item_selected = false;
if(is_panel_selected)
is_item_selected = i == self->cur;
item->draw(item->data, item_place, is_item_selected);
// adjust place for next item
if(self->is_horizontal){
item_place.x += item_place.w + self->spacing;
}
else {
item_place.y += item_place.h + self->spacing;
}
}
}
return &self->items[self->cur];
}

View File

@@ -35,9 +35,9 @@ TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h) {
} }
i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h) { i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h) {
if (tim->scope + 1 >= TIM_MAX_SCOPE) { if (tim->scope + 1 >= TIM_MAX_SCOPE)
return 0; return 0;
}
TimRect r = tim_scope_rect_to_absolute(x, y, w, h); TimRect r = tim_scope_rect_to_absolute(x, y, w, h);
tim->scope += 1; tim->scope += 1;
tim->scopes[tim->scope] = r; tim->scopes[tim->scope] = r;

81
src/scroll_view.c Normal file
View File

@@ -0,0 +1,81 @@
#include "tim.h"
#include <math.h>
void tim_scroll_view(TimScrollView* self, i32 x, i32 y, i32 w, i32 h, TimStyle style){
tim_scope(x, y, w, h)
{
TimRect content_scope = tim->scopes[tim->scope];
// shrink content_scope to put scrollbar
content_scope.w -= 1;
i32 max_offset = MAX(self->content_h - content_scope.h, 0);
// draw scroll bar and
TimRect arrow_up_r = {
.x = content_scope.x + content_scope.w,
.y = content_scope.y,
.w = 1,
.h = 1
};
TimRect scrollbar_r = {
.x = arrow_up_r.x,
.y = arrow_up_r.y + 1,
.w = 1,
.h = content_scope.h - 2
};
TimRect arrow_down_r = {
.x = scrollbar_r.x,
.y = scrollbar_r.y + scrollbar_r.h,
.w = 1,
.h = 1
};
if (tim->event.type == TimEvent_Draw) {
f32 scroll_ratio = 0;
f32 slider_h = 0;
if(max_offset != 0){
slider_h = ceilf((f32)scrollbar_r.h / max_offset);
scroll_ratio = (f32)self->offset / max_offset + 0.001f;
}
i32 slider_y = scrollbar_r.y + (scrollbar_r.h - slider_h) * scroll_ratio;
tim_draw_chr(tim_cell("", style.brd, style.bg), arrow_up_r.x, arrow_up_r.y);
tim_draw_col(tim_cell("", style.brd, style.bg), scrollbar_r.x, scrollbar_r.y, scrollbar_r.h);
tim_draw_col(tim_cell("", style.brd, style.bg), scrollbar_r.x, slider_y, slider_h);
tim_draw_chr(tim_cell("", style.brd, style.bg), arrow_down_r.x, arrow_down_r.y);
}
if(tim_is_mouse_click_over(arrow_up_r) || tim_is_mouse_scroll_up()){
self->offset--;
}
else if(tim_is_mouse_click_over(arrow_down_r) || tim_is_mouse_scroll_down()){
self->offset++;
}
else if(tim_is_key_press(TimKey_PageUp)){
self->offset -= content_scope.h;
}
else if(tim_is_key_press(TimKey_PageDown)){
self->offset += content_scope.h;
}
if(self->offset > max_offset)
self->offset = max_offset;
else if(self->offset < 0)
self->offset = 0;
if(tim_is_mouse_click_over(scrollbar_r)){
i32 click_y_rel = tim->event.y - scrollbar_r.y;
if(scrollbar_r.h != 0){
f32 slider_h = ceilf((f32)scrollbar_r.h / max_offset);
f32 scroll_ratio = (f32)click_y_rel / (scrollbar_r.h - slider_h) + 0.001f;
self->offset = max_offset * scroll_ratio;
}
}
// update current scope
tim->scopes[tim->scope] = content_scope;
// draw content
TimRect content_place = { .x = 0, .y = -self->offset - 1, .w = content_scope.w, .h = content_scope.h };
self->draw(self->data, content_place);
}
}

View File

@@ -1,3 +1,12 @@
// Enable POSIX 2004 definitions.
// Required to use clock_gettime, localtime_r, gmtime_r in ISO C.
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif
// Enable cfmakeraw()
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#include "tim.h" #include "tim.h"
#ifdef TIM_UNIX #ifdef TIM_UNIX
@@ -56,13 +65,14 @@ void tim_reset_terminal(void) {
tcsetattr(STDOUT_FILENO, TCSADRAIN, &tim->attr); // restore attributes tcsetattr(STDOUT_FILENO, TCSADRAIN, &tim->attr); // restore attributes
tim_write_str(S("\33[?1000l")); // disable mouse tim_write_str(S("\33[?1000l")); // disable mouse
tim_write_str(S("\33[?1002l")); // disable mouse tim_write_str(S("\33[?1002l")); // disable mouse
tim_write_str(S("\33[?1006l")); // disable mouse
tim_write_str(S("\33[m")); // reset colors tim_write_str(S("\33[m")); // reset colors
tim_write_str(S("\33[?25h")); // show cursor tim_write_str(S("\33[?25h")); // show cursor
tim_write_str(S("\33[?1049l")); // exit alternate buffer tim_write_str(S("\33[?1049l")); // exit alternate buffer
} }
// parse input stored in e->s // parse input stored in e->s
bool parse_input(event* restrict e, i32 n) { bool parse_input(TimEvent* restrict e, i32 n) {
char* s = e->s; char* s = e->s;
if (n == 1 || s[0] != 27) { if (n == 1 || s[0] != 27) {
@@ -76,14 +86,23 @@ bool parse_input(event* restrict e, i32 n) {
// sgr mouse sequence // sgr mouse sequence
e->type = TimEvent_Mouse; e->type = TimEvent_Mouse;
i32 btn = strtol(s + 3, &s, 10); i32 btn = strtol(s + 3, &s, 10);
e->x = strtol(s + 1, &s, 10) - 1; e->x = strtol(s + 1, &s, 10);
e->y = strtol(s + 1, &s, 10) - 1; e->y = strtol(s + 1, &s, 10);
if (btn == 0 && s[0] == 'M') { // coordinates start from 1
// left button pressed if(e->x > 0) e->x--;
e->key = TimKey_MouseButtonLeft; if(e->y > 0) e->y--;
return true; // invalid sequence end
} if (s[0] != 'M')
return false; return false;
switch(btn){
case 0: e->key = TimKey_MouseButtonLeft; break;
case 64: e->key = TimKey_MouseScrollUp; break;
case 65: e->key = TimKey_MouseScrollDown; break;
default:
return false;
}
return true;
} }
struct {char s[4]; i32 k;} key_table[] = { struct {char s[4]; i32 k;} key_table[] = {
@@ -120,7 +139,7 @@ bool parse_input(event* restrict e, i32 n) {
} }
void tim_read_event(i32 timeout_ms) { void tim_read_event(i32 timeout_ms) {
event* e = &tim->event; TimEvent* e = &tim->event;
struct pollfd pfd[2] = { struct pollfd pfd[2] = {
{.fd = tim->signal_pipe[0], .events = POLLIN}, {.fd = tim->signal_pipe[0], .events = POLLIN},

View File

@@ -1,20 +1,20 @@
#include "tim.h" #include "tim.h"
void tim_frame(i32 x, i32 y, i32 w, i32 h, u64 color) { void tim_frame(i32 x, i32 y, i32 w, i32 h, TimStyle style) {
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
TimRect r = tim_scope_rect_to_absolute(x, y, w, h); TimRect r = tim_scope_rect_to_absolute(x, y, w, h);
tim_draw_box(r.x, r.y, r.w, r.h, color, color >> 8); tim_draw_box(r.x, r.y, r.w, r.h, style.brd, style.bg);
} }
} }
void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, u64 color) { void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, TimStyle style) {
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
TimText t = tim_scan_str(s); TimText t = tim_scan_str(s);
w = (w == A) ? t.width : w; w = (w == A) ? t.width : w;
h = (h == A) ? t.lines : h; h = (h == A) ? t.lines : h;
TimRect r = tim_scope_rect_to_absolute(x, y, w, h); TimRect r = tim_scope_rect_to_absolute(x, y, w, h);
TimCell c = tim_cell(" ", color, color >> 8); TimCell c = tim_cell(" ", style.fg, style.bg);
tim_draw_lot(c, r.x, r.y, r.w, r.h); tim_fill(c, r.x, r.y, r.w, r.h);
TimLine l = {.s = s, .line = ""}; TimLine l = {.s = s, .line = ""};
for (i32 i = 0; tim_next_line(&l); i++) { for (i32 i = 0; tim_next_line(&l); i++) {
tim_draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg); tim_draw_str(l.line, r.x, r.y + i, l.width, c.fg, c.bg);
@@ -22,28 +22,40 @@ void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, u64 color) {
} }
} }
bool tim_button(cstr txt, i32 x, i32 y, i32 w, i32 h, u64 color) { bool tim_button(cstr txt, i32 x, i32 y, i32 w, i32 h, TimStyle style) {
i32 tw = tim_utf8_len(txt); i32 txt_w = tim_utf8_len(txt);
w = (w == A) ? (tw + 4) : w; w = (w == A) ? (txt_w + 4) : w;
h = (h == A) ? 3 : h; h = (h == A) ? 3 : h;
TimRect r = tim_scope_rect_to_absolute(x, y, w, h); TimRect r = tim_scope_rect_to_absolute(x, y, w, h);
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
tim_draw_box(r.x, r.y, r.w, r.h, color >> 16, color >> 8); tim_draw_box(r.x, r.y, r.w, r.h, style.brd, style.bg);
tim_draw_str(txt, r.x + (w - tw) / 2, r.y + h / 2, w, color, color >> 8); tim_draw_str(txt, r.x + (w - txt_w) / 2, r.y + h / 2, w, style.fg, style.bg);
} }
return tim_is_mouse_click_over(r); return tim_is_mouse_click_over(r);
} }
bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, u64 color) { bool tim_button_noborder(cstr txt, i32 x, i32 y, i32 w, i32 h, TimStyle style) {
i32 txt_w = tim_utf8_len(txt);
w = (w == A) ? txt_w : w;
h = (h == A) ? 1 : h;
TimRect r = tim_scope_rect_to_absolute(x, y, w, h);
if (tim->event.type == TimEvent_Draw) {
tim_draw_str(txt, r.x + (w - txt_w) / 2, r.y + h / 2, w, style.fg, style.bg);
}
return tim_is_mouse_click_over(r);
}
bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, TimStyle style) {
w = (w == A) ? tim_utf8_len(txt) + 4 : w; w = (w == A) ? tim_utf8_len(txt) + 4 : w;
TimRect r = tim_scope_rect_to_absolute(x, y, w, 1); TimRect r = tim_scope_rect_to_absolute(x, y, w, 1);
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
cstr st = *state == -1 ? "-" : *state ? "x" : " "; cstr st = *state == -1 ? "-" : *state ? "x" : " ";
tim_draw_str("[ ] ", r.x, r.y, 4, color, color >> 8); tim_draw_str("[ ] ", r.x, r.y, 4, style.fg, style.bg);
tim_draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); tim_draw_str(st, r.x + 1, r.y, 1, style.brd, style.bg);
tim_draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); tim_draw_str(txt, r.x + 4, r.y, r.w - 4, style.fg, style.bg);
} }
bool click = tim_is_mouse_click_over(r); bool click = tim_is_mouse_click_over(r);
@@ -52,20 +64,18 @@ bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, u64 color) {
} }
bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, u64 color) { bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, TimStyle style) {
w = (w == A) ? tim_utf8_len(txt) + 4 : w; w = (w == A) ? tim_utf8_len(txt) + 4 : w;
TimRect r = tim_scope_rect_to_absolute(x, y, w, 1); TimRect r = tim_scope_rect_to_absolute(x, y, w, 1);
if (tim->event.type == TimEvent_Draw) { if (tim->event.type == TimEvent_Draw) {
cstr st = *state == v ? "o" : " "; cstr st = *state == v ? "o" : " ";
tim_draw_str("( ) ", r.x, r.y, 4, color, color >> 8); tim_draw_str("( ) ", r.x, r.y, 4, style.fg, style.bg);
tim_draw_str(st, r.x + 1, r.y, 1, color >> 16, color >> 8); tim_draw_str(st, r.x + 1, r.y, 1, style.brd, style.bg);
tim_draw_str(txt, r.x + 4, r.y, r.w - 4, color, color >> 8); tim_draw_str(txt, r.x + 4, r.y, r.w - 4, style.fg, style.bg);
} }
bool click = tim_is_mouse_click_over(r); bool click = tim_is_mouse_click_over(r);
*state = click ? v : *state; *state = click ? v : *state;
return click; return click;
} }

View File

@@ -113,7 +113,7 @@ void tim_read_event(i32 timeout_ms) {
// received input // received input
INPUT_RECORD rec = {0}; INPUT_RECORD rec = {0};
DWORD n = 0; DWORD n = 0;
ReadConsoleInput(h, &rec, 1, &n); ReadConsoleInputW(h, &rec, 1, &n);
switch (rec.EventType) { switch (rec.EventType) {
case KEY_EVENT: { case KEY_EVENT: {
@@ -140,9 +140,16 @@ void tim_read_event(i32 timeout_ms) {
} }
case MOUSE_EVENT: { case MOUSE_EVENT: {
bool wheel = rec.Event.MouseEvent.dwEventFlags & MOUSE_WHEELED;
if(wheel){
i16 scroll_value = HIWORD(rec.Event.MouseEvent.dwButtonState);
e->type = TimEvent_Mouse;
e->key = scroll_value > 0 ? TimKey_MouseScrollUp : TimKey_MouseScrollDown;
return;
}
bool move = rec.Event.MouseEvent.dwEventFlags & ~DOUBLE_CLICK; bool move = rec.Event.MouseEvent.dwEventFlags & ~DOUBLE_CLICK;
bool left = rec.Event.MouseEvent.dwButtonState & bool left = rec.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED;
FROM_LEFT_1ST_BUTTON_PRESSED;
if (move || !left) { if (move || !left) {
// ignore move events and buttons other than left // ignore move events and buttons other than left
continue; continue;

View File

@@ -26,4 +26,5 @@ i32 main(void) {
exit(1); exit(1);
} }
} }
return 0;
} }

View File

@@ -67,4 +67,5 @@ i32 main(void) {
TEST(tim_utf8_is_wide_perhaps(U("")) == false); TEST(tim_utf8_is_wide_perhaps(U("")) == false);
TEST(tim_utf8_is_wide_perhaps(U("")) == true); TEST(tim_utf8_is_wide_perhaps(U("")) == true);
TEST(tim_utf8_is_wide_perhaps(U("𐍈")) == true); TEST(tim_utf8_is_wide_perhaps(U("𐍈")) == true);
return 0;
} }

View File

@@ -1,120 +1,142 @@
#include "tim.h" #include "tim.h"
static inline void test_screen(TimEvent* e) { static TimEditState ed1;
static TimEvent me; static TimEditState ed2;
static TimEvent ke;
static i32 render_us;
char buf[64];
ke = (e->type == KEY_EVENT) ? *e : ke; static inline void test_screen(TimEvent* e) {
me = (e->type == MOUSE_EVENT) ? *e : me; static TimEvent me = {0};
static TimEvent ke = {0};
static i32 render_us = 0;
char buf[64] = {0};
if(e->type == TimEvent_Key)
ke = *e;
if(e->type == TimEvent_Mouse)
me = *e;
// positioning // positioning
tim_label("+", 0, 0, A, A, 0xf); static TimStyle style_default = { .fg = TimColor16_White };
tim_label("+", ~0, 0, A, A, 0xf); tim_label("+", 0, 0, A, A, style_default);
tim_label("+", 0, ~0, A, A, 0xf); tim_label("+", ~0, 0, A, A, style_default);
tim_label("+", ~0, ~0, A, A, 0xf); tim_label("+", 0, ~0, A, A, style_default);
tim_label("+", A, A, A, A, 0xf); tim_label("+", ~0, ~0, A, A, style_default);
tim_label("-", 0, A, A, A, 0xf); tim_label("+", A, A, A, A, style_default);
tim_label("-", ~0, A, A, A, 0xf); tim_label("-", 0, A, A, A, style_default);
tim_label("|", A, 0, A, A, 0xf); tim_label("-", ~0, A, A, A, style_default);
tim_label("|", A, ~0, A, A, 0xf); tim_label("|", A, 0, A, A, style_default);
tim_label("|", A, ~0, A, A, style_default);
// some information // some information
sprintf(buf, "screen: %dx%d", tim->w, tim->h); sprintf(buf, "screen: %dx%d", tim->w, tim->h);
tim_label(buf, 2, 0, A, A, 0xf); tim_label(buf, 2, 0, A, A, style_default);
sprintf(buf, "frame : [%c] %d", ": "[tim->frame & 1], tim->frame); sprintf(buf, "frame : [%c] %d", ": "[tim->frame & 1], tim->frame);
tim_label(buf, 2, 1, A, A, 0xf); tim_label(buf, 2, 1, A, A, style_default);
sprintf(buf, "key : [%d] %s", ke.key, ke.s + (ke.key < 32)); sprintf(buf, "key : [%d] %s", ke.key, ke.s + (ke.key < 32));
tim_label(buf, 2, 2, A, A, 0xf); tim_label(buf, 2, 2, A, A, style_default);
sprintf(buf, "mouse : [%d] %d:%d", me.key, me.x, me.y); sprintf(buf, "mouse : [%d] %d:%d", me.key, me.x, me.y);
tim_label(buf, 2, 3, A, A, 0xf); tim_label(buf, 2, 3, A, A, style_default);
sprintf(buf, "input : %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx", sprintf(buf, "input : %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx %02hhx",
e->s[0], e->s[1], e->s[2], e->s[3], e->s[4], e->s[5], e->s[6], e->s[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]);
tim_label(buf, 2, 4, A, A, 0xf); tim_label(buf, 2, 4, A, A, style_default);
// replace unprintable characters with space
i32 s_len = tim_utf8_len(e->s);
for(i32 i = 0; i < s_len; i++){
i32 pos = tim_utf8_pos(e->s, i);
u8 uc = e->s[pos];
if(uc < (u8)' '){
e->s[pos] = ' ';
}
}
tim_label(e->s, 2, 5, A, A, style_default);
// lower right // lower right
render_us += tim->render_us; render_us += tim->render_us;
sprintf(buf, "%d µs (Ø %d µs)", tim->render_us, render_us / MAX(tim->frame, 1)); sprintf(buf, "%d µs (Ø %d µs)", tim->render_us, render_us / MAX(tim->frame, 1));
tim_label(buf, ~2, ~2, A, A, 0xf); tim_label(buf, ~2, ~2, A, A, style_default);
sprintf(buf, "%d cells (%.0f %%)", tim->w * tim->h, 100.0 * tim->w * tim->h / TIM_MAX_CELLS); sprintf(buf, "%d cells (%.0f %%)", tim->w * tim->h, 100.0 * tim->w * tim->h / TIM_MAX_CELLS);
tim_label(buf, ~2, ~1, A, A, 0xf); tim_label(buf, ~2, ~1, A, A, style_default);
sprintf(buf, "%d bytes (%.0f %%)", tim->buf_size, 100.0 * tim->buf_size / TIM_MAX_BUF); sprintf(buf, "%d bytes (%.0f %%)", tim->buf_size, 100.0 * tim->buf_size / TIM_MAX_BUF);
tim_label(buf, ~2, ~0, A, A, 0xf); tim_label(buf, ~2, ~0, A, A, style_default);
// multi line label // multi line label
tim_label("multi\nliñe\nlabël", 24, 1, A, A, 0xf); tim_label("multi\nliñe\nlabël", 24, 1, A, A, style_default);
// colors // colors
tim_scope(1, 5, 16, 5) { tim_scope(1, 6, 16, 5) {
tim_frame(0, 0, ~0, ~0, 0xf); tim_frame(0, 0, ~0, ~0, style_default);
tim_label(" Red ", 1, 1, 7, A, 0x0900); tim_label(" Red ", 1, 1, 7, A, (TimStyle){ .bg = 0x09 });
tim_label(" ", 8, 1, 7, A, 0xc400); tim_label(" ", 8, 1, 7, A, (TimStyle){ .bg = 0xc4 });
tim_label(" Green ", 1, 2, 7, A, 0x0a00); tim_label(" Green ", 1, 2, 7, A, (TimStyle){ .bg = 0x0a });
tim_label(" ", 8, 2, 7, A, 0x2e00); tim_label(" ", 8, 2, 7, A, (TimStyle){ .bg = 0x2e });
tim_label(" Blue ", 1, 3, 7, A, 0x0c00); tim_label(" Blue ", 1, 3, 7, A, (TimStyle){ .bg = 0x0c });
tim_label(" ", 8, 3, 7, A, 0x1500); tim_label(" ", 8, 3, 7, A, (TimStyle){ .bg = 0x15 });
} }
// button // button
static u64 bc = 0x100; static TimStyle style_button = { .bg = 0x01 };
if (tim_button("Click Me", 17, 5, 16, 5, bc)) { if (tim_button("Click Me", 17, 6, 16, 5, style_button)) {
bc = (bc + 0x100) & 0xff00; style_button.bg = (style_button.bg + 1) & 0xff;
} }
// edit // edit
static TimEditState ed1; static TimStyle style_edit = { .brd = 0xff, .fg = 0xff };
static TimEditState ed2; tim_edit(&ed1, 1, 11, 32, style_edit);
TimEditState_init(&ed1, 32, "Edit 1");
TimEditState_init(&ed2, 32, "");
tim_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);
tim_label(buf, 2, 13, A, A, 0xf); tim_label(buf, 2, 12, A, A, style_default);
tim_edit(&ed2, 1, 14, 32, 0xff00ff); tim_edit(&ed2, 1, 15, 32, style_edit);
tim_label(ed2.s, 2, 17, A, A, 0xf); tim_label(ed2.s, 2, 18, A, A, style_default);
// checkbox // checkbox
static TimStyle style_checkbox = { .brd = TimColor16_Cyan, .fg = TimColor16_White };
static i32 chk[2] = {-1, 1}; static i32 chk[2] = {-1, 1};
tim_checkbox("Check 1", &chk[0], 1, 18, A, 0xa000f); tim_checkbox("Check 1", &chk[0], 1, 19, A, style_checkbox);
tim_checkbox("Check 2", &chk[1], 14, 18, A, 0xa000f); tim_checkbox("Check 2", &chk[1], 14, 19, A, style_checkbox);
// radiobox // radiobox
static i32 rad = 0; static i32 rad = 0;
tim_radiobutton("Radio 1", &rad, 1, 1, 19, A, 0xa000f); tim_radiobutton("Radio 1", &rad, 1, 1, 20, A, style_checkbox);
tim_radiobutton("Radio 2", &rad, 2, 14, 19, A, 0xa000f); tim_radiobutton("Radio 2", &rad, 2, 14, 20, A, style_checkbox);
tim_radiobutton("Radio 3", &rad, 3, 1, 20, A, 0xa000f); tim_radiobutton("Radio 3", &rad, 3, 1, 21, A, style_checkbox);
tim_radiobutton("Radio 4", &rad, 4, 14, 20, A, 0xa000f); tim_radiobutton("Radio 4", &rad, 4, 14, 21, A, style_checkbox);
// scope nesting // scope nesting
tim_scope(~1, 1, 20, 10) { tim_scope(~1, 1, 20, 10) {
tim_scope(0, 0, 10, 5) { tim_scope(0, 0, 10, 5) {
tim_frame(0, 0, ~0, ~0, 0x9); tim_frame(0, 0, ~0, ~0, (TimStyle){ .brd = TimColor16_Red });
} }
tim_scope(~0, 0, 10, 5) { tim_scope(~0, 0, 10, 5) {
tim_frame(0, 0, ~0, ~0, 0xa); tim_frame(0, 0, ~0, ~0, (TimStyle){ .brd = TimColor16_Green });
} }
tim_scope(~0, ~0, 10, 5) { tim_scope(~0, ~0, 10, 5) {
tim_frame(0, 0, ~0, ~0, 0xb); tim_frame(0, 0, ~0, ~0, (TimStyle){ .brd = TimColor16_Yellow });
} }
tim_scope(0, ~0, 10, 5) { tim_scope(0, ~0, 10, 5) {
tim_frame(0, 0, ~0, ~0, 0xc); tim_frame(0, 0, ~0, ~0, (TimStyle){ .brd = TimColor16_Blue });
} }
} }
// funny characters // funny characters
static TimStyle style_funny = { .bg = TimColor16_White, .fg = TimColor16_Magenta };
tim_scope(~1, ~3, 11, 5) { tim_scope(~1, ~3, 11, 5) {
tim_frame(0, 0, ~0, ~0, 0xf); tim_frame(0, 0, ~0, ~0, style_default);
tim_label("123456789", 1, 1, 9, A, 0x0f05); tim_label("123456789", 1, 1, 9, A, style_funny);
tim_label("$£ह€𐍈6789", 1, 2, A, A, 0x0f05); tim_label("$£ह€𐍈6789", 1, 2, A, A, style_funny);
tim_label("圍棋56789", 1, 3, A, A, 0x0f05); tim_label("圍棋56789", 1, 3, A, A, style_funny);
} }
} }
i32 main(void) { i32 main(void) {
char ed1_buf[32];
char ed2_buf[32];
TimEditState_construct(&ed1, ed1_buf, ARRAY_SIZE(ed1_buf), "Edit 1");
TimEditState_construct(&ed2, ed2_buf, ARRAY_SIZE(ed2_buf), NULL);
while (tim_run(1.5)) { while (tim_run(1.5)) {
test_screen(&tim->event); test_screen(&tim->event);
if (tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) { if (tim_is_key_press('q') || tim_is_key_press(TimKey_Escape)) {
break; break;
} }
} }
return 0;
} }

View File

@@ -66,4 +66,5 @@ i32 main(i32 argc, char** argv) {
tim_reset_terminal(); tim_reset_terminal();
fclose(f); fclose(f);
return 0;
} }