diff --git a/btk/btk-utils.h b/btk/btk-utils.h new file mode 100644 index 0000000..aaa520f --- /dev/null +++ b/btk/btk-utils.h @@ -0,0 +1,75 @@ +#include +#include + +#define BTK_MIN(a, b) (a < b ? a : b) +#define BTK_MAX(a, b) (a > b ? a : b) + +enum btk_label_justify { + BTK_JUSTIFY_LEFT, + BTK_JUSTIFY_RIGHT +}; + +typedef struct { + float r, g, b; +} btk_rgb_t; + +typedef struct { + float r, g, b, a; +} btk_rgba_t; + +typedef struct { + int x, y; +} btk_pos_t; + +typedef struct { + unsigned int w, h; +} btk_size_t; + +typedef struct { + int x, y; + unsigned int w, h; +} btk_area_t; + +/* cairo related */ + +typedef struct { + cairo_surface_t *c_srf; + cairo_t *c_ctx; +} btk_field_t; + +typedef struct { + int i; + float f; + char *c; + void *p; +} btk_arg_t; + +/* xcb related */ + +typedef struct +{ + uint32_t flags; + int32_t x, y; + int32_t w, h; + int32_t min_w, min_h; + int32_t max_w, max_h; + int32_t w_inc, h_inc; + int32_t min_aspect_num, min_aspect_den; + int32_t max_aspect_num, max_aspect_den; + int32_t base_w, base_h; + uint32_t gravity; +} btk_wm_size_hint_t; + +enum btk_wm_size_hint_flags +{ + BTK_WM_SIZE_HINT_US_POSITION = 1U << 0, + BTK_WM_SIZE_HINT_US_SIZE = 1U << 1, + BTK_WM_SIZE_HINT_P_POSITION = 1U << 2, + BTK_WM_SIZE_HINT_P_SIZE = 1U << 3, + BTK_WM_SIZE_HINT_P_MIN_SIZE = 1U << 4, + BTK_WM_SIZE_HINT_P_MAX_SIZE = 1U << 5, + BTK_WM_SIZE_HINT_P_RESIZE_INC = 1U << 6, + BTK_WM_SIZE_HINT_P_ASPECT = 1U << 7, + BTK_WM_SIZE_HINT_BASE_SIZE = 1U << 8, + BTK_WM_SIZE_HINT_P_WIN_GRAVITY = 1U << 9 +}; diff --git a/btk/btk-window.c b/btk/btk-window.c new file mode 100644 index 0000000..501bfbf --- /dev/null +++ b/btk/btk-window.c @@ -0,0 +1,476 @@ +#include +#include +#include +#include +#include +#include + +#include "btk-window.h" +#include "btk-log.h" + +void draw_overlay (btk_window_t *, btk_area_t); +void update_cell_area (btk_window_t *, btk_cell_t *); +void update_stretch_area (btk_window_t *); + +btk_window_t* +btk_window_create(xcb_connection_t *x_con, + xcb_screen_t *x_scr, + xcb_visualtype_t *x_vis, + unsigned int cw, + unsigned int ch, + int sc, + int sr, + unsigned int ew, + unsigned int eh, + unsigned int cells_n, + void (*func_kill)(void)) +{ + btk_window_t *w = (btk_window_t*)malloc(sizeof(btk_window_t)); + w->cells = (btk_cell_t*)(malloc(sizeof(btk_cell_t) * cells_n)); + w->cells_n = cells_n; + w->func_kill = func_kill; + + /* states setup */ + w->state = BTK_WINDOW_STATE_INITIAL; + w->cell_focus = -1; + w->cell_press = -1; + w->focus_in = -1; + + /* geometry setup */ + w->cs.w = cw; + w->cs.h = ch; + w->es.w = sc < 0 ? 0 : ew; + w->es.h = sr < 0 ? 0 : eh; + w->ms.w = cw * (btk_cp.w + btk_frame) + btk_frame; + w->ms.h = ch * (btk_cp.h + btk_frame) + btk_frame; + w->ps.w = w->ms.w + w->es.w; + w->ps.h = w->ms.h + w->es.h; + w->sc.x = sc; + w->sc.y = sr; + update_stretch_area(w); + + /* xcb setup */ + uint32_t mask_vals[2] = { + x_scr->black_pixel, + XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_BUTTON_PRESS | + XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW | + XCB_EVENT_MASK_KEY_PRESS + }; + w->x_con = x_con; + w->x_win = xcb_generate_id(x_con); + xcb_create_window(x_con, + XCB_COPY_FROM_PARENT, + w->x_win, + x_scr->root, + 0, 0, + w->ps.w, w->ps.h, + 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, + x_scr->root_visual, + XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, + mask_vals); + + /* wm hints setup */ + btk_wm_size_hint_t x_hints = { + .flags = BTK_WM_SIZE_HINT_P_MIN_SIZE | BTK_WM_SIZE_HINT_P_MAX_SIZE, + .min_w = w->ms.w, + .min_h = w->ms.h + }; + if (sc < 0) x_hints.max_w = w->ms.w; + if (sr < 0) x_hints.max_h = w->ms.h; + xcb_change_property(w->x_con, + XCB_PROP_MODE_REPLACE, + w->x_win, + XCB_ATOM_WM_NORMAL_HINTS, + XCB_ATOM_WM_SIZE_HINTS, + 32, + sizeof(btk_wm_size_hint_t), + &x_hints); + + /* suscribe to WM_DELETE_WINDOW protocol from the wm */ + xcb_intern_atom_cookie_t x_cookie_protocol = + xcb_intern_atom(x_con, 0, strlen("WM_PROTOCOLS"), "WM_PROTOCOLS"); + xcb_intern_atom_reply_t *x_reply_protocol = + xcb_intern_atom_reply(x_con, x_cookie_protocol, NULL); + + xcb_intern_atom_cookie_t x_cookie_del = + xcb_intern_atom(x_con, 0, strlen("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW"); + xcb_intern_atom_reply_t *x_reply_del = + xcb_intern_atom_reply(x_con, x_cookie_del, NULL); + + xcb_change_property(x_con, + XCB_PROP_MODE_REPLACE, + w->x_win, + x_reply_protocol->atom, + XCB_ATOM_ATOM, + 32, + 1, + &(x_reply_del->atom)); + + /* cairo setup */ + w->c_srf = cairo_xcb_surface_create(x_con, w->x_win, x_vis, w->ps.w, w->ps.h); + w->c_ctx = cairo_create(w->c_srf); + + /* setup font */ + + xcb_flush(x_con); + cairo_surface_flush(w->c_srf); + btk_log(BTK_LOG_WINDOW_CREATE); + + return w; +} + +void +btk_window_destroy(btk_window_t *w) +{ + if (!w) { + btk_log_warning(BTK_WARNING_WINDOW_DESTROY); + return; + } + + cairo_destroy(w->c_ctx); + cairo_surface_destroy(w->c_srf); + free(w->cells); + free(w); + + btk_log(BTK_LOG_WINDOW_DESTROY); +} + +void +btk_window_disable(btk_window_t *w) +{ + w->state |= BTK_WINDOW_STATE_DISABLED; + btk_window_redraw(w); +} + +void +btk_window_enable(btk_window_t *w) +{ + w->state &= ~BTK_WINDOW_STATE_DISABLED; + btk_window_redraw(w); +} + + +void +btk_window_init_cells(btk_window_t *w) +{ + for (int i = 0; i < w->cells_n; i++) { + btk_window_update_cell_area(w, &(w->cells[i])); + } +} + +void +btk_window_input_button(btk_window_t *w, int button, int combo) +{ + if ((w->state & BTK_WINDOW_STATE_DISABLED)) + return; + if (w->cell_focus < 0) + return; + + btk_cell_t *c = &(w->cells[w->cell_focus]); + + if ((c->state & BTK_CELL_STATE_DISABLED) && button < 4) + return; + if (button == 1) { + c->state |= BTK_CELL_STATE_PRESSED; + w->cell_press = w->cell_focus; + } + + /* relative pointer position the the pressed cell */ + btk_pos_t rel_pt; + rel_pt.x = w->pt.x - c->pa.x; + rel_pt.y = w->pt.y - c->pa.y; + + /* forward press event to individual handlers for special actions */ + switch (c->type) { + case BTK_CELL_TYPE_BUTTON: + btk_cell_button_trigger(c, button); + break; + case BTK_CELL_TYPE_INPUT: + btk_cell_input_button(c, button, combo, rel_pt); + break; + case BTK_CELL_TYPE_LIST: + btk_cell_list_input_button(c, button, combo, rel_pt); + break; + case BTK_CELL_TYPE_SWITCH: + btk_cell_switch_toggle(c, button); + break; + case BTK_CELL_TYPE_TABLE: + btk_cell_table_input_button(c, button, combo, rel_pt); + break; + case BTK_CELL_TYPE_EDITOR: + btk_cell_editor_input_button(c, button, combo, rel_pt); + break; + default: + break; + } + + btk_cell_draw(c, w->c_ctx); + draw_overlay(w, c->pa); +} + +void +btk_window_input_key(btk_window_t *w, xcb_keysym_t key) +{ + if ((w->state & BTK_WINDOW_STATE_DISABLED)) + return; + if (w->cell_focus < 0) + return; + + btk_cell_t *c = &(w->cells[w->cell_focus]); + + switch (c->type) { + case BTK_CELL_TYPE_INPUT: + btk_cell_input_input_key(c, (uint32_t)key); + break; + case BTK_CELL_TYPE_EDITOR: + btk_cell_editor_input_key(c, (uint32_t)key); + break; + default: + return; + } + + btk_cell_draw(c, w->c_ctx); + draw_overlay(w, c->pa); +} + +/* updates the pointer position on the windows and the focused cell */ +void +btk_window_input_pointer(btk_window_t *w, btk_pos_t pos) +{ + if ((w->state & BTK_WINDOW_STATE_DISABLED)) + return; + + int prev_focus = w->cell_focus; + + w->pt.x = pos.x; + w->pt.y = pos.y; + + /* iteration instead of calculation to avoid frame interference in the + * case of thick frames */ + for (int i = 0; i < w->cells_n; i++) { + if (w->pt.x >= w->cells[i].pa.x && + w->pt.y >= w->cells[i].pa.y && + w->pt.x <= w->cells[i].pa.x + w->cells[i].pa.w && + w->pt.y <= w->cells[i].pa.y + w->cells[i].pa.h) { + w->cell_focus = i; + break; + } + } + /* if focus didn't change, leave */ + if (w->cell_focus == prev_focus) + return; + + /* unfocus previous cell if any */ + if (prev_focus >= 0) { + w->cells[prev_focus].state &= ~BTK_CELL_STATE_FOCUSED; + btk_window_redraw_cell_focus(w, &(w->cells[prev_focus])); + } + + /* focus new cell */ + w->cells[w->cell_focus].state |= BTK_CELL_STATE_FOCUSED; + btk_window_redraw_cell_focus(w, &(w->cells[w->cell_focus])); +} + +void +btk_window_redraw(btk_window_t *w) +{ + /* clear surface to redraw */ + cairo_set_source_rgb(w->c_ctx, btk_cl_frame.r, btk_cl_frame.g, btk_cl_frame.b); + cairo_paint(w->c_ctx); + + /* hilight frame around focused cell if any */ + if (w->cell_focus >= 0) { + btk_rgb_t cl = w->focus_in >= 0 ? btk_cl_frame_in : btk_cl_frame_focus; + cairo_set_source_rgb(w->c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(w->c_ctx, + w->cells[w->cell_focus].pa.x - btk_frame, + w->cells[w->cell_focus].pa.y - btk_frame, + w->cells[w->cell_focus].pa.w + 2 * btk_frame, + w->cells[w->cell_focus].pa.h + 2 * btk_frame); + cairo_fill(w->c_ctx); + } + + + /* render cells */ + for (int i = 0; i < w->cells_n; i++) + btk_cell_draw(&(w->cells[i]), w->c_ctx); + + /* disable overlay */ + btk_area_t ov; + ov.x = 0; + ov.y = 0; + ov.w = w->ps.w; + ov.h = w->ps.h; + draw_overlay(w, ov); + + cairo_surface_flush(w->c_srf); +} + +/* to be used to regenretate cells externally */ +void +btk_window_redraw_cell(btk_window_t *w, unsigned int c_id) +{ + if ((c_id) >= w->cells_n) { + btk_log_warning(BTK_WARNING_OUT_OF_BOUND_CELL); + return; + } + + btk_cell_draw(&(w->cells[c_id]), w->c_ctx); + draw_overlay(w, w->cells[c_id].pa); +} + +/* only redraw a specific cell and not the whole window + * unlike calling btk_winsow_redraw_cell, this function + * also redraws the frame around the cell */ +void +btk_window_redraw_cell_focus(btk_window_t *w, btk_cell_t *c) +{ + /* redraw frame */ + btk_rgb_t cl; + if ((c->state & BTK_CELL_STATE_IN)) { + cl = btk_cl_frame_in; + } else if ((c->state & BTK_CELL_STATE_FOCUSED)) { + cl = btk_cl_frame_focus; + } else { + cl = btk_cl_frame; + } + cairo_set_source_rgb(w->c_ctx, cl.r, cl.g, cl.b); + cairo_rectangle(w->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(w->c_ctx); + + /* redraw cell */ + btk_cell_draw(c, w->c_ctx); + + /* disable overlay */ + btk_area_t ov; + ov.x = c->pa.x - btk_frame; + ov.y = c->pa.y - btk_frame; + ov.w = c->pa.w + 2 * btk_frame; + ov.h = c->pa.h + 2 * btk_frame; + draw_overlay(w, ov); + + cairo_surface_flush(w->c_srf); +} + +/* only called by an expose event */ +int +btk_window_resize(btk_window_t *w) +{ + /* check if window geometry changed, update window size if it did */ + xcb_get_geometry_reply_t *x_geom; + xcb_get_geometry_cookie_t x_cookie = xcb_get_geometry(w->x_con, w->x_win); + x_geom = xcb_get_geometry_reply(w->x_con, x_cookie, NULL); + if (!x_geom) + return 0; + + if (w->ps.w == x_geom->width && w->ps.h == x_geom->height) + return 0; + + /* update window's geometry */ + w->ps.w = x_geom->width; + w->ps.h = x_geom->height; + w->es.w = w->ps.w - w->ms.w; + w->es.h = w->ps.h - w->ms.h; + update_stretch_area(w); + cairo_xcb_surface_set_size(w->c_srf, w->ps.w, w->ps.h); + + /* update cell's geometry */ + /* only for those after the stretch col and row */ + btk_cell_t *c; + for (int i = 0; i < w->cells_n; i++) { + c = &(w->cells[i]); + if (c->ca.x + c->ca.w >= w->sc.x || c->ca.y + c->ca.h >= w->sc.y) + btk_window_update_cell_area(w, c); + } + + cairo_surface_flush(w->c_srf); + btk_log(BTK_LOG_WINDOW_RESIZE); + return 1; +} + +void +btk_window_set_name(btk_window_t *w, char *name) +{ + xcb_change_property(w->x_con, + XCB_PROP_MODE_REPLACE, + w->x_win, + XCB_ATOM_WM_NAME, + XCB_ATOM_STRING, + 8, + strlen(name), + name); + + btk_log(BTK_LOG_WINDOW_RENAME); +} + +void +btk_window_update_cell_area(btk_window_t *w, btk_cell_t *c) +{ + unsigned int ex, ey, ew, eh = 0; /* extra to account for stretched columns & rows */ + + ex = c->ca.x > w->sc.x ? w->es.w : 0; + ey = c->ca.y > w->sc.y ? w->es.h : 0; + ew = c->ca.x <= w->sc.x && c->ca.x + c->ca.w > w->sc.x ? w->es.w : 0; + if (!(c->lh)) + eh = c->ca.y <= w->sc.y && c->ca.y + c->ca.h > w->sc.y ? w->es.h : 0; + + c->pa.x = c->ca.x * (btk_cp.w + btk_frame) + btk_frame + ex; + c->pa.y = c->ca.y * (btk_cp.h + btk_frame) + btk_frame + ey; + c->pa.w = c->ca.w * (btk_cp.w + btk_frame) - btk_frame + ew; + c->pa.h = c->ca.h * (btk_cp.h + btk_frame) - btk_frame + eh; + + /* field re-creation with correct size */ + if (c->group == BTK_CELL_GROUP_FIELD) { + if (c->fd) + btk_cell_field_destroy(c->fd); + c->fd = btk_cell_field_create(w->c_srf, c->pa); + } + + /* special geometry depending on cell type */ + switch (c->type) { + case BTK_CELL_TYPE_TABLE: + btk_cell_table_update_geometry(c); + break; + case BTK_CELL_TYPE_EDITOR: + btk_cell_editor_update_text(c); + break; + default: + break; + } +} + +/* add semi-transparent block overlay on areas to redraw if the window is disabled */ +void +draw_overlay(btk_window_t *w, btk_area_t a) +{ + if (!(w->state & BTK_WINDOW_STATE_DISABLED)) + return; + + if ((w->state & BTK_WINDOW_STATE_DISABLED)) { + cairo_set_source_rgba(w->c_ctx, + btk_cl_window_overlay.r, + btk_cl_window_overlay.g, + btk_cl_window_overlay.b, + btk_cl_window_overlay.a); + cairo_rectangle(w->c_ctx, a.x, a.y, a.w, a.h); + cairo_fill(w->c_ctx); + } +} + +void +update_stretch_area(btk_window_t *w) +{ + w->sa.x = w->sc.x * (btk_cp.w + btk_frame); + w->sa.y = w->sc.y * (btk_cp.h + btk_frame); + w->sa.w = w->sa.x + w->es.w + btk_cp.w; + w->sa.h = w->sa.x + w->es.h + btk_cp.h; +} diff --git a/btk/btk-window.h b/btk/btk-window.h new file mode 100644 index 0000000..8bf408b --- /dev/null +++ b/btk/btk-window.h @@ -0,0 +1,66 @@ +#include +#include + +#include "btk-cell.h" + +/* window states mask */ +enum btk_window_states { + BTK_WINDOW_STATE_INITIAL = 0, + BTK_WINDOW_STATE_MAPPED = 1U << 0, + BTK_WINDOW_STATE_DISABLED = 1U << 1, + BTK_WINDOW_STATE_FOCUSED = 1U << 2, + BTK_WINDOW_STATE_LOCKED = 1U << 3 +}; + +typedef struct { + /* xcb + cairo */ + xcb_connection_t *x_con; + int pad0; + xcb_window_t x_win; + cairo_surface_t *c_srf; + int pad1; + cairo_t *c_ctx; + int pad2; + /* self info */ + int state; + btk_size_t ps; /* actual pixel dimensions */ + btk_size_t ms; /* minimum pixel dimensions */ + btk_size_t es; /* extra pixel dimensions */ + btk_size_t cs; /* dimensions in cells */ + btk_pos_t sc; /* stretch row and column positions in cells */ + btk_area_t sa; /* area of the stretched core */ + btk_pos_t pt; /* pointer position in pixels */ + /* contents */ + btk_cell_t *cells; /* array of cell */ + unsigned int cells_n; /* number of cells */ + int cell_focus; /* id of currently focused cell */ + int cell_press; /* id of cell under the pointer when press */ + int focus_in; /* = 1 if the focus is 'inside' the cell */ + void (*func_kill)(void); +} btk_window_t; + +btk_window_t* btk_window_create (xcb_connection_t *, + xcb_screen_t *, + xcb_visualtype_t *, + unsigned int, + unsigned int, + int, + int, + unsigned int, + unsigned int, + unsigned int, + void (*)(void)); + +void btk_window_destroy (btk_window_t *); +void btk_window_disable (btk_window_t *); +void btk_window_enable (btk_window_t *); +void btk_window_init_cells (btk_window_t *); +void btk_window_input_button (btk_window_t *, int, int); +void btk_window_input_key (btk_window_t *, xcb_keysym_t); +void btk_window_input_pointer (btk_window_t *, btk_pos_t); +void btk_window_redraw (btk_window_t *); +void btk_window_redraw_cell (btk_window_t *, unsigned int); +void btk_window_redraw_cell_focus (btk_window_t *, btk_cell_t*); +int btk_window_resize (btk_window_t *); /* returns 1 if size actually changed */ +void btk_window_set_name (btk_window_t *, char *); +void btk_window_update_cell_area (btk_window_t *, btk_cell_t*);