pane

Termbox2 based terminal UI library
git clone git://git.dimitrijedobrota.com/pane.git
Log | Files | Refs

commit 2ad2a19cc37a2aa1e13be9949ce6abb43e16643a
Author: Dimitrije Dobrota <mail@dimitrijedobrota.com>
Date:   Wed, 31 Aug 2022 01:16:59 +0200

Added menu structure

Diffstat:
A.clang-format | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.gitignore | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AMakefile | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abin/main | 0
Ainclude/pane.h | 39+++++++++++++++++++++++++++++++++++++++
Ainclude/utils.h | 24++++++++++++++++++++++++
Asrc/main.c | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pane.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 727 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... + diff --git a/.gitignore b/.gitignore @@ -0,0 +1,56 @@ +.ccls-cache/ +.ccls +docs/* + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/Makefile b/Makefile @@ -0,0 +1,74 @@ +# GNU Makefile for C Interfaces and Implementations +# +# Usage: make [-f path\Makefile] [DEBUG=Y] target + +NAME = display +CC = gcc + +SRC = src +OBJ = . +BIN = bin +INCLUDE = include +LATEX = docs/latex + +EXCLUDE = $(SRC)/main.c + +SRCS=$(wildcard $(SRC)/*.c) + +SRCS := $(filter-out $(EXCLUDE), $(SRCS)) +OBJS=$(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) +CFLAGS = -I$(INCLUDE) + +ifeq ($(DEBUG),Y) + CFLAGS += -Wall -ggdb + SRCS := $(filter-out $(SRC)/mem.c, $(SRCS)) + LIBNAME =$(NAME)d + LIB = $(OBJ)/lib$(LIBNAME) +else + CFLAGS += -DNDEBUG + SRCS := $(filter-out $(SRC)/memchk.c, $(SRCS)) + LIBNAME = $(NAME) + LIB = $(OBJ)/lib$(LIBNAME) +endif + +all: $(LIB) main + +$(LIB): $(OBJS) + ar crs $@.a $^ + +main: main.o + gcc $(SRC)/main.c -o $(BIN)/main $(CFLAGS) $(LDFLAGS) -L. -l$(LIBNAME) + +$(OBJ)/%.o: $(SRC)/%.c + $(CC) -c $< -o $@ $(CFLAGS) $(LDFLAGS) + +install: + install -d /usr/local/include/$(NAME) + install -p -m 644 $(INCLUDE)/* /usr/local/include/$(NAME) + install -d /usr/local/lib + install -p -m 644 $(LIB).a /usr/local/lib + +clean: + -$(RM) $(OBJS) $(LIB).a + +docs: + doxygen + make -C $(LATEX) + +help: + @echo "Terbox2 based display library" + @echo + @echo "Usage: make [-f path\Makefile] [DEBUG=Y] target" + @echo + @echo "Target rules:" + @echo " all - Compiles libary file [Default]" + @echo " install - Installs the static library and header files" + @echo " clean - Clean the project by removing binaries" + @echo " help - Prints a help message with target rules" + @echo " docs - Compile html and pdf documentation using doxygen and pdflatex" + @echo + @echo "Optional parameters:" + @echo " DEBUG - Compile binary file with debug flags enabled" + @echo + +.PHONY: all clean help docs diff --git a/bin/main b/bin/main Binary files differ. diff --git a/include/pane.h b/include/pane.h @@ -0,0 +1,39 @@ +#ifndef PANE_H +#define PANE_H + +#define T Pane_T +typedef struct T *T; + +int pane_start(void); +int pane_stop(void); +int pane_handle_resize(void); +int pane_resize(T self); +T pane_child(T self, int index); + +T *pane_split(T self, size_t count, ...); +T *pane_vsplit(T self, size_t count, ...); + +void pane_setTitle(T self, char *title); + +struct menuItem_T { + void (*callback)(char *, int); + char *name; +}; + +typedef struct menu_T *menu_T; +struct menu_T { + T pane; + char *title; + char *separator; + + int padding; + int spacing; + + int items_size; + struct menuItem_T items[]; +}; + +void pane_menu(menu_T self); + +#undef T +#endif diff --git a/include/utils.h b/include/utils.h @@ -0,0 +1,24 @@ +#ifndef UTILS_H +#define UTILS_H + +#include <stdio.h> + +#define MAX(a, b) ((a > b) ? a : b) +#define MIN(a, b) ((a < b) ? a : b) +#define CLAMP(a, x, y) ((a) = (MAX(x, MIN(a, y)))) +#define ACLAMP(a, x, y) (MAX(x, MIN(a, y))) + +#define UNIMPLEMENTED \ + do { \ + fprintf(stderr, "%s:%d: UNIMPLEMENTED\n", __FILE__, __LINE__); \ + exit(1); \ + } while (0); + +#define UNUSED(x) (void)(x) + +#define HANDLE_RESIZE \ + { \ + pane_handle_resize(); \ + } + +#endif diff --git a/src/main.c b/src/main.c @@ -0,0 +1,67 @@ +#define TB_IMPL +#define TB_OPT_TRUECOLOR + +#include <termbox.h> +#include <stdio.h> + +#include "pane.h" +#include "utils.h" + +extern Pane_T MAIN; + +void load(char *pass, int index) +{ + pane_stop(); + UNIMPLEMENTED; +} + +struct menu_T mainMenu = { + .pane=NULL, + .title = "Main Menu", + .separator="<------------->", + .padding = 5, + .spacing = 2, + + .items = { + { load, "Start" }, + { load, "Help" }, + { load, "Exit" }, + }, + .items_size = 3 +}; + +int main(int argc, char **argv) +{ + struct tb_event ev; + int y = 0; + + pane_start(); + { + Pane_T *sections, *mids, *bottoms; + + sections = pane_vsplit(MAIN, 3, 1, 1, 1); + mids = pane_split(sections[1], 2, 1, 2); + bottoms = pane_split(sections[2], 2, 1, 1); + + pane_vsplit(mids[0], 2, 1, 1); + mainMenu.pane = mids[1]; + pane_menu(&mainMenu); + + tb_present(); + while (1) { + tb_poll_event(&ev); + switch (ev.type) { + case TB_EVENT_RESIZE: + HANDLE_RESIZE; + break; + case TB_EVENT_KEY: + switch (ev.ch) + case 'q': + goto end; + } + } + } +end: + pane_stop(); + return 0; +} diff --git a/src/pane.c b/src/pane.c @@ -0,0 +1,347 @@ +#include <termbox.h> +#include <stdarg.h> +#include <mem.h> + +#include "pane.h" +#include "utils.h" + +#define T Pane_T + +struct T { + int children_num; + T *children; + int horizontal; + int *rules; + + char *title; + + int width; + int height; + + int x; + int y; +}; + +Pane_T MAIN; + +#define BORDER_HORIZONTAL "─" +#define BORDER_VERTICAL "│" +#define BORDER_CORNER_1 "┌" +#define BORDER_CORNER_2 "┐" +#define BORDER_CORNER_3 "└" +#define BORDER_CORNER_4 "┘" + +void draw_title(T self) +{ + if (self->title) + tb_printf(self->x + 1, self->y, TB_RED, 0, " %s ", self->title); +} + +void draw_border(T self) +{ + size_t i; + + int x_max = self->x + self->width - 1; + int y_max = self->y + self->height - 1; + + for (i = self->x + 1; i < x_max; i++) { + tb_print(i, self->y, TB_GREEN, 0, BORDER_HORIZONTAL); + tb_print(i, y_max, TB_GREEN, 0, BORDER_HORIZONTAL); + } + + for (i = self->y + 1; i < y_max; i++) { + tb_print(self->x, i, TB_GREEN, 0, BORDER_VERTICAL); + tb_print(x_max, i, TB_GREEN, 0, BORDER_VERTICAL); + } + + tb_print(self->x, self->y, TB_GREEN, 0, BORDER_CORNER_1); + tb_print(x_max, self->y, TB_GREEN, 0, BORDER_CORNER_2); + tb_print(self->x, y_max, TB_GREEN, 0, BORDER_CORNER_3); + tb_print(x_max, y_max, TB_GREEN, 0, BORDER_CORNER_4); + + tb_printf(self->x + 1, self->y + 1, TB_RED, 0, "%d-%d", self->x, + self->y, self->width, self->height); + + draw_title(self); +} + +#define SPLIT_FORMULA(ac, ad, sc, sd) \ + { \ + int total = 0, part, next; \ + size_t i; \ + int available = self->ad; \ + int last_split = -1; \ + \ + for (i = 0; i < self->children_num; i++) { \ + self->children[i]->sc = self->sc; \ + self->children[i]->sd = self->sd; \ + if (self->rules[i] > 0) { \ + total += self->rules[i]; \ + last_split = i; \ + } else { \ + available += self->rules[i]; \ + } \ + } \ + \ + part = available / total; \ + next = self->ac; \ + total = 0; \ + for (i = 0; i < last_split; i++) { \ + self->children[i]->ac = next; \ + if (self->rules[i] > 0) { \ + total += self->children[i]->ad = \ + part * self->rules[i]; \ + } else { \ + self->children[i]->ad = -self->rules[i]; \ + } \ + next += self->children[i]->ad; \ + } \ + \ + self->children[i]->ac = next; \ + self->children[i]->ad = available - total; \ + next += self->children[i]->ad; \ + \ + for (i = i + 1; i < self->children_num; i++) { \ + self->children[i]->ac = next; \ + self->children[i]->ad = -self->rules[i]; \ + next += self->children[i]->ad; \ + } \ + } + +int calc_children(T self) +{ + if (self->horizontal) + SPLIT_FORMULA(x, width, y, height) + else + SPLIT_FORMULA(y, height, x, width) + + return 1; +} + +int pane_resize(T self) +{ + size_t i; + + if (self->children_num == 0) { + draw_border(self); + tb_present(); + /* usleep(200000); */ + return 1; + } + + calc_children(self); + for (i = 0; i < self->children_num; i++) + pane_resize(self->children[i]); + + return 1; +} + +int pane_handle_resize(void) +{ + MAIN->height = tb_height(); + MAIN->width = tb_width(); + MAIN->x = 0; + MAIN->y = 0; + + tb_clear(); + pane_resize(MAIN); + + return 1; +} + +int pane_start(void) +{ + tb_init(); + MAIN = calloc(1, sizeof(*MAIN)); + pane_handle_resize(); + + return 1; +} + +int pane_stop(void) +{ + return tb_shutdown(); +} + +T pane_child(T self, int index) +{ + return self->children[index]; +} + +void split(T self, size_t count, va_list ap) +{ + int current; + size_t i; + + self->children_num = count; + self->children = malloc(self->children_num * sizeof(T)); + self->rules = malloc(self->children_num * sizeof(int)); + + for (i = 0; i < self->children_num; i++) { + self->children[i] = calloc(1, sizeof(struct T)); + self->rules[i] = va_arg(ap, int); + } + + pane_resize(self); +} + +T *pane_split(T self, size_t count, ...) +{ + va_list ap; + + self->horizontal = 1; + va_start(ap, count); + split(self, count, ap); + va_end(ap); + + return self->children; +} + +T *pane_vsplit(T self, size_t count, ...) +{ + va_list ap; + + self->horizontal = 0; + va_start(ap, count); + split(self, count, ap); + va_end(ap); + + return self->children; +} + +void pane_setTitle(T self, char *title) +{ + self->title = title; + if (self->children_num == 0) + draw_title(self); +} + +int centerVertical(T self, int len) +{ + return (self->height - len) / 2; +} + +int centerHorisontal(T self, int len) +{ + return (self->width - len - 2) / 2 + 1; +} + +void pane_clear(T self, int clear_border) +{ + size_t i; + /* int border = self->children_num != 0; */ + int border = !clear_border; + int width = self->width - 3 * border; + char *line = malloc(width * sizeof(char)); + memset(line, ' ', width); + + for (i = self->y + border; i < self->y + self->height - border; i++) + tb_print(self->x + border, i, TB_BLACK, 0, line); +} + +void pane_menu(menu_T menu) +{ + struct tb_event ev; + int rl_crnt = 0; + int i, len, maxi = 0; + int sep_len = strlen(menu->separator); + + for (i = 0; i < menu->items_size; i++) + if ((len = strlen(menu->items[i].name)) > maxi) + maxi = len; + + pane_setTitle(menu->pane, menu->title); + + int ab_crnt, items_num, y_start; + ab_crnt = 0; +redraw:; + + // optimal number of elements to display on the screen + int max_items = + (menu->pane->height - menu->padding * 2 + menu->spacing) / + (menu->spacing + 1); + // number of items displayed cant exceed actual number of items in the menu + items_num = ACLAMP(max_items, 0, menu->items_size); + // staring point for displaying these items for them to be centered + y_start = centerVertical(menu->pane, items_num * (menu->spacing + 1) - + menu->spacing); + + while (1) { + pane_clear(menu->pane, 0); + int y = menu->pane->y + y_start; + int x = menu->pane->x + centerHorisontal(menu->pane, maxi); + int x_separator = + menu->pane->x + centerHorisontal(menu->pane, sep_len); + + if (rl_crnt == -1) + ab_crnt--; + + if (rl_crnt == items_num) + ab_crnt++; + + CLAMP(ab_crnt, 0, menu->items_size - items_num); + CLAMP(rl_crnt, 0, items_num - 1); + + if (!menu->items_size) { + char *message = "NOTHING TO DISPLAY HERE!"; + int x = centerHorisontal(menu->pane, strlen(message)); + tb_print(x, y, TB_BLUE, 0, message); + tb_present(); + tb_poll_event(&ev); + return; + } + + if (items_num < menu->items_size && ab_crnt == 0) { + tb_print(x_separator, y - 1, TB_BLUE, 0, + menu->separator); + } + + int index = ab_crnt; + for (i = 0; i < items_num; i++) { + int color = (i == rl_crnt) ? TB_RED : TB_BLUE; + tb_printf(x, y, color, 0, "%s", + menu->items[ab_crnt + i].name); + y += menu->spacing + 1; + } + + if (items_num < menu->items_size && + ab_crnt == menu->items_size - items_num) { + tb_print(x_separator, y - menu->spacing, TB_BLUE, 0, + menu->separator); + } + + tb_present(); + + while (1) { + tb_poll_event(&ev); + + if (ev.type == TB_EVENT_RESIZE) { + HANDLE_RESIZE; + goto redraw; + } else if (ev.type == TB_EVENT_KEY) { + if (ev.ch == 'k' || ev.ch == 'w' || + ev.key == TB_KEY_ARROW_UP) { + rl_crnt--; + break; + } + if (ev.ch == 'j' || ev.ch == 's' || + ev.key == TB_KEY_ARROW_DOWN) { + rl_crnt++; + break; + } + if (ev.ch == 'q' || ev.key == TB_KEY_ESC) { + pane_clear(menu->pane, 1); + tb_present(); + return; + } + if (ev.key == TB_KEY_ENTER) { + int index = ab_crnt + rl_crnt; + struct menuItem_T *item = + menu->items + index; + item->callback(item->name, index); + return; + } + } + } + } +}