diff options
| author | A404M <ahmadmahmoudiprogrammer@gmail.com> | 2024-08-28 00:18:53 +0330 | 
|---|---|---|
| committer | A404M <ahmadmahmoudiprogrammer@gmail.com> | 2024-08-28 00:18:53 +0330 | 
| commit | e9d373a154f538ef316940a142f652dba1a9bea6 (patch) | |
| tree | 1330afde6a7e695e6e334f12483c732eca90314e | |
initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rwxr-xr-x | compile | 12 | ||||
| -rwxr-xr-x | run | 7 | ||||
| -rw-r--r-- | src/main.c | 32 | ||||
| -rw-r--r-- | src/ui/tui.c | 496 | ||||
| -rw-r--r-- | src/ui/tui.h | 135 | 
6 files changed, 683 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ @@ -0,0 +1,12 @@ +#!/bin/bash + +project_name="atui" + +if [ ! -d build ]; then +	if [ $(mkdir build) ]; then # if error +		echo "cannot make 'build' dir" +		exit +	fi +fi + +gcc -Wall -Wextra -O3 src/main.c src/ui/tui.c -o "build/$project_name" @@ -0,0 +1,7 @@ +#!/bin/bash + +project_name="atui" + +./compile && "./build/$project_name" "$@" +echo +echo "$?" diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b7a97d1 --- /dev/null +++ b/src/main.c @@ -0,0 +1,32 @@ +#include "ui/tui.h" +#include <unistd.h> +#include <stdio.h> + +void on_button_click(MOUSE_ACTION mouse_action) { +  printf("hello"); +  sleep(1); +} + +WIDGET *ui_build(TUI *tui) { +  return tui_make_box( +      -1, -1, +      tui_make_row(tui_make_widget_array( +          2, +          tui_make_box( +              8, 2, +              tui_make_button(tui_make_text("Hello, World!", COLOR_BLUE), +                              on_button_click), +              COLOR_YELLOW), +          tui_make_text("Hello, World!", COLOR_RED), 1)), +      COLOR_MAGENTA); +} + +int main() { +  TUI *tui = tui_init(); + +  tui_start_app(tui, ui_build); + +  tui_delete(tui); + +  return 0; +} 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  |