From 717c049265f4bbdb723fcb4704dea8ee7d93dcf8 Mon Sep 17 00:00:00 2001 From: Timerix Date: Mon, 12 Jan 2026 21:54:15 +0500 Subject: [PATCH] added mouse wheel support --- .vscode/launch.json | 34 ++++++++++++++++++++++++++++++++++ .vscode/tasks.json | 31 +++++++++++++++++++++++++++++++ include/tim.h | 8 +++++++- project.config | 14 ++++++++------ src/event.c | 4 ---- src/unix.c | 38 ++++++++++++++++++++++++++++---------- src/windows.c | 15 +++++++++++---- test/test.c | 34 ++++++++++++++++++++++------------ 8 files changed, 141 insertions(+), 37 deletions(-) create mode 100755 .vscode/launch.json create mode 100755 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 0000000..e3d7e25 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,34 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "(gdb) test | Build and debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/test", + "windows": { "program": "${workspaceFolder}/bin/test.exe" }, + "cwd": "${workspaceFolder}/bin", + "preLaunchTask": "build_exec_dbg", + + "stopAtEntry": false, + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + }, + { + "name": "(gdb) test | Just debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/test", + "windows": { "program": "${workspaceFolder}/bin/test.exe" }, + "cwd": "${workspaceFolder}/bin", + + "stopAtEntry": false, + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100755 index 0000000..be19258 --- /dev/null +++ b/.vscode/tasks.json @@ -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 + } + } + ] + } \ No newline at end of file diff --git a/include/tim.h b/include/tim.h index a271903..ccc3273 100644 --- a/include/tim.h +++ b/include/tim.h @@ -129,6 +129,8 @@ typedef enum TimEventType { enum { TimKey_MouseButtonLeft = 1, + TimKey_MouseScrollUp = 4, + TimKey_MouseScrollDown = 5, TimKey_Backspace = 8, TimKey_Tab = 9, TimKey_Enter = 13, @@ -307,7 +309,11 @@ void TimEditState_delete(TimEditState* e); bool tim_is_event_key(TimEventType type, TimKey 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 bool tim_is_mouse_over(TimRect r); diff --git a/project.config b/project.config index d650b0f..31edf0e 100644 --- a/project.config +++ b/project.config @@ -60,6 +60,8 @@ case "$OS" in ;; esac +TEST_C_ARGS="-O0 -g3" + # TASKS case "$TASK" in # ./ask question? @@ -67,7 +69,7 @@ case "$TASK" in EXEC_FILE="ask$EXEC_EXT" SRC_C="example/ask.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" @@ -79,7 +81,7 @@ case "$TASK" in EXEC_FILE="hello$EXEC_EXT" SRC_C="example/hello.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" @@ -91,7 +93,7 @@ case "$TASK" in EXEC_FILE="snek$EXEC_EXT" SRC_C="example/snek.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" @@ -103,7 +105,7 @@ case "$TASK" in EXEC_FILE="test$EXEC_EXT" SRC_C="test/test.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" @@ -115,7 +117,7 @@ case "$TASK" in EXEC_FILE="color$EXEC_EXT" SRC_C="test/color.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" @@ -127,7 +129,7 @@ case "$TASK" in EXEC_FILE="string$EXEC_EXT" SRC_C="test/string.c" LINKER_LIBS="bin/tim.a" - C_ARGS="-O0 -g" + C_ARGS="$TEST_C_ARGS" CPP_ARGS="$C_ARGS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" PRE_TASK_SCRIPT="" diff --git a/src/event.c b/src/event.c index 7804edf..e6eac11 100755 --- a/src/event.c +++ b/src/event.c @@ -4,10 +4,6 @@ bool tim_is_event_key(TimEventType type, TimKey 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) { i32 x = tim->event.x; i32 y = tim->event.y; diff --git a/src/unix.c b/src/unix.c index 236b4d1..be67b62 100755 --- a/src/unix.c +++ b/src/unix.c @@ -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" #ifdef TIM_UNIX @@ -62,7 +71,7 @@ void tim_reset_terminal(void) { } // 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; if (n == 1 || s[0] != 27) { @@ -76,14 +85,23 @@ bool parse_input(event* restrict e, i32 n) { // sgr mouse sequence e->type = TimEvent_Mouse; i32 btn = strtol(s + 3, &s, 10); - e->x = strtol(s + 1, &s, 10) - 1; - e->y = strtol(s + 1, &s, 10) - 1; - if (btn == 0 && s[0] == 'M') { - // left button pressed - e->key = TimKey_MouseButtonLeft; - return true; - } - return false; + e->x = strtol(s + 1, &s, 10); + e->y = strtol(s + 1, &s, 10); + // coordinates start from 1 + if(e->x > 0) e->x--; + if(e->y > 0) e->y--; + // invalid sequence end + if (s[0] != 'M') + 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[] = { @@ -120,7 +138,7 @@ bool parse_input(event* restrict e, i32 n) { } void tim_read_event(i32 timeout_ms) { - event* e = &tim->event; + TimEvent* e = &tim->event; struct pollfd pfd[2] = { {.fd = tim->signal_pipe[0], .events = POLLIN}, diff --git a/src/windows.c b/src/windows.c index cc5fc10..a99be08 100755 --- a/src/windows.c +++ b/src/windows.c @@ -140,9 +140,16 @@ void tim_read_event(i32 timeout_ms) { } 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 left = rec.Event.MouseEvent.dwButtonState & - FROM_LEFT_1ST_BUTTON_PRESSED; + bool left = rec.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED; if (move || !left) { // ignore move events and buttons other than left continue; @@ -150,8 +157,8 @@ void tim_read_event(i32 timeout_ms) { tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT e->type = TimEvent_Mouse; e->key = TimKey_MouseButtonLeft; - e->x = rec.Event.MouseEvent.dwMousePosition.X - tim->window.Left; - e->y = rec.Event.MouseEvent.dwMousePosition.Y - tim->window.Top; + e->x = rec.Event.MouseEvent.dwMousePosition.X - tim->window.Left; + e->y = rec.Event.MouseEvent.dwMousePosition.Y - tim->window.Top; return; } diff --git a/test/test.c b/test/test.c index 1baeb77..d6a8887 100644 --- a/test/test.c +++ b/test/test.c @@ -38,6 +38,16 @@ static inline void test_screen(TimEvent* e) { 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]); 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 render_us += tim->render_us; @@ -52,7 +62,7 @@ static inline void test_screen(TimEvent* e) { tim_label("multi\nliñe\nlabël", 24, 1, A, A, style_default); // colors - tim_scope(1, 5, 16, 5) { + tim_scope(1, 6, 16, 5) { tim_frame(0, 0, ~0, ~0, style_default); tim_label(" Red ", 1, 1, 7, A, (TimStyle){ .bg = 0x09 }); tim_label(" ", 8, 1, 7, A, (TimStyle){ .bg = 0xc4 }); @@ -64,30 +74,30 @@ static inline void test_screen(TimEvent* e) { // button static TimStyle style_button = { .bg = 0x01 }; - if (tim_button("Click Me", 17, 5, 16, 5, style_button)) { + if (tim_button("Click Me", 17, 6, 16, 5, style_button)) { style_button.bg = (style_button.bg + 1) & 0xff; } // edit static TimStyle style_edit = { .brd = 0xff, .fg = 0xff }; - tim_edit(&ed1, 1, 10, 32, style_edit); + tim_edit(&ed1, 1, 11, 32, style_edit); sprintf(buf, "cursor: %d length: %d", ed1.cursor, ed1.length); - tim_label(buf, 2, 13, A, A, style_default); - tim_edit(&ed2, 1, 14, 32, style_edit); - tim_label(ed2.s, 2, 17, A, A, style_default); + tim_label(buf, 2, 12, A, A, style_default); + tim_edit(&ed2, 1, 15, 32, style_edit); + tim_label(ed2.s, 2, 18, A, A, style_default); // checkbox static TimStyle style_checkbox = { .brd = TimColor16_Cyan, .fg = TimColor16_White }; static i32 chk[2] = {-1, 1}; - tim_checkbox("Check 1", &chk[0], 1, 18, A, style_checkbox); - tim_checkbox("Check 2", &chk[1], 14, 18, A, style_checkbox); + tim_checkbox("Check 1", &chk[0], 1, 19, A, style_checkbox); + tim_checkbox("Check 2", &chk[1], 14, 19, A, style_checkbox); // radiobox static i32 rad = 0; - tim_radiobutton("Radio 1", &rad, 1, 1, 19, A, style_checkbox); - tim_radiobutton("Radio 2", &rad, 2, 14, 19, A, style_checkbox); - tim_radiobutton("Radio 3", &rad, 3, 1, 20, A, style_checkbox); - tim_radiobutton("Radio 4", &rad, 4, 14, 20, A, style_checkbox); + tim_radiobutton("Radio 1", &rad, 1, 1, 20, A, style_checkbox); + tim_radiobutton("Radio 2", &rad, 2, 14, 20, A, style_checkbox); + tim_radiobutton("Radio 3", &rad, 3, 1, 21, A, style_checkbox); + tim_radiobutton("Radio 4", &rad, 4, 14, 21, A, style_checkbox); // scope nesting tim_scope(~1, 1, 20, 10) {