summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/tui.c496
-rw-r--r--src/ui/tui.h135
2 files changed, 631 insertions, 0 deletions
diff --git a/src/ui/tui.c b/src/ui/tui.c
new file mode 100644
index 0000000..8ed59ce
--- /dev/null
+++ b/src/ui/tui.c
@@ -0,0 +1,496 @@
+#include "tui.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <unistd.h>
+
+void _tui_init_callbacks(TUI *tui) {
+ const int width = tui_get_width(tui);
+ const int height = tui_get_height(tui);
+
+ tui->on_click_callbacks = malloc(width * sizeof(ON_CLICK_CALLBACK *));
+ for (int i = 0; i < width; ++i) {
+ tui->on_click_callbacks[i] = malloc(height * sizeof(ON_CLICK_CALLBACK));
+
+ for (int j = 0; j < height; ++j) {
+ tui->on_click_callbacks[i][j] = NULL;
+ }
+ }
+}
+
+void _tui_delete_callbacks(TUI *tui, int width) {
+ for (int i = 0; i < width; ++i) {
+ free(tui->on_click_callbacks[i]);
+ }
+
+ free(tui->on_click_callbacks);
+}
+
+TUI *tui_init() {
+ setbuf(stdout, NULL);
+
+ TUI *tui = malloc(sizeof(TUI));
+ tui->buffer = malloc(0);
+
+ tui_get_cursor_pos(tui, &tui->init_cursor_x, &tui->init_cursor_y);
+
+ // Save original serial communication configuration for stdin
+ tcgetattr(STDIN_FILENO, &tui->original);
+
+ // Put stdin in raw mode so keys get through directly without
+ // requiring pressing enter.
+ cfmakeraw(&tui->raw);
+ tcsetattr(STDIN_FILENO, TCSANOW, &tui->raw);
+
+ // Switch to the alternate buffer screen
+ write(STDOUT_FILENO, "\e[?47h", 6);
+
+ // Enable mouse tracking
+ write(STDOUT_FILENO, "\e[?9h", 5);
+
+ _tui_init_callbacks(tui);
+
+ tui_refresh(tui);
+ return tui;
+}
+
+void tui_delete(TUI *tui) {
+ const int width = tui_get_width(tui);
+
+ // Revert the terminal back to its original state
+ write(STDOUT_FILENO, "\e[?9l", 5);
+ write(STDOUT_FILENO, "\e[?47l", 6);
+ tcsetattr(STDIN_FILENO, TCSANOW, &tui->original);
+
+ tui_move_to(tui->init_cursor_x, tui->init_cursor_y);
+
+ _tui_delete_callbacks(tui, width);
+ free(tui);
+}
+
+void tui_refresh(TUI *tui) {
+ const int width = tui_get_width(tui);
+ const int height = tui_get_height(tui);
+
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &tui->size);
+
+ if (width == tui_get_width(tui) && height == tui_get_height(tui)) {
+ return;
+ }
+ _tui_delete_callbacks(tui, width);
+ _tui_init_callbacks(tui);
+}
+
+int tui_get_width(TUI *tui) { return tui->size.ws_col; }
+
+int tui_get_height(TUI *tui) { return tui->size.ws_row; }
+
+int tui_clear_screen() { return printf("\033[2J\r"); }
+
+int tui_move_top(int n) { return printf("\033[%dA", n); }
+
+int tui_move_up(int n) { return printf("\033[%dB", n); }
+
+int tui_move_right(int n) { return printf("\033[%dC", n); }
+
+int tui_move_left(int n) { return printf("\033[%dD", n); }
+
+int tui_save_cursor() { return printf("\0337"); }
+
+int tui_restore_cursor() { return printf("\0338"); }
+
+void tui_get_cursor_pos(TUI *tui, int *x, int *y) {
+ char buf[8];
+ char cmd[] = "\033[6n";
+ tcgetattr(0, &tui->raw);
+ cfmakeraw(&tui->helper);
+ tcsetattr(0, TCSANOW, &tui->helper);
+ if (isatty(fileno(stdin))) {
+ write(STDOUT_FILENO, cmd, sizeof(cmd));
+ read(STDIN_FILENO, buf, sizeof(buf));
+
+ sscanf(buf, "\033[%d;%dR", y, x);
+ --x;
+ --y;
+ }
+ tcsetattr(0, TCSANOW, &tui->raw);
+}
+
+int tui_move_to(int x, int y) { return printf("\033[%d;%dH", y + 1, x + 1); }
+
+int tui_delete_before() { return printf("\b \b"); }
+
+int tui_delete_under_cursor() { return printf(" \b"); }
+
+void tui_set_on_click_callback(TUI *tui, int x, int y,
+ ON_CLICK_CALLBACK callback) {
+ tui->on_click_callbacks[x][y] = callback;
+}
+
+void tui_handle_mouse_action(TUI *tui, MOUSE_ACTION mouse_action) {
+ const ON_CLICK_CALLBACK callback =
+ tui->on_click_callbacks[mouse_action.x][mouse_action.y];
+ if (callback != NULL) {
+ callback(mouse_action);
+ }
+}
+
+int tui_change_terminal_text_color(COLOR color) {
+ if (color == COLOR_NO_COLOR) {
+ return 0;
+ } else if (color == COLOR_RESET) {
+ return printf("\033[%dm", COLOR_RESET);
+ }
+ return printf("\033[%dm", color + 30);
+}
+
+int tui_change_terminal_background_color(COLOR color) {
+ if (color == COLOR_NO_COLOR) {
+ return 0;
+ } else if (color == COLOR_RESET) {
+ return printf("\033[%dm", COLOR_RESET);
+ }
+ return printf("\033[%dm", color + 40);
+}
+
+int handle_input(TUI *tui) {
+ unsigned char buff[6];
+ read(STDIN_FILENO, &buff, 1);
+ if (buff[0] == 3) { // User pressd Ctr+C
+ return 1;
+ } else if (buff[0] == '\x1B') { // [ESCAPE]
+ // TODO: fix for inputting actual <ESC>
+ read(STDIN_FILENO, &buff, 5);
+ switch (buff[1]) {
+ case 77: {
+ MOUSE_ACTION mouse_action = {
+ .button = buff[2],
+ .x = buff[3] - 32 - 1, // starts at 0
+ .y = buff[4] - 32 - 1, // starts at 0
+ };
+ tui_handle_mouse_action(tui, mouse_action);
+ /*printf("button:%u\n\rx:%u\n\ry:%u\n\n\r", mouse_action.button,*/
+ /* mouse_action.x, mouse_action.y);*/
+ } break;
+ }
+ } else {
+ switch (buff[0]) {
+ case 'h':
+ tui_move_left(1);
+ break;
+ case 'j':
+ tui_move_up(1);
+ break;
+ case 'k':
+ tui_move_top(1);
+ break;
+ case 'l':
+ tui_move_right(1);
+ break;
+ case 'c':
+ tui_clear_screen();
+ break;
+ case 's':
+ tui_save_cursor();
+ break;
+ case 'r':
+ tui_restore_cursor();
+ break;
+ case '\b':
+ case 127: // back space
+ tui_delete_before();
+ break;
+ default:
+ /*printf("unknown:%c,%d\n\r", buff[0], buff[0]);*/
+ break;
+ }
+ }
+ return 0;
+}
+
+void tui_start_app(TUI *tui, WIDGET_BUILDER widget_builder) {
+ tui_main_loop(tui, widget_builder);
+}
+
+void _tui_draw_widget(TUI *tui, const WIDGET *widget, int width_begin,
+ int width_end, int height_begin, int height_end,
+ COLOR parent_color, int *child_width, int *child_height) {
+ switch (widget->type) {
+ case WIDGET_TYPE_TEXT: {
+ const TEXT_METADATA *metadata = widget->metadata;
+ const int widthDiff = width_end - width_begin;
+ const int textLen = strlen(metadata->text);
+ int height = height_begin;
+ for (; height < height_end; ++height) {
+ tui_move_to(width_begin, height);
+ const int begin = (height - height_begin) * widthDiff;
+ int end = begin + widthDiff;
+ int shouldExit = 0;
+ if (end > textLen) {
+ end = textLen;
+ shouldExit = 1;
+ }
+
+ tui_change_terminal_text_color(metadata->color);
+ tui_change_terminal_background_color(parent_color);
+ write(STDOUT_FILENO, metadata->text + begin, end - begin);
+ tui_change_terminal_background_color(COLOR_RESET);
+ tui_change_terminal_text_color(COLOR_RESET);
+ if (shouldExit) {
+ break;
+ }
+ }
+ *child_height = height + 1;
+ *child_width =
+ (textLen > widthDiff ? width_end : textLen + width_begin) + 1;
+ } break;
+ case WIDGET_TYPE_BUTTON: {
+ const BUTTON_METADATA *metadata = widget->metadata;
+ tui_move_to(width_begin, height_begin);
+ if (metadata->child != NULL) {
+ _tui_draw_widget(tui, metadata->child, width_begin, width_end,
+ height_begin, height_end, parent_color, child_width,
+ child_height);
+ /*printf("%d,%d\n\r",*child_width,*child_height);*/
+ /*sleep(1);*/
+ for (int i = width_begin; i < *child_width; ++i) {
+ for (int j = height_begin; j < *child_height; ++j) {
+ tui_set_on_click_callback(tui, i, j, metadata->callback);
+ }
+ }
+ }
+ } break;
+ case WIDGET_TYPE_COLUMN: {
+ const COLUMN_METADATA *metadata = widget->metadata;
+ *child_width = width_begin;
+ *child_height = height_begin;
+ for (int i = 0; i < metadata->children->size; ++i) {
+ const WIDGET *child = metadata->children->widgets[i];
+ int width_temp;
+ _tui_draw_widget(tui, child, width_begin, width_end, *child_height,
+ height_end, parent_color, &width_temp, child_height);
+ if (width_temp > *child_width) {
+ *child_width = width_temp;
+ }
+ }
+ } break;
+ case WIDGET_TYPE_ROW: {
+ const ROW_METADATA *metadata = widget->metadata;
+ *child_width = width_begin;
+ *child_height = height_begin;
+ for (int i = 0; i < metadata->children->size; ++i) {
+ const WIDGET *child = metadata->children->widgets[i];
+ int height_temp;
+ _tui_draw_widget(tui, child, *child_width, width_end, height_begin,
+ height_end, parent_color, child_width, &height_temp);
+ if (height_temp > *child_height) {
+ *child_height = height_temp;
+ }
+ }
+ } break;
+ case WIDGET_TYPE_BOX: {
+ const BOX_METADATA *metadata = widget->metadata;
+
+ if (metadata->width != -1) {
+ width_end = metadata->width + width_begin >= width_end
+ ? width_end
+ : metadata->width + width_begin;
+ }
+ if (metadata->height != -1) {
+ height_end = metadata->height + height_begin >= height_end
+ ? height_end
+ : metadata->height + height_begin;
+ }
+
+ for (int y = height_begin; y < height_end; ++y) {
+ tui_move_to(width_begin, y);
+ for (int x = width_begin; x < width_end; ++x) {
+ tui_change_terminal_background_color(metadata->color);
+ write(STDOUT_FILENO, " ", 1);
+ tui_change_terminal_background_color(COLOR_RESET);
+ }
+ }
+
+ if (metadata->child != NULL) {
+ int t0, t1;
+ _tui_draw_widget(tui, metadata->child, width_begin, width_end,
+ height_begin, height_end, metadata->color, &t0, &t1);
+ }
+ *child_width = width_end;
+ *child_height = height_end;
+ } break;
+ default:
+ fprintf(stderr, "widget type '%d' went wrong in _tui_draw_widget",
+ widget->type);
+ exit(1);
+ }
+}
+
+void tui_main_loop(TUI *tui, WIDGET_BUILDER widget_builder) {
+ while (1) {
+ clock_t start = clock();
+ tui_refresh(tui);
+ tui_save_cursor();
+ tui_clear_screen();
+ WIDGET *root_widget = widget_builder(tui);
+
+ int width, height;
+ _tui_draw_widget(tui, root_widget, 0, tui_get_width(tui), 0,
+ tui_get_height(tui), COLOR_NO_COLOR, &width, &height);
+
+ tui_delete_widget(root_widget);
+ tui_move_to(30, 30);
+ printf("time:%ld",clock()-start);
+ tui_restore_cursor();
+ if (handle_input(tui)) {
+ return;
+ }
+ }
+}
+
+WIDGET *tui_new_widget(WIDGET_TYPE type, void *metadata) {
+ WIDGET *widget = malloc(sizeof(WIDGET));
+ widget->type = type;
+ widget->metadata = metadata;
+ return widget;
+}
+
+void tui_delete_widget(WIDGET *widget) {
+ switch (widget->type) {
+ case WIDGET_TYPE_TEXT:
+ _tui_delete_text(widget);
+ break;
+ case WIDGET_TYPE_BUTTON:
+ _tui_delete_button(widget);
+ break;
+ case WIDGET_TYPE_COLUMN:
+ _tui_delete_column(widget);
+ break;
+ case WIDGET_TYPE_ROW:
+ _tui_delete_row(widget);
+ break;
+ case WIDGET_TYPE_BOX:
+ _tui_delete_box(widget);
+ break;
+ default:
+ fprintf(stderr, "Type error '%d' in tui_delete_widget\n", widget->type);
+ exit(1);
+ }
+ free(widget);
+}
+
+WIDGET *tui_make_text(char *text, COLOR color) {
+ return tui_new_widget(WIDGET_TYPE_TEXT, _tui_make_text_metadata(text, color));
+}
+
+TEXT_METADATA *_tui_make_text_metadata(char *text, COLOR color) {
+ TEXT_METADATA *metadata = malloc(sizeof(TEXT_METADATA));
+ metadata->text = malloc(strlen(text) + 1);
+ strcpy(metadata->text, text);
+ metadata->color = color;
+ return metadata;
+}
+
+void _tui_delete_text(WIDGET *text) {
+ free(((TEXT_METADATA *)text->metadata)->text);
+ free(text->metadata);
+}
+
+WIDGET *tui_make_button(WIDGET *child, ON_CLICK_CALLBACK callback) {
+ return tui_new_widget(WIDGET_TYPE_BUTTON,
+ _tui_make_button_metadata(child, callback));
+}
+
+BUTTON_METADATA *_tui_make_button_metadata(WIDGET *child,
+ ON_CLICK_CALLBACK callback) {
+ BUTTON_METADATA *metadata = malloc(sizeof(BUTTON_METADATA));
+ metadata->child = child;
+ metadata->callback = callback;
+ return metadata;
+}
+
+void _tui_delete_button(WIDGET *button) {
+ tui_delete_widget(((BUTTON_METADATA *)button->metadata)->child);
+ free(button->metadata);
+}
+
+WIDGET *tui_make_column(WIDGET_ARRAY *children) {
+ return tui_new_widget(WIDGET_TYPE_COLUMN,
+ _tui_make_column_metadata(children));
+}
+
+COLUMN_METADATA *_tui_make_column_metadata(WIDGET_ARRAY *children) {
+ COLUMN_METADATA *metadata = malloc(sizeof(COLUMN_METADATA));
+ metadata->children = children;
+ return metadata;
+}
+
+void _tui_delete_column(WIDGET *column) {
+ COLUMN_METADATA *metadata = column->metadata;
+ for (size_t i = 0; i < metadata->children->size; ++i) {
+ tui_delete_widget(metadata->children->widgets[i]);
+ }
+ free(metadata->children);
+ free(column->metadata);
+}
+
+WIDGET *tui_make_row(WIDGET_ARRAY *children) {
+ return tui_new_widget(WIDGET_TYPE_ROW,
+ _tui_make_row_metadata(children));
+}
+
+ROW_METADATA *_tui_make_row_metadata(WIDGET_ARRAY *children) {
+ ROW_METADATA *metadata = malloc(sizeof(ROW_METADATA));
+ metadata->children = children;
+ return metadata;
+}
+
+void _tui_delete_row(WIDGET *row) {
+ ROW_METADATA *metadata = row->metadata;
+ for (size_t i = 0; i < metadata->children->size; ++i) {
+ tui_delete_widget(metadata->children->widgets[i]);
+ }
+ free(metadata->children);
+ free(row->metadata);
+}
+
+WIDGET *tui_make_box(int width, int height, WIDGET *child, COLOR color) {
+ return tui_new_widget(WIDGET_TYPE_BOX,
+ _tui_make_box_metadata(child, width, height, color));
+}
+
+BOX_METADATA *_tui_make_box_metadata(WIDGET *child, int width, int height,
+ COLOR color) {
+ BOX_METADATA *metadata = malloc(sizeof(BOX_METADATA));
+ metadata->width = width;
+ metadata->height = height;
+ metadata->child = child;
+ metadata->color = color;
+ return metadata;
+}
+
+void _tui_delete_box(WIDGET *box) {
+ tui_delete_widget(((BOX_METADATA *)box->metadata)->child);
+ free(box->metadata);
+}
+
+WIDGET_ARRAY *tui_make_widget_array(int size,...){
+ va_list arg_pointer;
+ va_start(arg_pointer, size);
+
+ WIDGET **widgets = malloc(size*sizeof(WIDGET**));
+
+ for(int i = 0;i < size;++i){
+ widgets[i] = va_arg(arg_pointer,WIDGET*);
+ }
+ va_end(arg_pointer);
+
+ WIDGET_ARRAY *widget_array = malloc(sizeof(WIDGET_ARRAY));
+ widget_array->widgets = widgets;
+ widget_array->size = size;
+
+ return widget_array;
+}
diff --git a/src/ui/tui.h b/src/ui/tui.h
new file mode 100644
index 0000000..a3e64f3
--- /dev/null
+++ b/src/ui/tui.h
@@ -0,0 +1,135 @@
+#ifndef A404M_UI_TUI
+#define A404M_UI_TUI 1
+
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+typedef enum MOUSE_BUTTON {
+ MOUSE_BUTTON_LEFT_CLICK = 32,
+ MOUSE_BUTTON_MIDDLE_CLICK = 33,
+ MOUSE_BUTTON_RIGHT_CLICK = 34,
+ MOUSE_BUTTON_SCROLL_UP = 96,
+ MOUSE_BUTTON_SCROLL_DOWN = 97
+} MOUSE_BUTTON;
+
+typedef struct MOUSE_ACTION {
+ MOUSE_BUTTON button;
+ unsigned int x;
+ unsigned int y;
+} MOUSE_ACTION;
+
+typedef void (*ON_CLICK_CALLBACK)(MOUSE_ACTION mouse_action);
+
+typedef struct TUI {
+ struct winsize size;
+ struct termios original, raw, helper;
+ ON_CLICK_CALLBACK **on_click_callbacks;
+ int init_cursor_x, init_cursor_y;
+ char *buffer;
+} TUI;
+
+typedef enum WIDGET_TYPE {
+ WIDGET_TYPE_TEXT,
+ WIDGET_TYPE_BUTTON,
+ WIDGET_TYPE_COLUMN,
+ WIDGET_TYPE_ROW,
+ WIDGET_TYPE_BOX,
+} WIDGET_TYPE;
+
+typedef struct WIDGET {
+ WIDGET_TYPE type;
+ void *metadata;
+} WIDGET;
+
+typedef enum COLOR {
+ COLOR_NO_COLOR = -1,
+ COLOR_RESET = 0,
+ COLOR_RED = 1,
+ COLOR_GREEN = 2,
+ COLOR_YELLOW = 3,
+ COLOR_BLUE = 4,
+ COLOR_MAGENTA = 5,
+ COLOR_CYAN = 6,
+ COLOR_WHITE = 7
+} COLOR;
+
+typedef struct WIDGET_ARRAY {
+ int size;
+ WIDGET **widgets;
+} WIDGET_ARRAY;
+
+typedef struct TEXT_METADATA {
+ char *text;
+ COLOR color;
+} TEXT_METADATA;
+
+typedef struct BUTTON_METADATA {
+ WIDGET *child;
+ ON_CLICK_CALLBACK callback;
+} BUTTON_METADATA;
+
+typedef struct COLUMN_METADATA {
+ WIDGET_ARRAY *children;
+} COLUMN_METADATA;
+
+typedef struct ROW_METADATA {
+ WIDGET_ARRAY *children;
+} ROW_METADATA;
+
+typedef struct BOX_METADATA {
+ int width;
+ int height;
+ WIDGET *child;
+ COLOR color;
+} BOX_METADATA;
+
+typedef WIDGET *(*WIDGET_BUILDER)(TUI *tui);
+
+extern TUI *tui_init();
+extern void tui_delete(TUI *tui);
+extern void tui_refresh(TUI *tui);
+
+extern int tui_get_width(TUI *tui);
+extern int tui_get_height(TUI *tui);
+
+extern void tui_get_cursor_pos(TUI *tui, int *x, int *y);
+extern int tui_move_to(int x, int y);
+
+extern int tui_clear_screen();
+
+extern void tui_start_app(TUI *tui, WIDGET_BUILDER widget_builder);
+extern void _tui_draw_widget(TUI *tui, const WIDGET *widget, int widthBegin,
+ int widthEnd, int heightBegin, int heightEnd,
+ COLOR parent_color, int *childWidth,
+ int *childHeight);
+extern void tui_main_loop(TUI *tui, WIDGET_BUILDER widget_builder);
+
+extern WIDGET *tui_new_widget(WIDGET_TYPE type, void *metadata);
+extern void tui_delete_widget(WIDGET *widget);
+
+extern WIDGET *tui_make_text(char *text, COLOR color);
+extern TEXT_METADATA *_tui_make_text_metadata(char *text, COLOR color);
+extern void _tui_delete_text(WIDGET *text);
+
+extern WIDGET *tui_make_button(WIDGET *child, ON_CLICK_CALLBACK callback);
+extern BUTTON_METADATA *_tui_make_button_metadata(WIDGET *child,
+ ON_CLICK_CALLBACK callback);
+extern void _tui_delete_button(WIDGET *button);
+
+extern WIDGET *tui_make_column(WIDGET_ARRAY *children);
+extern COLUMN_METADATA *_tui_make_column_metadata(WIDGET_ARRAY *children);
+extern void _tui_delete_column(WIDGET *column);
+
+extern WIDGET *tui_make_row(WIDGET_ARRAY *children);
+extern ROW_METADATA *_tui_make_row_metadata(WIDGET_ARRAY *children);
+extern void _tui_delete_row(WIDGET *row);
+
+extern WIDGET *tui_make_box(int width, int height, WIDGET *child, COLOR color);
+extern BOX_METADATA *_tui_make_box_metadata(WIDGET *child, int width,
+ int height, COLOR color);
+extern void _tui_delete_box(WIDGET *box);
+
+extern WIDGET_ARRAY *tui_make_widget_array(int size, ...);
+
+#endif