pane

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

pane.c (12753B)


      1 #include <termbox.h>
      2 #include <stdarg.h>
      3 #include <stdlib.h>
      4 
      5 #include "pane.h"
      6 #include "utils.h"
      7 
      8 #define T Pane_T
      9 struct T {
     10 	int horizontal;
     11 	int children_num;
     12 	T *children;
     13 	int *rules;
     14 
     15 	int x;
     16 	int y;
     17 
     18 	int width;
     19 	int height;
     20 
     21 	int active;
     22 	T direction[4];
     23 
     24 	widget_T widget;
     25 
     26 	char *title;
     27 	int index;
     28 	int child_no;
     29 	T parent;
     30 };
     31 
     32 int pane_hasBorder(T self)
     33 {
     34 	return !self->children;
     35 }
     36 
     37 int pane_x(T self)
     38 {
     39 	if (pane_hasBorder(self))
     40 		return self->x + 1;
     41 	else
     42 		return self->x;
     43 }
     44 
     45 int pane_y(T self)
     46 {
     47 	if (pane_hasBorder(self))
     48 		return self->y + 1;
     49 	else
     50 		return self->y;
     51 }
     52 int pane_width(T self)
     53 {
     54 	if (pane_hasBorder(self))
     55 		return self->width - 2;
     56 	else
     57 		return self->width;
     58 }
     59 int pane_height(T self)
     60 {
     61 	if (pane_hasBorder(self))
     62 		return self->height - 2;
     63 	else
     64 		return self->height;
     65 }
     66 
     67 #define UP(pane) pane->direction[0]
     68 #define RIGHT(pane) pane->direction[1]
     69 #define DOWN(pane) pane->direction[2]
     70 #define LEFT(pane) pane->direction[3]
     71 
     72 Pane_T MAIN;
     73 Pane_T ACTIVE;
     74 
     75 #define BORDER_HORIZONTAL "─"
     76 #define BORDER_VERTICAL "│"
     77 #define BORDER_CORNER_1 "┌"
     78 #define BORDER_CORNER_2 "┐"
     79 #define BORDER_CORNER_3 "└"
     80 #define BORDER_CORNER_4 "┘"
     81 
     82 void draw_title(T self)
     83 {
     84 	if (self->title)
     85 		tb_printf(self->x + 1, self->y, TB_RED, 0, " %s ", self->title);
     86 	else
     87 		tb_printf(self->x + 1, self->y, TB_RED, 0, " %d ", self->index);
     88 }
     89 
     90 void draw_border(int x, int y, int x_max, int y_max, uintattr_t color)
     91 {
     92 	size_t i;
     93 
     94 	for (i = x + 1; i < x_max; i++) {
     95 		tb_print(i, y, color, 0, BORDER_HORIZONTAL);
     96 		tb_print(i, y_max, color, 0, BORDER_HORIZONTAL);
     97 	}
     98 
     99 	for (i = y + 1; i < y_max; i++) {
    100 		tb_print(x, i, color, 0, BORDER_VERTICAL);
    101 		tb_print(x_max, i, color, 0, BORDER_VERTICAL);
    102 	}
    103 
    104 	tb_print(x, y, color, 0, BORDER_CORNER_1);
    105 	tb_print(x_max, y, color, 0, BORDER_CORNER_2);
    106 	tb_print(x, y_max, color, 0, BORDER_CORNER_3);
    107 	tb_print(x_max, y_max, color, 0, BORDER_CORNER_4);
    108 }
    109 
    110 void pane_draw_border(T self)
    111 {
    112 	int x_max = self->x + self->width - 1;
    113 	int y_max = self->y + self->height - 1;
    114 	int color = (self == ACTIVE) ? TB_BLUE : TB_GREEN;
    115 
    116 	draw_border(self->x, self->y, x_max, y_max, color);
    117 	draw_title(self);
    118 }
    119 
    120 void pane_clear(T self, int clear_border)
    121 {
    122 	size_t i, j;
    123 	int border = !clear_border;
    124 
    125 	for (j = border; j < self->height - border; j++)
    126 		for (i = border; i < self->width - border; i++)
    127 			tb_printf(self->x + i, self->y + j, TB_BLACK, 0, " ");
    128 }
    129 
    130 #define SPLIT_FORMULA(ac, ad, sc, sd)                                    \
    131 	{                                                                \
    132 		int total = 0, part, next;                               \
    133 		size_t i;                                                \
    134 		int available = self->ad;                                \
    135 		int last_split = -1;                                     \
    136                                                                          \
    137 		for (i = 0; i < self->children_num; i++) {               \
    138 			self->children[i]->sc = self->sc;                \
    139 			self->children[i]->sd = self->sd;                \
    140 			if (self->rules[i] > 0) {                        \
    141 				total += self->rules[i];                 \
    142 				last_split = i;                          \
    143 			} else {                                         \
    144 				available += self->rules[i];             \
    145 			}                                                \
    146 		}                                                        \
    147                                                                          \
    148 		part = available / total;                                \
    149 		next = self->ac;                                         \
    150 		total = 0;                                               \
    151 		for (i = 0; i < last_split; i++) {                       \
    152 			self->children[i]->ac = next;                    \
    153 			if (self->rules[i] > 0) {                        \
    154 				total += self->children[i]->ad =         \
    155 					part * self->rules[i];           \
    156 			} else {                                         \
    157 				self->children[i]->ad = -self->rules[i]; \
    158 			}                                                \
    159 			next += self->children[i]->ad;                   \
    160 		}                                                        \
    161                                                                          \
    162 		self->children[i]->ac = next;                            \
    163 		self->children[i]->ad = available - total;               \
    164 		next += self->children[i]->ad;                           \
    165                                                                          \
    166 		for (i = i + 1; i < self->children_num; i++) {           \
    167 			self->children[i]->ac = next;                    \
    168 			self->children[i]->ad = -self->rules[i];         \
    169 			next += self->children[i]->ad;                   \
    170 		}                                                        \
    171 	}
    172 
    173 #define wrap_or_slap(dir, index)                                    \
    174 	dir(child) = (dir(self) && dir(self) != self) ? dir(self) : \
    175 							self->children[index];
    176 
    177 #define CONNECTION_FORMULA(af, ab, s1, s2)                             \
    178 	{                                                              \
    179 		T child;                                               \
    180 		for (i = 0; i < self->children_num; i++) {             \
    181 			child = self->children[i];                     \
    182 			wrap_or_slap(s1, i);                           \
    183 			wrap_or_slap(s2, i);                           \
    184 		}                                                      \
    185                                                                        \
    186 		for (i = 1; i < self->children_num; i++)               \
    187 			ab(self->children[i]) = self->children[i - 1]; \
    188 		child = self->children[0];                             \
    189 		wrap_or_slap(ab, self->children_num - 1);              \
    190                                                                        \
    191 		for (i = 0; i < self->children_num - 1; i++)           \
    192 			af(self->children[i]) = self->children[i + 1]; \
    193 		child = self->children[self->children_num - 1];        \
    194 		wrap_or_slap(af, 0);                                   \
    195 	}
    196 
    197 int calc_children(T self)
    198 {
    199 	if (self->horizontal)
    200 		SPLIT_FORMULA(x, width, y, height)
    201 	else
    202 		SPLIT_FORMULA(y, height, x, width)
    203 
    204 	return 1;
    205 }
    206 
    207 int pane_resize(T self)
    208 {
    209 	size_t i;
    210 
    211 	if (self->widget) {
    212 		self->widget->resized = 1;
    213 		self->widget->callback(self->widget);
    214 	}
    215 
    216 	if (self->children_num == 0) {
    217 		pane_draw_border(self);
    218 		return 1;
    219 	}
    220 
    221 	calc_children(self);
    222 	for (i = 0; i < self->children_num; i++)
    223 		pane_resize(self->children[i]);
    224 
    225 	return 1;
    226 }
    227 
    228 int pane_handle_resize(void)
    229 {
    230 	MAIN->height = tb_height();
    231 	MAIN->width = tb_width();
    232 	MAIN->x = 0;
    233 	MAIN->y = 0;
    234 	MAIN->direction[0] = NULL;
    235 	MAIN->direction[1] = NULL;
    236 	MAIN->direction[2] = NULL;
    237 	MAIN->direction[3] = NULL;
    238 
    239 	tb_clear();
    240 	pane_resize(MAIN);
    241 	tb_present();
    242 
    243 	return 1;
    244 }
    245 
    246 int pane_start(int active)
    247 {
    248 	tb_init();
    249 	MAIN = calloc(1, sizeof(*MAIN));
    250 	MAIN->height = tb_height();
    251 	MAIN->width = tb_width();
    252 	MAIN->parent = MAIN;
    253 	pane_handle_resize();
    254 
    255 	if (active) {
    256 		ACTIVE = MAIN;
    257 		MAIN->active = 1;
    258 	}
    259 
    260 	return 1;
    261 }
    262 
    263 int pane_stop(void)
    264 {
    265 	return tb_shutdown();
    266 }
    267 
    268 T pane_child(T self, int index)
    269 {
    270 	return self->children[index];
    271 }
    272 
    273 widget_T pane_widget(T self)
    274 {
    275 	return self->widget;
    276 }
    277 
    278 void split(T self, size_t count, va_list ap)
    279 {
    280 	static int index = 1;
    281 	size_t i;
    282 
    283 	self->children_num = count;
    284 	self->children = malloc(self->children_num * sizeof(T));
    285 	self->rules = malloc(self->children_num * sizeof(int));
    286 
    287 	for (i = 0; i < self->children_num; i++) {
    288 		self->children[i] = calloc(1, sizeof(struct T));
    289 		self->rules[i] = va_arg(ap, int);
    290 
    291 		self->children[i]->index = index++;
    292 		self->children[i]->parent = self;
    293 		self->children[i]->child_no = i;
    294 	}
    295 
    296 	if (self->active) {
    297 		self->active = 0;
    298 		self->children[0]->active = 1;
    299 		ACTIVE = self->children[0];
    300 	}
    301 
    302 	if (self->widget)
    303 		widget_deactivate(self->widget);
    304 
    305 	pane_resize(self);
    306 }
    307 
    308 void pane_unsplit(T self)
    309 {
    310 	size_t i;
    311 	int active = 0;
    312 
    313 	if (self->children_num) {
    314 		for (i = 0; i < self->children_num; i++) {
    315 			pane_unsplit(self->children[i]);
    316 			if (self->children[i]->active)
    317 				active = 1;
    318 			free(self->children[i]);
    319 		}
    320 		free(self->children);
    321 		free(self->rules);
    322 		self->children = NULL;
    323 		self->rules = NULL;
    324 	} else {
    325 		active = self->active;
    326 	}
    327 	self->children_num = 0;
    328 	self->active = active;
    329 	if (active) {
    330 		ACTIVE = self;
    331 	}
    332 	pane_clear(self, 1);
    333 	pane_draw_border(self);
    334 	tb_present();
    335 }
    336 
    337 T *pane_split(T self, size_t count, ...)
    338 {
    339 	size_t i;
    340 	va_list ap;
    341 
    342 	self->horizontal = 1;
    343 	va_start(ap, count);
    344 	split(self, count, ap);
    345 	va_end(ap);
    346 
    347 	CONNECTION_FORMULA(RIGHT, LEFT, UP, DOWN)
    348 
    349 	return self->children;
    350 }
    351 
    352 T *pane_vsplit(T self, size_t count, ...)
    353 {
    354 	size_t i;
    355 	va_list ap;
    356 
    357 	self->horizontal = 0;
    358 	va_start(ap, count);
    359 	split(self, count, ap);
    360 	va_end(ap);
    361 
    362 	CONNECTION_FORMULA(DOWN, UP, LEFT, RIGHT)
    363 
    364 	return self->children;
    365 }
    366 
    367 void pane_setTitle(T self, char *title)
    368 {
    369 	self->title = title;
    370 	if (self->children_num == 0) {
    371 		pane_draw_border(self);
    372 		draw_title(self);
    373 	}
    374 }
    375 
    376 void setActive(int dir)
    377 {
    378 	T old = ACTIVE, tmp;
    379 	if (!ACTIVE->direction[dir])
    380 		return;
    381 
    382 	ACTIVE = ACTIVE->direction[dir];
    383 
    384 	while (ACTIVE->children_num) {
    385 		int last = ACTIVE->children_num - 1;
    386 		if (!ACTIVE->horizontal) {
    387 			if (dir == 0 || dir == 2) {
    388 				ACTIVE = ACTIVE->children[dir == 2 ? 0 : last];
    389 				continue;
    390 			}
    391 		} else {
    392 			if (dir == 1 || dir == 3) {
    393 				ACTIVE = ACTIVE->children[dir == 1 ? 0 : last];
    394 				continue;
    395 			}
    396 		}
    397 		ACTIVE = ACTIVE->children[ACTIVE->active];
    398 	}
    399 
    400 	old->active = 0;
    401 	ACTIVE->active = 1;
    402 
    403 	for (tmp = ACTIVE; tmp->parent != tmp; tmp = tmp->parent) {
    404 		tmp->parent->active = tmp->child_no;
    405 	}
    406 
    407 	pane_draw_border(old);
    408 	pane_draw_border(ACTIVE);
    409 	tb_present();
    410 }
    411 
    412 int centerVertical(Pane_T pane, int len)
    413 {
    414 	if (len > pane_height(pane))
    415 		return pane_hasBorder(pane) ? 1 : 0;
    416 
    417 	return pane_y(pane) + (pane_height(pane) - len) / 2;
    418 }
    419 
    420 int centerHorisontal(Pane_T pane, int len)
    421 {
    422 	if (len > pane_width(pane))
    423 		return pane_hasBorder(pane) ? 1 : 0;
    424 
    425 	return pane_x(pane) + (pane_width(pane) - len - 2) / 2 + 1;
    426 }
    427 
    428 /* DATA */
    429 
    430 data_T data_new(void *payload, data_handler handler)
    431 {
    432 	data_T data = calloc(1, sizeof(*data));
    433 	data->handle_input = handler;
    434 	data->payload = payload;
    435 	data->widget_num = 0;
    436 	data->widgets = NULL;
    437 	return data;
    438 }
    439 
    440 /* WIDGET */
    441 
    442 int widget_activate(widget_T self, Pane_T pane)
    443 {
    444 	self->pane = pane;
    445 	pane->widget = self;
    446 	pane_setTitle(pane, self->title);
    447 
    448 	self->callback(self);
    449 	tb_present();
    450 
    451 	return 1;
    452 }
    453 
    454 int widget_deactivate(widget_T self)
    455 {
    456 	size_t i, j;
    457 
    458 	pane_setTitle(self->pane, NULL);
    459 	self->pane->widget = NULL;
    460 	self->pane = NULL;
    461 
    462 	data_T data = self->data;
    463 	for (i = j = 0; i < data->widget_num; i++)
    464 		if (data->widgets[i] != self)
    465 			data->widgets[j++] = data->widgets[i];
    466 	data->widget_num = j;
    467 
    468 	return 1;
    469 }
    470 
    471 void widget_setData(widget_T self, data_T data)
    472 {
    473 	self->inited = 0;
    474 	self->data = data;
    475 	data->widgets = realloc(data->widgets, (data->widget_num + 1) *
    476 						       sizeof(*data->widgets));
    477 	data->widgets[data->widget_num++] = self;
    478 }
    479 
    480 int pane_handle_input(Pane_T self, struct tb_event ev)
    481 {
    482 	size_t i;
    483 	int res;
    484 	widget_T widget;
    485 	data_T data;
    486 
    487 	if ((widget = pane_widget(self)) && (data = widget->data)) {
    488 		if (data->handle_input) {
    489 			switch ((res = data->handle_input(data, ev))) {
    490 			case INPUT_HANDLED: {
    491 				data = widget->data;
    492 				size_t size = data->widget_num;
    493 				for (i = 0; i < size; i++) {
    494 					widget = data->widgets[i];
    495 					widget->callback(widget);
    496 					size = data->widget_num;
    497 				}
    498 				tb_present();
    499 				return 1;
    500 			}
    501 			case INPUT_IGNORED:
    502 			case INPUT_REFORM:
    503 				return res;
    504 			}
    505 		}
    506 	}
    507 	return INPUT_IGNORED;
    508 }
    509 
    510 int pane_recursive_handle(Pane_T self, struct tb_event ev)
    511 {
    512 	size_t i;
    513 	int res;
    514 
    515 	if (!self)
    516 		return 0;
    517 
    518 	switch ((res = pane_handle_input(self, ev))) {
    519 	case INPUT_HANDLED:
    520 	case INPUT_REFORM:
    521 		return res;
    522 	}
    523 
    524 	for (i = 0; i < self->children_num; i++)
    525 		switch ((res = pane_recursive_handle(self->children[i], ev))) {
    526 		case INPUT_IGNORED:
    527 			continue;
    528 		case INPUT_HANDLED:
    529 		case INPUT_REFORM:
    530 			return res;
    531 		}
    532 
    533 	return INPUT_IGNORED;
    534 }
    535 
    536 void pane_event_loop(void)
    537 {
    538 	struct tb_event ev;
    539 
    540 	while (1) {
    541 		tb_poll_event(&ev);
    542 		switch (ev.type) {
    543 		case TB_EVENT_RESIZE:
    544 			pane_handle_resize();
    545 			continue;
    546 		case TB_EVENT_KEY: {
    547 			if (ev.key == TB_KEY_ESC) {
    548 				return;
    549 			}
    550 
    551 			if (ACTIVE) {
    552 				switch (ev.key) {
    553 				case TB_KEY_ARROW_UP:
    554 					setActive(0);
    555 					continue;
    556 				case TB_KEY_ARROW_RIGHT:
    557 					setActive(1);
    558 					continue;
    559 				case TB_KEY_ARROW_DOWN:
    560 					setActive(2);
    561 					continue;
    562 				case TB_KEY_ARROW_LEFT:
    563 					setActive(3);
    564 					continue;
    565 				}
    566 				if (!pane_handle_input(ACTIVE, ev))
    567 					continue;
    568 				break;
    569 			} else {
    570 				pane_recursive_handle(MAIN, ev);
    571 			}
    572 		}
    573 		}
    574 	}
    575 }