diff --git a/btk/btk-cell-render.c b/btk/btk-cell-render.c new file mode 100644 index 0000000..91b63cb --- /dev/null +++ b/btk/btk-cell-render.c @@ -0,0 +1,441 @@ +void fill_cell (btk_cell_t *, cairo_t *, btk_rgb_t); +void guard_scroll (int *, int, int, unsigned int, int); +void render_button (btk_cell_t *, cairo_t *); +void render_editor (btk_cell_t *); +void render_gauge (btk_cell_t *, cairo_t *); +void render_input (btk_cell_t *, cairo_t *); +void render_list (btk_cell_t *); +void render_mark (btk_cell_t *, cairo_t *); +void render_prompt (btk_cell_t *, cairo_t *); +void render_table (btk_cell_t *); +void render_table_row (btk_cell_t *, char *, int, btk_rgb_t, btk_rgb_t); +void render_text (btk_cell_t *); +void render_switch (btk_cell_t *, cairo_t *); +void render_wheel (btk_cell_t *, cairo_t *); +void reset_field (btk_cell_t *); +void write_label (cairo_t *, int, int, unsigned int, int, char *, btk_rgb_t); + +static cairo_font_options_t *font_opt; +static btk_pos_t label_offset; +static btk_size_t glyph; + +/* ch is a modifier to force the heigth of a rendered cell to 1 cell unit */ +void +fill_cell(btk_cell_t *c, cairo_t *c_ctx, btk_rgb_t cl) +{ + cairo_set_source_rgb(c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(c_ctx, c->pa.x, c->pa.y, c->pa.w, c->pa.h); + cairo_fill(c_ctx); +} + +/* pi = pixel indent, of each scrollable element in the considered axis */ +/* cl = cell length, in the considered axis */ +void +guard_scroll(int *scroll_var, int pi, int cl, unsigned int items_n, int extra) +{ + if (*scroll_var < 0 || !items_n) { /* no negative scroll position */ + *scroll_var = 0; + } else if (cl >= pi * items_n + extra) { /* reset scroll pos if everything fits in */ + *scroll_var = 0; + } else if (*scroll_var + cl > pi * items_n + extra) { + *scroll_var = pi * items_n - cl + extra; + } +} + +void +render_button(btk_cell_t *c, cairo_t *c_ctx) +{ + btk_rgb_t clt, cl; + if ((c->state & BTK_CELL_STATE_DISABLED)) { + cl = btk_cl_dyn_disabled; + clt = btk_cl_text_disabled; + } else if ((c->state & BTK_CELL_STATE_PRESSED)) { + cl = btk_cl_dyn_pressed; + clt = btk_cl_text_hilight; + } else { + cl = btk_cl_dyn_idle; + clt = btk_cl_text_normal; + } + + fill_cell(c, c_ctx, cl); + write_label(c_ctx, c->pa.x, c->pa.y, c->pa.w, BTK_JUSTIFY_LEFT, c->label, clt); +} + +void +render_editor(btk_cell_t *c) +{ + btk_pp_editor_t *pp = PP_EDITOR(c); + + cairo_set_source_rgb(c->fd->c_ctx, btk_cl_field.r, btk_cl_field.g, btk_cl_field.b); + cairo_paint(c->fd->c_ctx); + + int text_n = 0; + if (*pp->text) + text_n = strlen(pp->text); + + guard_scroll(&pp->scroll, glyph.h + btk_text_spacing, c->pa.h, pp->par->rows_n, label_offset.y); + + /* render caret */ + if (!(c->state & BTK_CELL_STATE_DISABLED)) { + cairo_set_source_rgb(c->fd->c_ctx, + btk_cl_bell.r, + btk_cl_bell.g, + btk_cl_bell.b); + cairo_move_to(c->fd->c_ctx, + label_offset.x + pp->caret_2d.x * glyph.w, + pp->caret_2d.y * (glyph.h + btk_text_spacing) + + btk_caret_offset + label_offset.y - pp->scroll); + cairo_show_text(c->fd->c_ctx, "_"); + } + + /* just render label if there are no rows to draw */ + if (!text_n || !pp->par) { + write_label(c->fd->c_ctx, + 0, + 0, + c->pa.w, + BTK_JUSTIFY_LEFT, + c->label, + btk_cl_text_lolight); + return; + } + + btk_rgb_t cl; + /* else render row by row */ + for (int i = 0; i < pp->par->rows_n; i++) { + cl = btk_cl[pp->par->rows_spot[i]]; + cairo_set_source_rgb(c->fd->c_ctx, cl.r, cl.g, cl.b); + cairo_move_to(c->fd->c_ctx, + label_offset.x, + label_offset.y - pp->scroll + i * (glyph.h + btk_text_spacing)); + cairo_show_text(c->fd->c_ctx, pp->par->rows + i * pp->par->rows_max_w); + } +} + +void +render_input(btk_cell_t *c, cairo_t *c_ctx) +{ + btk_pp_input_t *pp = PP_INPUT(c); + int text_n = strlen(pp->text); + btk_rgb_t clt, clt2; + + /* passive components */ + clt = (c->state & BTK_CELL_STATE_DISABLED) ? btk_cl_dyn_disabled : btk_cl_pass_on; + fill_cell(c, c_ctx, btk_cl_field); + cairo_set_source_rgb(c_ctx, clt.r, clt.g, clt.b); + cairo_move_to(c_ctx, c->pa.x + label_offset.x, c->pa.y + label_offset.y); + cairo_show_text(c_ctx, "►"); + + /* limits */ + int max_n = (c->pa.w - 3 * label_offset.x ) / glyph.w - 1; + if (pp->caret > pp->scroll + max_n - 1) + pp->scroll = pp->caret - max_n + 1; + if (pp->caret < pp->scroll) + pp->scroll = pp->caret; + if (text_n > max_n && pp->scroll + max_n > text_n) + pp->scroll -= pp->scroll + max_n - text_n; + + /* render caret */ + if (!(c->state & BTK_CELL_STATE_DISABLED)) { + cairo_set_source_rgb(c_ctx, btk_cl_bell.r, btk_cl_bell.g, btk_cl_bell.b); + cairo_move_to(c_ctx, + c->pa.x + 2 * label_offset.x + (1 + pp->caret - pp->scroll) * glyph.w, + c->pa.y + label_offset.y + btk_caret_offset); + cairo_show_text(c_ctx, "_"); + } + + /* prepare text to render */ + char cut_text[max_n + 1]; + if (text_n) { + strncpy(cut_text, pp->text + pp->scroll, max_n); + cut_text[max_n] = '\0'; + clt2 = btk_cl_text_disabled; + } else { + strcpy(cut_text, c->label); + clt2 = btk_cl_text_lolight; + } + + /* render text */ + cairo_set_source_rgb(c_ctx, clt2.r, clt2.g, clt2.b); + cairo_move_to(c_ctx, + c->pa.x + 2 * label_offset.x + glyph.w, + c->pa.y + label_offset.y); + cairo_show_text(c_ctx, cut_text); +} + +void +render_list(btk_cell_t *c) +{ + reset_field(c); + guard_scroll(&(PP_LIST(c)->scroll), + btk_cp.h + btk_frame, + c->pa.h, + *(PP_LIST(c)->items_n), + 0); + + /* compute item range to render */ + int item_min = PP_LIST(c)->scroll / (btk_cp.h + btk_frame); + int item_max = (PP_LIST(c)->scroll + c->pa.h) / (btk_cp.h + btk_frame) + 1; + item_max = BTK_MIN(item_max, *(PP_LIST(c)->items_n)); + + /* just render label if there are no rows to draw */ + if (!*(PP_LIST(c)->items_n)) { + write_label(c->fd->c_ctx, + 0, + 0, + c->pa.w, + BTK_JUSTIFY_LEFT, + c->label, + btk_cl_text_lolight); + return; + } + + /* render sub cells */ + btk_rgb_t cl, clt; + int row_pos; + for (int i = item_min; i < item_max; i++) { + if (i == PP_LIST(c)->item_sel) { + if ((c->state & BTK_CELL_STATE_PRESSED)) { + cl = btk_cl_dyn_pressed; + clt = btk_cl_text_hilight; + } else { + clt = btk_cl_text_select; + cl = btk_cl_select; + } + } else { + cl = btk_cl_field; + clt = btk_cl_text_disabled; + if (PP_LIST(c)->spot) + clt = btk_cl[*(*(PP_LIST(c)->spot) + i)]; + } + row_pos = (btk_cp.h + btk_frame) * i - PP_LIST(c)->scroll; + cairo_set_source_rgb(c->fd->c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(c->fd->c_ctx, 0, row_pos, c->pa.w, btk_cp.h); + cairo_fill(c->fd->c_ctx); + write_label(c->fd->c_ctx, + 0, + row_pos, + c->pa.w, + BTK_JUSTIFY_LEFT, + *(PP_LIST(c)->items) + i * PP_LIST(c)->items_w, + clt); + } +} + +void +render_mark(btk_cell_t *c, cairo_t *c_ctx) +{ + fill_cell(c, c_ctx, btk_cl_pass_off); + + btk_rgb_t clt, cl = {0.0f, 0.0f, 0.0f}; + if ((c->state & BTK_CELL_STATE_BELL)) { + cl = btk_cl_bell; + clt = btk_cl_text_bell; + } else if ((c->state & BTK_CELL_STATE_ON)) { + cl = btk_cl_pass_on; + clt = btk_cl_text_hilight; + } else { + clt = c->state & BTK_CELL_STATE_DISABLED ? + btk_cl_text_disabled : btk_cl_text_normal; + } + + if ((c->state & (BTK_CELL_STATE_BELL | BTK_CELL_STATE_ON))) { + cairo_set_source_rgb(c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(c_ctx, + c->pa.x + btk_frame, + c->pa.y + btk_frame, + c->pa.w - 2 * btk_frame, + c->pa.h - 2 * btk_frame); + cairo_fill(c_ctx); + } + + write_label(c_ctx, c->pa.x, c->pa.y, c->pa.w, PP_MARK(c)->justify, c->label, clt); +} + +void +render_switch(btk_cell_t *c, cairo_t *c_ctx) +{ + btk_rgb_t clt, cl; + if ((c->state & BTK_CELL_STATE_DISABLED)) { + cl = btk_cl_dyn_disabled; + clt = btk_cl_text_disabled; + } else if ((c->state & BTK_CELL_STATE_PRESSED)) { + cl = btk_cl_dyn_pressed; + clt = btk_cl_text_hilight; + } else { + cl = btk_cl_dyn_idle; + clt = btk_cl_text_normal; + } + + fill_cell(c, c_ctx, cl); + write_label(c_ctx, + c->pa.x, + c->pa.y, + c->pa.w - btk_cp.h, + BTK_JUSTIFY_LEFT, + c->label, + clt); + + /* state indicator */ + cl = (c->state & BTK_CELL_STATE_ON) ? btk_cl_pass_on : btk_cl_pass_off; + cairo_set_source_rgb(c_ctx, btk_cl_frame.r, btk_cl_frame.g, btk_cl_frame.b); + cairo_rectangle(c_ctx, + c->pa.x + c->pa.w - btk_cp.h + btk_padding, + c->pa.y + btk_padding, + btk_cp.h - 2 * btk_padding, + btk_cp.h - 2 * btk_padding); + cairo_fill(c_ctx); + cairo_set_source_rgb(c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(c_ctx, + c->pa.x + c->pa.w - btk_cp.h + btk_padding + btk_frame, + c->pa.y + btk_padding + btk_frame, + btk_cp.h - 2 * (btk_padding + btk_frame), + btk_cp.h - 2 * (btk_padding + btk_frame)); + cairo_fill(c_ctx); +} + +void +render_table(btk_cell_t *c) +{ + reset_field(c); + btk_pp_table_t *pp = PP_TABLE(c); + + /* horizontal then vertical scroll */ + guard_scroll(&pp->scroll.x, pp->tw, c->pa.w, 1, 0); + guard_scroll(&pp->scroll.y, btk_cp.h + btk_frame, c->pa.h, *pp->rows_n + 1, 0); + + int header_indent = pp->header ? 1 : 0; + + /* compute item range to render */ + int item_min = pp->scroll.y / (btk_cp.h + btk_frame); + int item_max = (pp->scroll.y + c->pa.h) / (btk_cp.h + btk_frame); + item_max = BTK_MIN(item_max, *pp->rows_n); + + /* just render label if there are no rows to draw */ + if (!*pp->rows_n) { + write_label(c->fd->c_ctx, + 0, + header_indent * (btk_cp.h + btk_frame), + c->pa.w, + BTK_JUSTIFY_LEFT, + c->label, + btk_cl_text_lolight); + goto header; + } + + /* render rows */ + btk_rgb_t cl, clt; + int row_pos; + for (int i = item_min; i < item_max; i++) { + if (i == pp->row_sel) { + if ((c->state & BTK_CELL_STATE_PRESSED)) { + cl = btk_cl_dyn_pressed; + clt = btk_cl_text_hilight; + } else { + clt = btk_cl_text_select; + cl = btk_cl_select; + } + } else { + cl = btk_cl_field; + clt = btk_cl_text_disabled; + if (pp->spot) + clt = btk_cl[*(*(PP_TABLE(c)->spot) + i)]; + } + row_pos = (btk_cp.h + btk_frame) * (i + header_indent) - pp->scroll.y; + render_table_row(c, btk_cell_table_get_item(c, 0, i), row_pos, cl, clt); + } + + /* render header */ +header: + if (pp->header) + render_table_row(c, pp->header, 0, btk_cl_pass_off, btk_cl_text_normal); +} + +void +render_table_row(btk_cell_t *c, + char *row_data_start, + int row_pos, + btk_rgb_t cl, + btk_rgb_t clt) +{ + btk_pp_table_t *pp = PP_TABLE(c); + char label[pp->items_w]; + btk_rgb_t clt2; + + for (int i = 0; i < pp->cols_n; i++) { + cairo_set_source_rgb(c->fd->c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(c->fd->c_ctx, + pp->cols_px[i] - pp->scroll.x, + row_pos, pp->cols_pw[i], + btk_cp.h); + cairo_fill(c->fd->c_ctx); + + clt2 = clt; + strcpy(label, row_data_start + i * pp->items_w); + if (label[0] == '\n' || label[0] == '\0') { + clt2 = btk_cl_text_lolight; + strncpy(label, "-/-", pp->items_w); + } + write_label(c->fd->c_ctx, + pp->cols_px[i] - pp->scroll.x, + row_pos, + pp->cols_pw[i], + BTK_JUSTIFY_LEFT, + label, + clt2); + } +} + +void +reset_field(btk_cell_t *c) +{ + if (!c->fd) { + btk_log_warning(BTK_WARNING_DRAW_NO_FIELD); + return; + } + + cairo_set_source_rgb(c->fd->c_ctx, btk_cl_empty.r, btk_cl_empty.g, btk_cl_empty.b); + cairo_paint(c->fd->c_ctx); +} + +void +write_label(cairo_t *c_ctx, + int px, + int py, + unsigned int pw, + int justify, + char *label, + btk_rgb_t clt) +{ + int n = strlen(label); + int max_n = (pw - 2 * label_offset.x) / glyph.w; + char cut_label[max_n]; + + /* make label fit in cell if needed */ + if (n > max_n) { + int caret; + if (justify == BTK_JUSTIFY_LEFT) { + caret = max_n - 3; + strncpy(cut_label, label, max_n); + } else { + caret = 0; + strncpy(cut_label, label + n - max_n, max_n); + } + cut_label[caret] = '.'; + cut_label[++caret] = '.'; + cut_label[++caret] = '.'; + cut_label[max_n] = '\0'; + } + + /* draw label */ + cairo_set_source_rgb(c_ctx, clt.r, clt.g, clt.b); + if (justify == BTK_JUSTIFY_LEFT) { + cairo_move_to(c_ctx, px + label_offset.x, py + label_offset.y); + } else { + int a = n > max_n ? max_n : n; + cairo_move_to(c_ctx, px + pw - label_offset.x - glyph.w * a, py + label_offset.y); + } + + char *l = n > max_n ? cut_label : label; + cairo_show_text(c_ctx, l); +} diff --git a/btk/btk-cell.c b/btk/btk-cell.c new file mode 100644 index 0000000..c7dae3f --- /dev/null +++ b/btk/btk-cell.c @@ -0,0 +1,712 @@ +#include +#include +#include + +#include + +#include "btk-cell.h" +#include "btk-log.h" +#include "btk-cell-render.c" + +void +btk_cell_set_button(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + char *label, + void (*func)(void)) +{ + btk_cell_set_empty(c, cx, cy, cw, 1); + c->group = BTK_CELL_GROUP_DYNAMIC; + c->type = BTK_CELL_TYPE_BUTTON; + c->lh = 1; + c->label = label; + + NEW_PP(btk_pp_button_t, c->pp); + PP_BUTTON(c)->func = func; +} + +void +btk_cell_set_editor(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + unsigned int ch, + char *text, + unsigned int text_w) +{ + btk_cell_set_empty(c, cx, cy, cw, ch); + c->group = BTK_CELL_GROUP_FIELD; + c->type = BTK_CELL_TYPE_EDITOR; + c->lh = 0; + c->label = "no data"; + + NEW_PP(btk_pp_editor_t, c->pp); + PP_EDITOR(c)->text = text; + PP_EDITOR(c)->text_w = text_w; + PP_EDITOR(c)->caret = 0; + PP_EDITOR(c)->caret_2d.x = 0; + PP_EDITOR(c)->caret_2d.y = 0; + PP_EDITOR(c)->scroll = 0; + PP_EDITOR(c)->par = NULL; +} + +void +btk_cell_set_empty(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + unsigned int ch) +{ + c->group = BTK_CELL_GROUP_PASSIVE; + c->type = BTK_CELL_TYPE_EMPTY; + c->state = BTK_CELL_STATE_INITIAL; + c->ca.x = cx; + c->ca.y = cy; + c->ca.w = cw; + c->ca.h = ch; + c->lh = 0; + c->pp = NULL; + c->fd = NULL; + c->label = NULL; +} + +void +btk_cell_set_mark(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + int justify, + char *label) +{ + btk_cell_set_empty(c, cx, cy, cw, 1); + c->type = BTK_CELL_TYPE_MARK; + c->lh = 1; + c->label = label; + + NEW_PP(btk_pp_mark_t, c->pp); + PP_MARK(c)->justify = justify; +} + +void +btk_cell_set_input(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + char *text, + unsigned int text_w) +{ + btk_cell_set_empty(c, cx, cy, cw, 1); + c->group = BTK_CELL_GROUP_DYNAMIC; + c->type = BTK_CELL_TYPE_INPUT; + c->lh = 1; + c->label = "-/-"; + + NEW_PP(btk_pp_input_t, c->pp); + PP_INPUT(c)->text = text; + PP_INPUT(c)->text_w = text_w; + PP_INPUT(c)->caret = 0; + PP_INPUT(c)->scroll = 0; +} + +void +btk_cell_set_list(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + unsigned int ch, + char **items, + unsigned int *items_n, + unsigned int items_w, + int **filter, + int **order, + int **spot, + void (*func_trigger)(int), + void (*func_sel)(int)) +{ + btk_cell_set_empty(c, cx, cy, cw, ch); + c->group = BTK_CELL_GROUP_FIELD; + c->type = BTK_CELL_TYPE_LIST; + c->label = "no data"; + + NEW_PP(btk_pp_list_t, c->pp); + PP_LIST(c)->items = items; + PP_LIST(c)->items_n = items_n; + PP_LIST(c)->items_w = items_w; + PP_LIST(c)->item_sel = -1; + PP_LIST(c)->filter = filter; + PP_LIST(c)->order = order; + PP_LIST(c)->spot = spot; + PP_LIST(c)->scroll = 0; + PP_LIST(c)->func_trigger = func_trigger; + PP_LIST(c)->func_sel = func_sel; +} + +void +btk_cell_set_switch(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + char *label, + void (*func)(int, btk_arg_t), + btk_arg_t func_args) +{ + btk_cell_set_empty(c, cx, cy, cw, 1); + c->group = BTK_CELL_GROUP_DYNAMIC; + c->type = BTK_CELL_TYPE_SWITCH; + c->lh = 1; + c->label = label; + + NEW_PP(btk_pp_switch_t, c->pp); + PP_SWITCH(c)->func = func; + PP_SWITCH(c)->func_args = func_args; +} + +void +btk_cell_set_table(btk_cell_t *c, + unsigned int cx, + unsigned int cy, + unsigned int cw, + unsigned int ch, + char *header, /* must be same item_w as items */ + char **items, + unsigned int items_w, + unsigned int cols_n, + unsigned int *rows_n, + int *cols_ena, + unsigned int *cols_cw, + unsigned int sc, + int **filter, + int **order, + int **spot, + void (*func_trigger)(int), + void (*func_sel)(int)) +{ + btk_cell_set_empty(c, cx, cy, cw, ch); + c->group = BTK_CELL_GROUP_FIELD; + c->type = BTK_CELL_TYPE_TABLE; + c->label = "no data"; + + NEW_PP(btk_pp_table_t, c->pp); + PP_TABLE(c)->header = header; + PP_TABLE(c)->items = items; + PP_TABLE(c)->items_w = items_w; + PP_TABLE(c)->rows_n = rows_n; + PP_TABLE(c)->cols_n = cols_n; + PP_TABLE(c)->row_sel = -1; + PP_TABLE(c)->cols_ena = cols_ena; + PP_TABLE(c)->cols_cw = cols_cw; + PP_TABLE(c)->sc = sc; + PP_TABLE(c)->tw = 0; + PP_TABLE(c)->filter = filter; + PP_TABLE(c)->order = order; + PP_TABLE(c)->spot = spot; + PP_TABLE(c)->scroll.x = 0; + PP_TABLE(c)->scroll.y = 0; + PP_TABLE(c)->func_trigger = func_trigger; + PP_TABLE(c)->func_sel = func_sel; + + PP_TABLE(c)->cols_pw = (unsigned int*)malloc(cols_n * sizeof(int)); + PP_TABLE(c)->cols_px = (unsigned int*)malloc(cols_n * sizeof(int)); +} + +/****************************************************************************************/ + +void +btk_cell_button_trigger(btk_cell_t *c, int button) +{ + if (button == 1 && PP_BUTTON(c)->func) + PP_BUTTON(c)->func(); +} + +void +btk_cell_destroy(btk_cell_t *c) +{ + if (c->pp) { + switch (c->type) { + case BTK_CELL_TYPE_TABLE: + free(PP_TABLE(c)->cols_pw); + free(PP_TABLE(c)->cols_px); + break; + case BTK_CELL_TYPE_EDITOR: + btk_text_destroy_par(PP_EDITOR(c)->par); + break; + default: + break; + } + free(c->pp); + } + free(c); +} + +void +btk_cell_draw(btk_cell_t *c, cairo_t *c_ctx) +{ + if (!c) + return; + if ((c->state & BTK_CELL_STATE_HIDDEN)) + return; + + switch(c->type) { + case BTK_CELL_TYPE_MARK: + render_mark(c, c_ctx); + break; + case BTK_CELL_TYPE_BUTTON: + render_button(c, c_ctx); + break; + case BTK_CELL_TYPE_SWITCH: + render_switch(c, c_ctx); + break; + case BTK_CELL_TYPE_LIST: + render_list(c); + break; + case BTK_CELL_TYPE_TABLE: + render_table(c); + break; + case BTK_CELL_TYPE_INPUT: + render_input(c, c_ctx); + break; + case BTK_CELL_TYPE_EDITOR: + render_editor(c); + break; + default: + fill_cell(c, c_ctx, btk_cl_empty); + break; + }; +} + +void +btk_cell_editor_input_button(btk_cell_t *c, int button, int combo, btk_pos_t pt) +{ + /* scrolling if appropriate button is clicked */ + switch (button) { + case 1: + break; + case 4: + PP_EDITOR(c)->scroll -= btk_scroll_speed; + return; + case 5: + PP_EDITOR(c)->scroll += btk_scroll_speed; + return; + default: + return; + } + + /* caret positioning */ + if (pt.x < label_offset.x || pt.x >= c->pa.w - label_offset.x || + pt.y < label_offset.y - glyph.h) + return; + + PP_EDITOR(c)->caret_2d.x = (pt.x - label_offset.x) / glyph.w; + PP_EDITOR(c)->caret_2d.y = (pt.y - label_offset.y + PP_EDITOR(c)->scroll + glyph.h) / (glyph.h + btk_text_spacing); + PP_EDITOR(c)->caret_2d = btk_text_guard_2d_caret(PP_EDITOR(c)->par, PP_EDITOR(c)->caret_2d); + PP_EDITOR(c)->caret = btk_text_caret_2dto1d(PP_EDITOR(c)->par, PP_EDITOR(c)->caret_2d); + //btk_text_caret_1dto2d(PP_EDITOR(c)->par, PP_EDITOR(c)->caret); +} + +void +btk_cell_editor_input_key(btk_cell_t *c, uint32_t key) +{ + btk_pp_editor_t *pp = PP_EDITOR(c); + int caret_type, modif = 0; + + switch (key) { + case 0xff08: + pp->caret -= btk_text_del_char(pp->text, pp->caret, 0); + caret_type = 0; + modif = 1; + break; + case 0xff50: + pp->caret_2d.x = 0; + caret_type = 1; + break; + case 0xff51: + pp->caret--; + caret_type = 0; + break; + case 0xff52: + pp->caret_2d.y--; + caret_type = 1; + break; + case 0xff53: + pp->caret++; + caret_type = 0; + break; + case 0xff54: + pp->caret_2d.y++; + caret_type = 1; + break; + case 0xff57: + pp->caret_2d.x = pp->text_w; + caret_type = 1; + break; + case 0xffff: + btk_text_del_char(pp->text, pp->caret, 1); + caret_type = 0; + modif = 1; + break; + case 0xff0d: + pp->caret += btk_text_insert_char(pp->text, '\n', pp->caret, pp->text_w); + caret_type = 0; + modif = 1; + break; + case 0xff09: + pp->caret += btk_text_insert_char(pp->text, '\t', pp->caret, pp->text_w); + caret_type = 0; + modif = 1; + break; + default : + /* only allow ascii printable chars */ + if (key < 32 || key >= 127) + return; + pp->caret += btk_text_insert_char(pp->text, key, pp->caret, pp->text_w); + caret_type = 0; + modif = 1; + break; + } + + if (modif) { + btk_cell_editor_update_text(c); + } + + if (caret_type) { + pp->caret_2d = btk_text_guard_2d_caret(pp->par, pp->caret_2d); + pp->caret = btk_text_caret_2dto1d(pp->par, pp->caret_2d); + } else { + pp->caret = btk_text_guard_1d_caret(pp->text, pp->caret); + pp->caret_2d = btk_text_caret_1dto2d(pp->par, pp->caret); + } + + /* modify scroll to show caret if needed */ + if (pp->caret_2d.y * (glyph.h + btk_text_spacing) < pp->scroll) + pp->scroll = pp->caret_2d.y * (glyph.h + btk_text_spacing); + if (pp->caret_2d.y * (glyph.h + btk_text_spacing) >= pp->scroll + c->pa.h - 2 * label_offset.y) { + pp->scroll = (pp->caret_2d.y + 1) * (glyph.h + btk_text_spacing) - c->pa.h + label_offset.y; + } +} + +void +btk_cell_editor_reset_caret(btk_cell_t*c) +{ + PP_EDITOR(c)->caret = 0; + PP_EDITOR(c)->caret_2d.x = 0; + PP_EDITOR(c)->caret_2d.y = 0; + PP_EDITOR(c)->scroll = 0; +} + +void +btk_cell_editor_update_text(btk_cell_t *c) +{ + btk_pp_editor_t *pp = PP_EDITOR(c); + int max_n = (c->pa.w - 2 * label_offset.x) / glyph.w + 1; + + if (!pp->text) + return; + + if (pp->par) + btk_text_destroy_par(pp->par); + + pp->par = btk_text_get_par(pp->text, max_n); + pp->caret_2d = btk_text_caret_1dto2d(pp->par, pp->caret); + + /* modify scroll to show caret if needed */ + if (pp->caret_2d.y * (glyph.h + btk_text_spacing) < pp->scroll) + pp->scroll = pp->caret_2d.y * (glyph.h + btk_text_spacing); + if (pp->caret_2d.y * (glyph.h + btk_text_spacing) >= pp->scroll + c->pa.h - label_offset.y) { + pp->scroll = (pp->caret_2d.y + 1) * (glyph.h + btk_text_spacing) - c->pa.h + label_offset.y; + } +} + +btk_field_t* +btk_cell_field_create(cairo_surface_t *c_srf, btk_area_t a) +{ + btk_field_t *f = (btk_field_t *)malloc(sizeof(btk_field_t)); + f->c_srf = cairo_surface_create_for_rectangle(c_srf, a.x, a.y, a.w, a.h); + f->c_ctx = cairo_create(f->c_srf); + + cairo_set_font_options(f->c_ctx, font_opt); + cairo_set_font_size(f->c_ctx, btk_font_size); + cairo_select_font_face(f->c_ctx, + btk_font_name, + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + return f; +} + +void +btk_cell_field_destroy(btk_field_t *f) +{ + if (!f) + return; + + cairo_destroy(f->c_ctx); + cairo_surface_destroy(f->c_srf); + free(f); +} + +void +btk_cell_input_button(btk_cell_t *c, int button, int combo, btk_pos_t pt) +{ + if (button != 1) + return; + + if (pt.x >= 2 * label_offset.x + glyph.w && pt.x < c->pa.w - label_offset.x) + PP_INPUT(c)->caret = (pt.x - 2 * label_offset.x - glyph.w) / + glyph.w + PP_INPUT(c)->scroll; + + int text_n = strlen(PP_INPUT(c)->text); + if (PP_INPUT(c)->caret < 0) + PP_INPUT(c)->caret = 0; + if (PP_INPUT(c)->caret > text_n) + PP_INPUT(c)->caret = text_n; + + /* TODO combo clicks selections */ +} + +void +btk_cell_input_input_key(btk_cell_t *c, uint32_t key) +{ + btk_pp_input_t *pp = PP_INPUT(c); + + switch (key) { + case 0xff08: + pp->caret -= btk_text_del_char(pp->text, pp->caret, 0); + break; + case 0xff50: + pp->caret = 0; + break; + case 0xff51: + pp->caret--; + break; + case 0xff53: + pp->caret++; + break; + case 0xff57: + pp->caret = pp->text_w; + break; + case 0xffff: + btk_text_del_char(pp->text, pp->caret, 1); + break; + default : + /* only allow ascii printable chars */ + if (key < 32 || key >= 127) + return; + pp->caret += btk_text_insert_char(pp->text, key, pp->caret, pp->text_w); + return; + } + + int text_n = strlen(pp->text); + if (pp->caret < 0) + pp->caret = 0; + if (pp->caret > text_n) + pp->caret = text_n; +} + +void +btk_cell_input_reset_caret(btk_cell_t *c) +{ + PP_INPUT(c)->caret = 0; +} + +void +btk_cell_list_deselect(btk_cell_t *c) +{ + PP_LIST(c)->item_sel = -1; +} + +void +btk_cell_list_input_button(btk_cell_t *c, int button, int combo, btk_pos_t pt) +{ + btk_pp_list_t *pp = PP_LIST(c); + int prev_sel = pp->item_sel; + + /* scrolling if appropriate button is clicked */ + switch (button) { + case 4: + pp->scroll -= btk_scroll_speed; + return; + case 5: + pp->scroll += btk_scroll_speed; + return; + } + + /* filter out remaining unwanted inputs */ + if (button != 1) + return; + + /* find item under pointer */ + pp->item_sel = (pt.y + pp->scroll) / (btk_cp.h + btk_frame); + + /* deselect if out of bounds */ + if (pp->item_sel < 0 || pp->item_sel >= *(pp->items_n)) + pp->item_sel = -1; + + /* adjust scrolling so that select item is displayed in full */ + // TODO + + /* on selection change, exec sel func */ + if (prev_sel != pp->item_sel && pp->func_sel) + pp->func_sel(pp->item_sel); + + /* if double click, exec trigger func */ + if (combo == 2 && pp->func_trigger) + pp->func_trigger(pp->item_sel); +} + +int +btk_cell_list_get_sel(btk_cell_t *c) +{ + return PP_LIST(c)->item_sel; +} + +void +btk_cell_setup_font(cairo_t *c_ctx) +{ + font_opt = cairo_font_options_create(); + cairo_font_options_set_subpixel_order(font_opt, CAIRO_SUBPIXEL_ORDER_DEFAULT); + cairo_font_options_set_antialias(font_opt, CAIRO_ANTIALIAS_NONE); + cairo_font_options_set_hint_style(font_opt, CAIRO_HINT_STYLE_SLIGHT); + cairo_font_options_set_hint_metrics(font_opt, CAIRO_HINT_METRICS_ON); + + cairo_set_font_options(c_ctx, font_opt); + cairo_set_font_size(c_ctx, btk_font_size); + cairo_select_font_face(c_ctx, + btk_font_name, + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + + cairo_font_extents_t fe; + cairo_font_extents(c_ctx, &fe); + glyph.h = fe.height; + glyph.w = fe.max_x_advance; + label_offset.x = (btk_cp.h - fe.ascent) / 2 + btk_text_offset_tweak.x; + label_offset.y = (btk_cp.h - fe.ascent) / 2 + btk_text_offset_tweak.y + fe.ascent; +} + +void +btk_cell_switch_toggle(btk_cell_t *c, int button) +{ + if (button != 1) + return; + + c->state ^= BTK_CELL_STATE_ON; + if (PP_SWITCH(c)->func) + PP_SWITCH(c)->func(((c->state & BTK_CELL_STATE_ON) >> 4), PP_SWITCH(c)->func_args); +} + +void +btk_cell_table_deselect(btk_cell_t *c) +{ + PP_TABLE(c)->row_sel = -1; +} + +char* +btk_cell_table_get_item(btk_cell_t *c, int col, int row) +{ + if (col < 0 || row < 0 || col >= PP_TABLE(c)->cols_n || row >= *(PP_TABLE(c)->rows_n)) + return NULL; + + return *(PP_TABLE(c)->items) + + PP_TABLE(c)->items_w * (col + row * PP_TABLE(c)->cols_n); +} + +void +btk_cell_table_input_button(btk_cell_t *c, int button, int combo, btk_pos_t pt) +{ + btk_pp_table_t *pp = PP_TABLE(c); + int prev_sel = pp->row_sel; + + /* scrolling if appropriate button is clicked */ + switch (button) { + case 4: + pp->scroll.y -= btk_scroll_speed; + return; + case 5: + pp->scroll.y += btk_scroll_speed; + return; + case 6: + pp->scroll.x -= btk_scroll_speed; + return; + case 7: + pp->scroll.x += btk_scroll_speed; + return; + } + + /* TODO ugly af block, to redo */ + /* if click on header */ + if (pt.y < btk_cp.h) { + int ena; + /* find clicked column */ + for (int i = 0; i < pp->cols_n; i++) { + ena = pp->cols_ena ? pp->cols_ena[i] : 1; + if (ena && pt.x > pp->cols_px[i] && pt.x <= pp->cols_px[i] + pp->cols_pw[i]) { + switch (button) { + case 1: + pp->cols_cw[i]++; + break; + case 2: + /* TODO sorting ? */ + break; + case 3: + pp->cols_cw[i] -= pp->cols_cw[i] > 1 ? 1 : 0; + } + btk_cell_table_update_geometry(c); + break; + } + } + return; + } + + /* remaining item list actions */ + /* filter out unwanted inputs */ + if (button != 1) + return; + + /* find item under pointer */ + pp->row_sel = (pt.y + pp->scroll.y) / (btk_cp.h + btk_frame) -1; + + /* deselect if out of bounds */ + if (pp->row_sel < 0 || pp->row_sel >= *(pp->rows_n)) + pp->row_sel = -1; + + /* on selection change, exec sel func */ + if (prev_sel != pp->row_sel && pp->func_sel) + pp->func_sel(pp->row_sel); + + /* if double click, exec trigger func */ + if (combo == 2 && pp->func_trigger) + pp->func_trigger(pp->row_sel); +} + +void +btk_cell_table_update_geometry(btk_cell_t *c) +{ + btk_pp_table_t *pp = PP_TABLE(c); + unsigned int cw = 0; /* width in cell units */ + unsigned int pw = 0; /* total minimum pixel width */ + unsigned int ew = 0; /* extra width if tw > minimum width */ + int ena; + + /* calculate extra width in pixels */ + for (int i = 0; i < pp->cols_n; i++) { + ena = pp->cols_ena ? pp->cols_ena[i] : 1; + cw += ena ? pp->cols_cw[i] : 0; + } + pw = cw * (btk_cp.w + btk_frame) - btk_frame; + ew = pw < c->pa.w ? c->pa.w - pw : 0; + pp->tw = pw + ew; + + /* calculate each columns widths in pixels */ + for (int i = 0; i < pp->cols_n; i++) { + pp->cols_pw[i] = pp->cols_cw[i] * (btk_cp.w + btk_frame) - btk_frame; + pp->cols_pw[i] += pp->sc == i ? ew : 0; + } + + /* calculate columns positions */ + int pos = 0; + for (int i = 0; i < pp->cols_n; i++) { + pp->cols_px[i] = pos; + ena = pp->cols_ena ? pp->cols_ena[i] : 1; + pos += ena ? pp->cols_pw[i] + btk_frame : 0; + } +} diff --git a/btk/btk-cell.h b/btk/btk-cell.h new file mode 100644 index 0000000..c2058f0 --- /dev/null +++ b/btk/btk-cell.h @@ -0,0 +1,223 @@ +#include + +#include "btk-text.h" + +#define NEW_PP(type, pp) (pp = (type*)malloc(sizeof(type))) + +#define PP_BUTTON(c) ((btk_pp_button_t*)c->pp) +#define PP_EDITOR(c) ((btk_pp_editor_t*)c->pp) +#define PP_INPUT(c) ((btk_pp_input_t*)c->pp) +#define PP_LIST(c) ((btk_pp_list_t*)c->pp) +#define PP_MARK(c) ((btk_pp_mark_t*)c->pp) +#define PP_SWITCH(c) ((btk_pp_switch_t*)c->pp) +#define PP_TABLE(c) ((btk_pp_table_t*)c->pp) + +enum btk_cell_states { + BTK_CELL_STATE_INITIAL = 0, + BTK_CELL_STATE_HIDDEN = 1U << 0, + BTK_CELL_STATE_DISABLED = 1U << 1, + BTK_CELL_STATE_FOCUSED = 1U << 2, + BTK_CELL_STATE_PRESSED = 1U << 3, + BTK_CELL_STATE_ON = 1U << 4, + BTK_CELL_STATE_BELL = 1U << 5, + BTK_CELL_STATE_IN = 1U << 6 +}; + +enum btk_cell_group { + BTK_CELL_GROUP_PASSIVE, + BTK_CELL_GROUP_DYNAMIC, + BTK_CELL_GROUP_FIELD +}; + +enum btk_cell_type { + BTK_CELL_TYPE_EMPTY, + BTK_CELL_TYPE_MARK, + BTK_CELL_TYPE_GAUGE, + BTK_CELL_TYPE_BUTTON, + BTK_CELL_TYPE_SWITCH, + BTK_CELL_TYPE_WHEEL, + BTK_CELL_TYPE_LIST, + BTK_CELL_TYPE_TABLE, + BTK_CELL_TYPE_INPUT, + BTK_CELL_TYPE_PROMPT, + BTK_CELL_TYPE_EDITOR +}; + +enum btk_cell_table_col_state { + BTK_CELL_TABLE_COL_HIDDEN, + BTK_CELL_TABLE_COL_VISIBLE +}; + +typedef struct { + /* global properties */ + unsigned int group; + unsigned int type; + unsigned int state; + btk_area_t ca; /* geometry in cell coordinates */ + btk_area_t pa; /* geometry in pixel coordinates */ + void *pp; /* type specific properties to cast */ + btk_field_t *fd; /* for field group types only */ + int lh; /* lock pixel height from stretching */ + char *label; +} btk_cell_t; + +typedef struct { + void (*func)(void); +} btk_pp_button_t; + +typedef struct { + char *text; + unsigned int text_w; + btk_pos_t caret_2d; + int caret; + int scroll; + btk_par_t *par; +} btk_pp_editor_t; + +typedef struct { + char *text; + unsigned int text_w; /* max text size */ + int caret; + int scroll; +} btk_pp_input_t; + +typedef struct { + char **items; + unsigned int *items_n; + unsigned int items_w; + int item_sel; + int **filter; // TODO + int **order; // TODO + int **spot; + int scroll; + void (*func_trigger)(int); + void (*func_sel)(int); +} btk_pp_list_t; + +typedef struct { + int justify; +} btk_pp_mark_t; + +typedef struct { + void (*func)(int, btk_arg_t); + btk_arg_t func_args; +} btk_pp_switch_t; + +typedef struct { + char *header; + char **items; + unsigned int items_w; + unsigned int cols_n; + unsigned int *rows_n; + int row_sel; + int *cols_ena; /* hidden cols */ + unsigned int *cols_cw; /* width in cell units of each col */ + unsigned int *cols_pw; /* width in picels of each col */ + unsigned int *cols_px; /* position of each col in pixels */ + unsigned int sc; /* stretch col */ + unsigned int tw; /* columns total pixel width */ + int **filter; // TODO + int **order; // TODO + int **spot; + btk_pos_t scroll; + void (*func_trigger)(int); + void (*func_sel)(int); +} btk_pp_table_t; + +void btk_cell_set_button (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + char *, + void (*)(void)); + +void btk_cell_set_editor (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + unsigned int, + char *, + unsigned int); + +void btk_cell_set_empty (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + unsigned int); + +void btk_cell_set_input (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + char *, + unsigned int); + +void btk_cell_set_list (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + unsigned int, + char **, + unsigned int *, + unsigned int, + int **, + int **, + int **, + void (*)(int), + void (*)(int)); + +void btk_cell_set_mark (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + int, + char *); + +void btk_cell_set_switch (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + char *, + void (*)(int, btk_arg_t), + btk_arg_t); + +void btk_cell_set_table (btk_cell_t *, + unsigned int, + unsigned int, + unsigned int, + unsigned int, + char *, + char **, + unsigned int, + unsigned int, + unsigned int *, + int *, + unsigned int *, + unsigned int, + int **, + int **, + int **, + void (*)(int), + void (*)(int)); + +void btk_cell_button_trigger (btk_cell_t *, int); +void btk_cell_destroy (btk_cell_t *); +void btk_cell_draw (btk_cell_t *, cairo_t *); +void btk_cell_editor_input_button (btk_cell_t *, int, int, btk_pos_t); +void btk_cell_editor_input_key (btk_cell_t *, uint32_t); +void btk_cell_editor_reset_caret (btk_cell_t*); +void btk_cell_editor_update_text (btk_cell_t *); +btk_field_t* btk_cell_field_create (cairo_surface_t *, btk_area_t); +void btk_cell_input_button (btk_cell_t *, int, int, btk_pos_t); +void btk_cell_input_input_key (btk_cell_t *, uint32_t); +void btk_cell_input_reset_caret (btk_cell_t *); +void btk_cell_field_destroy (btk_field_t *); +void btk_cell_list_deselect (btk_cell_t*); +void btk_cell_list_input_button (btk_cell_t *, int, int, btk_pos_t); +int btk_cell_list_get_sel (btk_cell_t *); +void btk_cell_setup_font (cairo_t *c_ctx); +void btk_cell_switch_toggle (btk_cell_t *, int); +void btk_cell_table_deselect (btk_cell_t*); +char* btk_cell_table_get_item (btk_cell_t *, int, int); +void btk_cell_table_input_button (btk_cell_t *, int, int, btk_pos_t); +void btk_cell_table_update_geometry (btk_cell_t *); diff --git a/btk/btk.c b/btk/btk.c new file mode 100644 index 0000000..43c5951 --- /dev/null +++ b/btk/btk.c @@ -0,0 +1,359 @@ +#include +#include +#include +#include + +#include "btk.h" +#include "btk-log.h" + +void event_button_press (btk_session_t *, xcb_button_press_event_t *); +void event_button_release (btk_session_t *, xcb_button_release_event_t *); +void event_client_message (btk_session_t *, xcb_client_message_event_t *); +void event_enter_window (btk_session_t *, xcb_enter_notify_event_t *); +void event_expose (btk_session_t *, xcb_expose_event_t *); +void event_key_press (btk_session_t *, xcb_key_press_event_t *); +void event_leave_window (btk_session_t *, xcb_leave_notify_event_t *); +void event_pointer_motion (btk_session_t *, xcb_motion_notify_event_t *); +int get_btk_window_id (btk_session_t *, xcb_window_t); + +void +btk_close(btk_session_t *s) +{ + for (int i = 0; i < s->windows_n; i++) + if (!s->windows[i]) + btk_window_destroy(s->windows[i]); + + xcb_disconnect(s->x_con); + btk_log(BTK_LOG_SESSION_CLOSE); +} + +void +btk_loop(btk_session_t *s, btk_window_t **w, int windows_n) +{ + if (!w || !windows_n) { + btk_log_warning(BTK_WARNING_EMPTY_LOOP); + btk_log(BTK_LOG_EVENT_LOOP_OUT); + return; + } + + s->windows = w; + s->windows_n = windows_n; + s->window_focus = 0; + s->window_press = -1; + + /* set initial cell geometry and fonts */ + for (int i = 0; i < windows_n; i++) { + btk_cell_setup_font(w[i]->c_ctx); + btk_window_init_cells(w[i]); + } + + /* actual event loop */ + btk_log(BTK_LOG_EVENT_LOOP_IN); + xcb_generic_event_t *x_ev; + while ((x_ev = xcb_wait_for_event(s->x_con))) { + switch (x_ev->response_type & ~0x80) { + case XCB_EXPOSE: + event_expose(s, (xcb_expose_event_t*)x_ev); + break; + case XCB_MOTION_NOTIFY: + event_pointer_motion(s, (xcb_motion_notify_event_t*)x_ev); + break; + case XCB_BUTTON_PRESS: + event_button_press(s, (xcb_button_press_event_t*)x_ev); + break; + case XCB_BUTTON_RELEASE: + event_button_release(s, (xcb_button_release_event_t*)x_ev); + break; + case XCB_KEY_PRESS: + event_key_press(s, (xcb_key_press_event_t*)x_ev); + break; + case XCB_ENTER_NOTIFY: + event_enter_window(s, (xcb_enter_notify_event_t*)x_ev); + break; + case XCB_LEAVE_NOTIFY: + event_leave_window(s, (xcb_leave_notify_event_t*)x_ev); + break; + case XCB_CLIENT_MESSAGE: + event_client_message(s, (xcb_client_message_event_t*)x_ev); + break; + default: + btk_log(BTK_LOG_GENERIC_EVENT_NOTIFY); + break; + } + free(x_ev); + xcb_flush(s->x_con); + } + btk_log(BTK_LOG_EVENT_LOOP_OUT); +} + +void +btk_map(btk_session_t *s, btk_window_t *w) +{ + w->state |= BTK_WINDOW_STATE_MAPPED; + xcb_map_window(s->x_con, w->x_win); + xcb_flush(s->x_con); + btk_log(BTK_LOG_WINDOW_MAP); +} + +btk_session_t* +btk_open() +{ + btk_session_t *s = (btk_session_t*)malloc(sizeof(btk_session_t)); + + /* base X stuff */ + s->x_con = xcb_connect(NULL, NULL); + if (!s->x_con) + btk_log_error(BTK_ERROR_X_CONNECTION); + + s->x_scr = xcb_setup_roots_iterator(xcb_get_setup(s->x_con)).data; + if (!s->x_scr) + btk_log_error(BTK_ERROR_X_SCREEN); + + /* setup x visual type for cairo surfaces */ + /* magic function from documentation */ + xcb_depth_iterator_t x_di = xcb_screen_allowed_depths_iterator(s->x_scr); + for (; x_di.rem; xcb_depth_next(&x_di)) { + xcb_visualtype_iterator_t x_vi; + x_vi = xcb_depth_visuals_iterator(x_di.data); + for (; x_vi.rem; xcb_visualtype_next(&x_vi)) { + if (s->x_scr->root_visual == x_vi.data->visual_id) { + s->x_vis = x_vi.data; + break; + } + } + } + if (!s->x_vis) + btk_log_error(BTK_ERROR_X_VISUAL); + + /* setup keysym table for keyboard input */ + const xcb_setup_t *x_stp = xcb_get_setup(s->x_con); + xcb_get_keyboard_mapping_reply_t* x_km = + xcb_get_keyboard_mapping_reply( + s->x_con, + xcb_get_keyboard_mapping( + s->x_con, + x_stp->min_keycode, + x_stp->max_keycode - x_stp->min_keycode + 1), + NULL); + if (!x_km) + btk_log_error(BTK_ERROR_X_KEYSYMS); + xcb_keysym_t *x_ks = (xcb_keysym_t*)(x_km + 1); + s->x_nks = x_km->keysyms_per_keycode; + s->x_nkc = x_km->length / s->x_nks; + /* copy x_ks into s->x_ksm because x_ks breaks due to cairo for some reason */ + s->x_ksm = (xcb_keysym_t*)malloc(sizeof(xcb_keysym_t) * s->x_nkc * s->x_nks); + for (int i = 0; i < s->x_nkc * s->x_nks; i++) { + s->x_ksm[i] = x_ks[i]; + } + free(x_km); + + xcb_flush(s->x_con); + btk_log(BTK_LOG_SESSION_OPEN); + return s; +} + +void +btk_unmap(btk_session_t *s, btk_window_t *w) +{ + w->state &= ~BTK_WINDOW_STATE_MAPPED; + xcb_unmap_window(s->x_con, w->x_win); + xcb_flush(s->x_con); + btk_log(BTK_LOG_WINDOW_UNMAP); + + /* undo all and focus press states if any */ + if (w->cell_press >= 0) { + w->cells[w->cell_press].state &= ~BTK_CELL_STATE_PRESSED; + w->cell_press = -1; + } + if (w->cell_focus >= 0) { + w->cells[w->cell_focus].state &= ~BTK_CELL_STATE_FOCUSED; + w->cell_focus = -1; + } +} + +void +event_button_press(btk_session_t *s, xcb_button_press_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_BUTTON_PRESS); + + if (x_ev->time - s->combo_t < btk_click_speed && x_ev->detail == s->combo_b) { + s->combo_n++; + btk_log(BTK_LOG_EVENT_BUTTON_COMBO); + } else { + s->combo_n = 1; + s->combo_b = x_ev->detail; + btk_log(BTK_LOG_EVENT_BUTTON_COMBO_BREAK); + } + s->combo_t = x_ev->time; + + /* only applies for left click */ + if (s->combo_b == 1) + s->window_press = s->window_focus; + + btk_window_input_button(s->windows[s->window_focus], s->combo_b, s->combo_n); +} + +void +event_button_release(btk_session_t *s, xcb_button_release_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_BUTTON_RELEASE); + + /* only applies for left-middle-right click */ + if (x_ev->detail > 1) + return; + + /* undo all press states */ + btk_window_t *w = s->windows[s->window_press]; + s->window_press = -1; + + if (w->cell_press < 0) + return; + + btk_cell_t *c = &(w->cells[w->cell_press]); + c->state &= ~BTK_CELL_STATE_PRESSED; + btk_window_redraw_cell(w, w->cell_press); + w->cell_press = -1; +} + +void +event_client_message(btk_session_t *s, xcb_client_message_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_CLIENT_MESSAGE); + btk_window_t *w = s->windows[get_btk_window_id(s, x_ev->window)]; + + /* when receiving a WM_DELETE_WINDOW atom, execute func_kill of the + * corresponding window, it func_kill = NULL, destroy window */ + if (x_ev->type && 384) { /* 384 = id of atom for WM_WINDOW_DELETE */ + if (w->func_kill) { + w->func_kill(); + } else { + btk_window_destroy(w); + } + } + /* if anyone ever reads this, I know that it is a wrong method, + * that I first need to check to an WM_PROTOCOL atom then for + * WM_DELETE_WINDOW one */ +} + +void +event_enter_window(btk_session_t *s, xcb_enter_notify_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_WINDOW_ENTER); + + int w_id = get_btk_window_id(s, x_ev->event); + s->window_focus = w_id; + s->windows[w_id]->state |= BTK_WINDOW_STATE_FOCUSED; +} + +void +event_expose(btk_session_t *s, xcb_expose_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_EXPOSE); + btk_window_t *w = s->windows[get_btk_window_id(s, x_ev->window)]; + + /* c->fd need to be created when window is mapped to be able to display stuff */ + /* TODO better way to do the first field creations */ + if (!s->start) { + btk_window_init_cells(w); + s->start = 1; + } + + btk_window_resize(w); + btk_window_redraw(w); +} + +void +event_key_press(btk_session_t *s, xcb_key_press_event_t *x_ev) +{ + /* XCB_MOD_MASK_SHIFT (1) : shift + * XCB_MOD_MASK_LOCK (2) : caps + * XCB_MOD_MASK_CONTROL (4) : ctrl + * XCB_MOD_MASK_1 (8) : alt + * XCB_MOD_MASK_2 (16) : numlock + * XCB_MOD_MASK_3 (32) : ??? + * XCB_MOD_MASK_4 (64) : super + * XCB_MOD_MASK_5 (128) : alt-gr */ + + btk_log(BTK_LOG_EVENT_KEY_PRESS); + + unsigned int keysym_mod = 0; + if (((x_ev->state & XCB_MOD_MASK_LOCK) >> 1) ^ (x_ev->state & XCB_MOD_MASK_SHIFT)) + keysym_mod = 1; + if ((x_ev->state & XCB_MOD_MASK_5)) + keysym_mod = 4; + unsigned int keysym = s->x_ksm[(x_ev->detail - 8) * s->x_nks + keysym_mod]; + + /* convert utf-8 numpad chars to equivalent ascii */ + if ((x_ev->state & XCB_MOD_MASK_2)) { + switch (keysym) { + case 0xffaf: keysym = '/'; break; + case 0xffaa: keysym = '*'; break; + case 0xffad: keysym = '-'; break; + case 0xff95: keysym = '7'; break; + case 0xff97: keysym = '8'; break; + case 0xff9a: keysym = '9'; break; + case 0xff96: keysym = '4'; break; + case 0xff9d: keysym = '5'; break; + case 0xff98: keysym = '6'; break; + case 0xff9c: keysym = '1'; break; + case 0xff99: keysym = '2'; break; + case 0xff9b: keysym = '3'; break; + case 0xff9e: keysym = '0'; break; + case 0xff9f: keysym = '.'; break; + case 0xffab: keysym = '+'; break; + default: break; + } + } + + /* custom keysym conversions for reasons */ + switch (keysym) { + case 0x00a7: keysym = 0x0000; break; + default: break; + } + + /* special cases that are keycode dependent only */ + switch (x_ev->detail) { + case 65: keysym = ' '; break; + default: break; + } + btk_window_input_key(s->windows[s->window_focus], keysym); +} + +void +event_leave_window(btk_session_t *s, xcb_leave_notify_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_WINDOW_LEAVE); + + /* remove all focuses */ + /* TODO + btk_window_t *w = s->windows[get_btk_window_id(s, x_ev->event)]; + if (w->cell_focus >= 0) { + btk_cell_t *c = &(w->cells[w->cell_focus]); + c->state &= ~(BTK_CELL_STATE_FOCUSED); + btk_window_redraw_cell_focus(w, c); + } + w->state &= ~(BTK_WINDOW_STATE_FOCUSED); + w->cell_focus = -1; + */ +} + +void +event_pointer_motion(btk_session_t *s, xcb_motion_notify_event_t *x_ev) +{ + btk_log(BTK_LOG_EVENT_POINTER_MOTION); + btk_pos_t pt; + pt.x = x_ev->event_x; + pt.y = x_ev->event_y; + btk_window_input_pointer(s->windows[s->window_focus], pt); +} + +int +get_btk_window_id(btk_session_t *s, xcb_window_t x_win) +{ + for (int i = 0; i < s->windows_n; i++) { + if (s->windows[i]->x_win == x_win) + return i; + } + btk_log_warning(BTK_WARNING_WINDOW_GET); + return -1; +} diff --git a/btk/btk.h b/btk/btk.h new file mode 100644 index 0000000..86b9da7 --- /dev/null +++ b/btk/btk.h @@ -0,0 +1,30 @@ +#include + +#include "btk-window.h" + +typedef struct { + xcb_connection_t *x_con; + int pad0; + xcb_screen_t *x_scr; + int pad1; + xcb_visualtype_t *x_vis; + int pad2; + xcb_keysym_t *x_ksm; /* keysym table */ + int pad3; + int x_nks; /* keysyms per keycode */ + int x_nkc; /* number of keycodes */ + btk_window_t **windows; + unsigned int windows_n; + int window_focus; + int window_press; + xcb_timestamp_t combo_t; /* timestamp of last click */ + int combo_n; /* number of successive clicks */ + int combo_b; /* button used for combo */ + int start; /* TODO, see expose event handler */ +} btk_session_t; + +void btk_close (btk_session_t *); +void btk_loop (btk_session_t *, btk_window_t **, int); +void btk_map (btk_session_t *, btk_window_t *); +btk_session_t* btk_open (); +void btk_unmap (btk_session_t *, btk_window_t *);