add a semi-functional TUI
This commit is contained in:
2
Makefile
2
Makefile
@@ -13,4 +13,4 @@ clean:
|
|||||||
rm -f *.o
|
rm -f *.o
|
||||||
|
|
||||||
all: todo.o main.o
|
all: todo.o main.o
|
||||||
$(CC) $(CFLAGS) todo.o main.o -o todd
|
$(CC) $(CFLAGS) todo.o main.o -lncurses -o todd
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ To build `todd` all you need to do is run `make`.
|
|||||||
## 3rd Party
|
## 3rd Party
|
||||||
Todd uses the following dependencies:
|
Todd uses the following dependencies:
|
||||||
- stb_ds - https://github.com/nothings/stb
|
- stb_ds - https://github.com/nothings/stb
|
||||||
|
- ncurses - https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html
|
||||||
|
|||||||
289
main.c
289
main.c
@@ -3,68 +3,206 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
#define MAX_TODOS UINT_MAX
|
#define MAX_TODOS UINT_MAX
|
||||||
#define SAVE_FILE "todos.todd"
|
#define MAX_PATH_LENGTH 1024
|
||||||
#define STB_DS_IMPLEMENTATION
|
#define STB_DS_IMPLEMENTATION
|
||||||
|
|
||||||
|
#define BORDERS_PAIR 1
|
||||||
|
#define DEFAULT_PAIR 2
|
||||||
|
#define COMPLETED_PAIR 3
|
||||||
|
|
||||||
#include "3rd-party/stb-ds.h"
|
#include "3rd-party/stb-ds.h"
|
||||||
|
|
||||||
|
char SAVE_FILE[MAX_PATH_LENGTH] = "todos.todd";
|
||||||
TodoItem *todos = NULL;
|
TodoItem *todos = NULL;
|
||||||
|
|
||||||
enum Command {
|
enum Command {
|
||||||
ADD = 'a',
|
ADD = 'a',
|
||||||
MARK = 'm',
|
MARK = 'm',
|
||||||
PRINT = 'p',
|
PRINT = 'p',
|
||||||
REMOVE = 'r',
|
REMOVE = 'd',
|
||||||
QUIT = 'q',
|
QUIT = 'q',
|
||||||
|
|
||||||
WRITE_TO_FILE = 'w',
|
WRITE_TO_FILE = 'w',
|
||||||
LOAD_FROM_FILE = 'l',
|
LOAD_FROM_FILE = 'l',
|
||||||
|
|
||||||
|
UP = 'k',
|
||||||
|
DOWN = 'j',
|
||||||
};
|
};
|
||||||
|
|
||||||
void add_command_handler(void) {
|
void clear_todos(void) {
|
||||||
|
for (int i = 0; i < arrlen(todos); i++) {
|
||||||
|
todo_free_item(&todos[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(todos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize_curses(int *width, int *height) {
|
||||||
|
initscr();
|
||||||
|
curs_set(0);
|
||||||
|
int tw, th;
|
||||||
|
getmaxyx(stdscr, th, tw);
|
||||||
|
|
||||||
|
*width = tw;
|
||||||
|
*height = th;
|
||||||
|
|
||||||
|
start_color();
|
||||||
|
init_pair(BORDERS_PAIR, COLOR_BLACK, COLOR_WHITE);
|
||||||
|
init_pair(DEFAULT_PAIR, COLOR_WHITE, COLOR_BLACK);
|
||||||
|
init_pair(COMPLETED_PAIR, COLOR_BLACK, COLOR_GREEN);
|
||||||
|
// turn off echoing of keys, and enter cbreak mode,
|
||||||
|
// where no buffering is performed on keyboard input
|
||||||
|
cbreak();
|
||||||
|
noecho();
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_header(int width) {
|
||||||
|
attron(COLOR_PAIR(BORDERS_PAIR));
|
||||||
|
move(0, 0);
|
||||||
|
addstr(" Todd");
|
||||||
|
for (int i = strlen("Todd"); i < width - 1; i++) {
|
||||||
|
addch(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw_footer(int width, int height) {
|
||||||
|
move(height - 1, 0);
|
||||||
|
for (int i = 0; i < width - 1; i++) {
|
||||||
|
addch(' ');
|
||||||
|
}
|
||||||
|
attroff(COLOR_PAIR(BORDERS_PAIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
void alert(const char *message, int terminal_width, int terminal_height) {
|
||||||
|
int window_height = 5;
|
||||||
|
int window_width = terminal_width - 4;
|
||||||
|
int window_y = terminal_height / 2 - 1;
|
||||||
|
int window_x = 2;
|
||||||
|
|
||||||
|
int message_x = (terminal_width - 4 - strlen(message)) / 2;
|
||||||
|
int message_y = 2;
|
||||||
|
|
||||||
|
// create a new window
|
||||||
|
WINDOW *alert_window = newwin(window_height, window_width, window_y, window_x);
|
||||||
|
// draw a border around the window
|
||||||
|
box(alert_window, 0, 0);
|
||||||
|
// print the message in the middle of the window
|
||||||
|
mvwprintw(alert_window, message_y, message_x, message);
|
||||||
|
// refresh the window
|
||||||
|
wrefresh(alert_window);
|
||||||
|
// wait for a key press
|
||||||
|
getch();
|
||||||
|
|
||||||
|
// delete the window
|
||||||
|
delwin(alert_window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_string(int terminal_width, int terminal_height, char *buffer, int buffer_size) {
|
||||||
|
int window_height = 5;
|
||||||
|
int window_width = terminal_width - 4;
|
||||||
|
int window_y = terminal_height / 2 - 1;
|
||||||
|
int window_x = 2;
|
||||||
|
|
||||||
|
int message_x = strlen("Enter todo title: ") + 1;
|
||||||
|
int message_y = 2;
|
||||||
|
|
||||||
|
curs_set(1);
|
||||||
|
// create a new window
|
||||||
|
WINDOW *input_window = newwin(window_height, window_width, window_y, window_x);
|
||||||
|
// draw a border around the window
|
||||||
|
box(input_window, 0, 0);
|
||||||
|
// print the message in the middle of the window
|
||||||
|
mvwprintw(input_window, message_y, message_x, "Enter todo title: ");
|
||||||
|
// refresh the window
|
||||||
|
wrefresh(input_window);
|
||||||
|
// wait for a key press
|
||||||
|
echo();
|
||||||
|
mvwgetnstr(input_window, message_y, message_x + strlen("Enter todo title: "), buffer, buffer_size);
|
||||||
|
noecho();
|
||||||
|
|
||||||
|
// delete the window
|
||||||
|
delwin(input_window);
|
||||||
|
curs_set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_todos(int width, int height, int current_line) {
|
||||||
|
int current_y = 1;
|
||||||
|
int current_index = current_line;
|
||||||
|
const char *padding = "...";
|
||||||
|
unsigned long title_length = 0;
|
||||||
|
unsigned long padding_length = strlen(padding);
|
||||||
|
|
||||||
|
char *title = (char *)malloc(width);
|
||||||
|
|
||||||
|
attron(COLOR_PAIR(DEFAULT_PAIR));
|
||||||
|
|
||||||
|
// TODO: Use an offset or something
|
||||||
|
|
||||||
|
while(current_y < height - 2) {
|
||||||
|
// check if we're out of bounds
|
||||||
|
if (current_index >= arrlen(todos)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the title to the buffer
|
||||||
|
title_length = strlen(todos[current_index].title);
|
||||||
|
strncpy(title, todos[current_index].title, width - padding_length - 1);
|
||||||
|
// append the padding if needed
|
||||||
|
if (title_length > strlen(title)) {
|
||||||
|
strcat(title, padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to the correct line
|
||||||
|
move(current_y, 0);
|
||||||
|
|
||||||
|
// if the current todo is completed, print it as green
|
||||||
|
if (todos[current_index].completed) {
|
||||||
|
attroff(COLOR_PAIR(DEFAULT_PAIR));
|
||||||
|
attron(COLOR_PAIR(COMPLETED_PAIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight the current line
|
||||||
|
if (current_index == current_line) {
|
||||||
|
attron(A_UNDERLINE);
|
||||||
|
}
|
||||||
|
|
||||||
|
printw("%d. %s", current_index + 1, title);
|
||||||
|
|
||||||
|
// increment the current y and index
|
||||||
|
current_y++;
|
||||||
|
current_index++;
|
||||||
|
|
||||||
|
attroff(A_UNDERLINE);
|
||||||
|
attroff(COLOR_PAIR(COMPLETED_PAIR));
|
||||||
|
attron(COLOR_PAIR(DEFAULT_PAIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
free(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command handlers
|
||||||
|
void add_command_handler(int width, int height) {
|
||||||
char title[TODO_MAX_TITLE_LENGTH];
|
char title[TODO_MAX_TITLE_LENGTH];
|
||||||
printf("Enter todo title: ");
|
|
||||||
fgets(title, TODO_MAX_TITLE_LENGTH - 1, stdin);
|
input_string(width, height, title, TODO_MAX_TITLE_LENGTH);
|
||||||
// remove the newline character from the buffer
|
|
||||||
title[strlen(title) - 1] = '\0';
|
|
||||||
|
|
||||||
TodoItem item = todo_create_item(title);
|
TodoItem item = todo_create_item(title);
|
||||||
arrput(todos, item);
|
arrput(todos, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mark_command_handler(void) {
|
void mark_command_handler(int index) {
|
||||||
int op_index = -1;
|
TodoItem item = todos[index];
|
||||||
do {
|
|
||||||
printf("Enter todo index: ");
|
|
||||||
op_index = getc(stdin) - '0';
|
|
||||||
getc(stdin); // remove the newline character from the buffer
|
|
||||||
} while (op_index < 0 || op_index >= arrlen(todos));
|
|
||||||
|
|
||||||
TodoItem item = todos[op_index];
|
|
||||||
|
|
||||||
todo_mark_item(&item, !item.completed);
|
todo_mark_item(&item, !item.completed);
|
||||||
|
|
||||||
todos[op_index] = item;
|
todos[index] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_command_handler(void) {
|
void remove_command_handler(int index) {
|
||||||
for (int i = 0; i < arrlen(todos); i++) {
|
arrdel(todos, index);
|
||||||
printf("%d. ", i);
|
|
||||||
todo_print_item(&todos[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_command_handler(void) {
|
|
||||||
int op_index = -1;
|
|
||||||
do {
|
|
||||||
printf("Enter todo index: ");
|
|
||||||
op_index = getc(stdin) - '0';
|
|
||||||
getc(stdin); // remove the newline character from the buffer
|
|
||||||
} while (op_index < 0 || op_index >= arrlen(todos));
|
|
||||||
|
|
||||||
arrdel(todos, op_index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_to_file_handler(void) {
|
void write_to_file_handler(void) {
|
||||||
@@ -85,6 +223,9 @@ void write_to_file_handler(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void load_from_file_handler(void) {
|
void load_from_file_handler(void) {
|
||||||
|
// clear the current todos
|
||||||
|
clear_todos();
|
||||||
|
|
||||||
FILE *file = fopen(SAVE_FILE, "rb");
|
FILE *file = fopen(SAVE_FILE, "rb");
|
||||||
char magic[5] = {'\0'};
|
char magic[5] = {'\0'};
|
||||||
int todos_count = 0;
|
int todos_count = 0;
|
||||||
@@ -126,47 +267,73 @@ void load_from_file_handler(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char **argv) {
|
||||||
char cmd;
|
int terminal_width = 0;
|
||||||
|
int terminal_height = 0;
|
||||||
|
int key = 0;
|
||||||
|
int running = 1;
|
||||||
|
int current_line = 0;
|
||||||
|
|
||||||
do {
|
// set the save file path if provided
|
||||||
printf("Enter command: ");
|
if (argc > 1 && strlen(argv[1]) < MAX_PATH_LENGTH) {
|
||||||
cmd = getc(stdin);
|
strncpy(SAVE_FILE, argv[1], MAX_PATH_LENGTH);
|
||||||
getc(stdin); // remove the newline character from the buffer
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
initialize_curses(&terminal_width, &terminal_height);
|
||||||
case ADD:
|
|
||||||
add_command_handler();
|
while (running) {
|
||||||
break;
|
clear();
|
||||||
case MARK:
|
draw_header(terminal_width);
|
||||||
mark_command_handler();
|
draw_footer(terminal_width, terminal_height);
|
||||||
break;
|
display_todos(terminal_width, terminal_height, current_line);
|
||||||
case PRINT:
|
refresh();
|
||||||
print_command_handler();
|
|
||||||
break;
|
// wait for a key press
|
||||||
case REMOVE:
|
key = getch();
|
||||||
remove_command_handler();
|
|
||||||
|
// handle the key press
|
||||||
|
switch (key) {
|
||||||
|
case LOAD_FROM_FILE:
|
||||||
|
load_from_file_handler();
|
||||||
|
alert("Loaded!", terminal_width, terminal_height);
|
||||||
break;
|
break;
|
||||||
case WRITE_TO_FILE:
|
case WRITE_TO_FILE:
|
||||||
write_to_file_handler();
|
write_to_file_handler();
|
||||||
|
alert("Saved!", terminal_width, terminal_height);
|
||||||
break;
|
break;
|
||||||
case LOAD_FROM_FILE:
|
case ADD:
|
||||||
load_from_file_handler();
|
add_command_handler(terminal_width, terminal_height);
|
||||||
|
break;
|
||||||
|
case MARK:
|
||||||
|
mark_command_handler(current_line);
|
||||||
|
break;
|
||||||
|
case REMOVE:
|
||||||
|
remove_command_handler(current_line);
|
||||||
|
if (current_line >= arrlen(todos)) {
|
||||||
|
current_line = arrlen(todos) - 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case QUIT:
|
case QUIT:
|
||||||
|
running = 0;
|
||||||
break;
|
break;
|
||||||
default:
|
case UP:
|
||||||
printf("Invalid command\n");
|
current_line--;
|
||||||
|
if (current_line < 0) {
|
||||||
|
current_line = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DOWN:
|
||||||
|
current_line++;
|
||||||
|
if (current_line >= arrlen(todos)) {
|
||||||
|
current_line = arrlen(todos) - 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (cmd != QUIT);
|
|
||||||
|
|
||||||
for (int i = 0; i < arrlen(todos); i++) {
|
|
||||||
todo_free_item(&todos[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(todos);
|
endwin();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user