#pragma once #pragma region include #include #include #include #include #include #include #include #ifdef _WIN32 // windows #define TIM_WINDOWS #define _CRT_SECURE_NO_WARNINGS #define WIN32_LEAN_AND_MEAN #include #ifdef _MSC_VER // disable integer conversion warnings #pragma warning(disable:4244) #endif #else // unix #define TIM_UNIX #include #include #include #include #endif typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef float f32; typedef double f64; typedef const char* cstr; #if !defined(__cplusplus) && !defined(bool) # define bool u8 # define false 0 # define true 1 #endif #ifdef __cplusplus extern "C" { #endif #pragma endregion #pragma region constants #define TIM_ENABLE_DBUF 1 // double buffering #define TIM_MAX_SCOPE 32 // max scope nesting #define TIM_MAX_CELLS 0x20000 // size of screen buffer #define TIM_MAX_BUF (TIM_MAX_CELLS * 4) // size of output buffer #define A INT_MAX // auto center / width / height #pragma endregion #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 { u8 fg; // foreground color u8 bg; // background color u8 wide; // wide or following wide character u8 n; // number of bytes in buf u8 buf[4]; // utf8 code point } TimCell; typedef struct TimRect { i32 x; // x coordinate (left = 0) i32 y; // y coordinate (top = 0) i32 w; // width i32 h; // height } TimRect; typedef struct TimText { i32 size; // size in bytes without terminator i32 width; // widest line i32 lines; // number of lines } TimText; typedef struct TimLine { cstr s; // input and parse state cstr line; // line strings, not terminated i32 size; // line size in bytes i32 width; // line width in glyph } 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 { TimEventType type; TimKey key; // used by TimEvent_Key and TimEvent_Mouse i32 x; // used by TimEvent_Mouse i32 y; // used by TimEvent_Mouse char s[32]; // string representation of key, used by TimEvent_Key } TimEvent; typedef struct TimEditState { bool masked; // if true prints '*' instead of buffer content i32 cursor; // cursor position (utf8) i32 length; // string length (utf8) i32 capacity; // buffer size in bytes char* s; // zero terminated buffer } TimEditState; typedef struct TimScrollItem { void* data; void* focus_target; // is assigned to tim->focus i32 h; // height of the item to know where to draw next item void (*draw)(bool is_selected, TimRect place, void* data); } TimScrollItem; typedef struct TimScrollState { TimScrollItem* items; i32 len; i32 cur; bool draw_border; bool show_scroll_bar_vertical; bool use_mouse_wheel; } TimScrollState; typedef struct TimState { i32 w; // screen width i32 h; // screen height i32 frame; // frame counter TimEvent event; // current event void* focus; // focused element i32 loop_stage; // loop stage bool resized; // screen was resized i32 scope; // current scope TimRect scopes[TIM_MAX_SCOPE]; // scope stack TimCell* cells; // current screen buffer (first or second half of cells_double_buf) TimCell* cells_double_buf; // pointer to double buffer char* buf; // final output buffer i32 buf_size; // position in write buffer i64 start_us; // render start time i32 render_us; // elapsed render time #ifdef TIM_UNIX struct termios attr; // initial attributes i32 signal_pipe[2]; // signal fifo pipe #endif #ifdef TIM_WINDOWS SMALL_RECT window; // screen buffer window size DWORD mode_in; // initial input mode DWORD mode_out; // initial output mode UINT cp_in; // initial input code page UINT cp_out; // initial output code page #endif } TimState; #pragma endregion #pragma region macros #define MAX(a, b) ((a) > (b) ? (a) : (b)) // #define MIN(a, b) ((a) < (b) ? (a) : (b)) // #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) // number of items in array #define S(s) ("" s), (sizeof(s) - 1) // expand to s, sizeof(s) - 1 #pragma endregion #pragma region general // global statem initialized by tim_run() extern TimState* tim; bool tim_run(f32 fps); void tim_reset_terminal(void); i64 tim_time_usec(void); #pragma endregion #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 #define tim_scope(x, y, w, h) for (\ 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 TimRect tim_scope_rect_to_absolute(i32 x, i32 y, i32 w, i32 h); // enter scope and push coordinates on stack i32 tim_enter_scope(i32 x, i32 y, i32 w, i32 h); // exit scope and pop stack i32 tim_exit_scope(void); static inline bool tim_rect_does_fit(TimRect r){ return ( r.x >= 0 && r.y >= 0 && r.w >= 0 && r.h >= 0 && r.x + r.w <= tim->w && r.y + r.h <= tim->h ); } #pragma endregion #pragma region widgets // frame // color: background, frame void tim_frame(i32 x, i32 y, i32 w, i32 h, TimStyle style); // text label // str : text - supports multiple lines // color: background, text void tim_label(cstr s, i32 x, i32 y, i32 w, i32 h, TimStyle style); // button with border - returns true on click // color: frame, background, text 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 // txt : text label // state: persistent state, 0 unchecked, -1 semi checked, !0: checked // color: check, background, text bool tim_checkbox(cstr txt, i32* state, i32 x, i32 y, i32 w, TimStyle style); // radio button - return true when clicked // txt : text label // state: persistent state, selected if *state == v // v : value // color: radio, background, text bool tim_radiobutton(cstr txt, i32* state, i32 v, i32 x, i32 y, i32 w, TimStyle style); /// text edit - value in state /// @param e persistent edit state, use TimEditState_construct() to create new state /// @param style frame, background, text /// @return key id or 0 TimKey tim_edit(TimEditState* e, i32 x, i32 y, i32 w, TimStyle style); /// @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_delete(TimEditState* e); /// @param l list of rows to display /// @param style frame, background, text TimScrollItem* tim_scroll(TimScrollState* l, i32 x, i32 y, i32 w, i32 h, TimStyle style); void TimScrollState_selectNext(TimScrollState* l); void TimScrollState_selectPrev(TimScrollState* l); #pragma endregion #pragma region event // returns true if event was of type and key bool tim_is_event_key(TimEventType type, TimKey key); // returns true if event was press of 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 bool tim_is_mouse_over(TimRect r); // returns true if event is mouse left-down and over r bool tim_is_mouse_click_over(TimRect r); static inline void tim_event_consume(){ tim->event.type = TimEvent_Void; } #pragma endregion #pragma region drawing // create cell from utf8 code point with fg and bg colors TimCell tim_cell(cstr s, u8 fg, u8 bg); // clear cell buffer void tim_clear_cells(void); // draw cell at position void tim_draw_chr(TimCell cell, i32 x, i32 y); // draw row of cells void tim_draw_row(TimCell cell, i32 x, i32 y, i32 w); // draw column of cells void tim_draw_col(TimCell cell, i32 x, i32 y, i32 h); // fill area with cells void tim_fill(TimCell cell, i32 x, i32 y, i32 w, i32 h); // draw string to line, tags potential wide characters void tim_draw_str(cstr s, i32 x, i32 y, i32 w, u8 fg, u8 bg); // draw box of ascii cell characters void tim_draw_box(i32 x, i32 y, i32 w, i32 h, u8 fg, u8 bg); // invert fg and bg colors of line of cells void tim_draw_invert(i32 x, i32 y, i32 w); #pragma endregion #pragma region string // like strlen, returns 0 on NULL or i32 overflow i32 tim_ztrlen(cstr s); // decode one utf8 code point i32 tim_utf8_to_i32(cstr s); // number of utf8 code points i32 tim_utf8_len(cstr s); // index of utf8 code point at pos i32 tim_utf8_pos(cstr s, i32 pos); // true if utf8 code point could be wide bool tim_utf8_is_wide_perhaps(const u8* s, i32 n); // bit scan reverse, count leading zeros i32 tim_bsr8(u8 x); // scan string for width and lines TimText tim_scan_str(cstr s); // iterate through lines, false when end is reached bool tim_next_line(TimLine* l); #pragma endregion #pragma region internal void tim_write_str(cstr s, i32 size); void tim_update_screen_size(void); void tim_init_terminal(void); void tim_read_event(i32 timeout_ms); #pragma endregion #ifdef __cplusplus } #endif