Mercurial > molko
diff libmlk-ui/mlk/ui/gridmenu.c @ 433:862b15c3a3ae
ui: cleanup hierarchy
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sat, 15 Oct 2022 21:19:25 +0200 |
parents | src/libmlk-ui/ui/gridmenu.c@8f59201dc76b |
children | 773a082f0b91 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-ui/mlk/ui/gridmenu.c Sat Oct 15 21:19:25 2022 +0200 @@ -0,0 +1,287 @@ +/* + * gridmenu.c -- GUI grid menu + * + * Copyright (c) 2020-2022 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <math.h> +#include <string.h> + +#include <mlk/core/event.h> +#include <mlk/core/maths.h> +#include <mlk/core/painter.h> +#include <mlk/core/panic.h> +#include <mlk/core/texture.h> +#include <mlk/core/trace.h> + +#include "frame.h" +#include "label.h" +#include "gridmenu.h" +#include "theme.h" + +#define THEME(m) ((m)->theme ? (m)->theme : theme_default()) + +struct index { + unsigned int row; + unsigned int col; +}; + +static struct index +get_index(const struct gridmenu *menu) +{ + return (struct index) { + .row = menu->selected / menu->ncols, + .col = menu->selected % menu->ncols + }; +} + +static void +geometry(struct gridmenu *menu) +{ + const struct theme *theme = THEME(menu); + struct label label = { + .theme = theme, + .flags = LABEL_FLAGS_SHADOW + }; + unsigned int reqw = 0, reqh = 0, lw, lh; + + /* Compute which item has the bigger width/height to create a spacing. */ + menu->eltw = menu->elth = 0; + menu->spacew = menu->spaceh = 0; + + for (size_t i = 0; i < menu->itemsz; ++i) { + if (!(label.text = menu->items[i])) + continue; + + + label_query(&label, &lw, &lh); + + menu->eltw = fmax(menu->eltw, lw); + menu->elth = fmax(menu->elth, lh); + } + + /* Total texture size required to draw items. */ + reqw = (theme->padding * 2) + (menu->eltw * menu->ncols); + reqh = (theme->padding * 2) + (menu->elth * menu->nrows); + + /* + * Compute spacing between elements. We remove the padding because it + * is outside of the elements. + */ + if (reqw > menu->w) { + tracef("gridmenu width is too small: %u < %u", menu->w, reqw); + menu->spacew = 1; + } else if (menu->ncols > 1) { + reqw -= theme->padding * 2; + menu->spacew = (menu->w - reqw) / menu->ncols; + } + + if (reqh > menu->h) { + tracef("gridmenu height is too small: %u < %u", menu->h, reqh); + menu->spaceh = 1; + } else if (menu->nrows > 1) { + reqh -= theme->padding * 2; + menu->spaceh = (menu->h - reqh) / menu->nrows; + } +} + +static void +draw_frame(const struct gridmenu *menu) +{ + const struct frame f = { + .x = menu->x, + .y = menu->y, + .w = menu->w, + .h = menu->h, + .theme = menu->theme, + }; + + frame_draw(&f); +} + +static void +draw_labels(const struct gridmenu *menu) +{ + size_t pagesz, pagenr, item, c = 0, r = 0; + struct label label = {0}; + const struct theme *theme = THEME(menu); + + label.theme = theme; + label.flags = LABEL_FLAGS_SHADOW; + + /* + * Select the first top-left column based on the current selection and + * the number of rows/columns. + */ + pagesz = menu->nrows * menu->ncols; + pagenr = menu->selected / pagesz; + + for (size_t i = 0; i < pagesz; ++i) { + item = i + pagenr * pagesz; + + if (item >= menu->itemsz || !menu->items[item]) + continue; + + label.text = menu->items[item]; + label.x = menu->x + theme->padding + (c * menu->eltw) + (c * menu->spacew); + label.y = menu->y + theme->padding + (r * menu->elth) + (r * menu->spaceh); + + if (i == menu->selected % pagesz) + label.flags |= LABEL_FLAGS_SELECTED; + else + label.flags &= ~(LABEL_FLAGS_SELECTED); + + label_draw(&label); + + if (++c >= menu->ncols) { + ++r; + c = 0; + } + } +} + +static int +handle_keydown(struct gridmenu *menu, const struct event_key *key) +{ + assert(key->type == EVENT_KEYDOWN); + + const struct index idx = get_index(menu); + int validate = 0; + + switch (key->key) { + case KEY_UP: + if (idx.row > 0) + menu->selected -= menu->ncols; + break; + case KEY_RIGHT: + if (menu->selected + 1U < menu->itemsz) + menu->selected += 1; + break; + case KEY_DOWN: + if (idx.row + 1U < menu->itemsz / menu->ncols) + menu->selected += menu->ncols; + else + menu->selected = menu->itemsz - 1; + break; + case KEY_LEFT: + if (idx.col > 0) + menu->selected -= 1; + break; + case KEY_ENTER: + validate = 1; + break; + default: + break; + } + + return validate; +} + +static int +handle_clickdown(struct gridmenu *menu, const struct event_click *click) +{ + assert(click->type == EVENT_CLICKDOWN); + + const struct theme *theme = THEME(menu); + size_t pagesz, pagenr, selected, c = 0, r = 0; + int x, y; + + pagesz = menu->nrows * menu->ncols; + pagenr = menu->selected / pagesz; + + for (size_t i = 0; i < pagesz; ++i) { + x = menu->x + theme->padding + (c * menu->eltw) + (c * menu->spacew); + y = menu->y + theme->padding + (r * menu->elth) + (r * menu->spaceh); + + if (maths_is_boxed(x, y, menu->eltw, menu->elth, click->x, click->y)) { + selected = c + r * menu->ncols; + selected += pagesz * pagenr; + + if (selected < menu->itemsz) { + menu->selected = selected; + return 1; + } + } + + if (++c >= menu->ncols) { + ++r; + c = 0; + } + } + + return 0; +} + +void +gridmenu_init(struct gridmenu *menu, + unsigned int nr, + unsigned int nc, + const char * const *items, + size_t itemsz) +{ + assert(menu); + assert(nr); + assert(nc); + + memset(menu, 0, sizeof (*menu)); + + menu->nrows = nr; + menu->ncols = nc; + menu->items = items; + menu->itemsz = itemsz; +} + +void +gridmenu_resize(struct gridmenu *menu, int x, int y, unsigned int w, unsigned int h) +{ + assert(menu); + + menu->x = x; + menu->y = y; + menu->w = w; + menu->h = h; + + geometry(menu); +} + +int +gridmenu_handle(struct gridmenu *menu, const union event *ev) +{ + assert(menu); + assert(ev); + + switch (ev->type) { + case EVENT_KEYDOWN: + return handle_keydown(menu, &ev->key); + break; + case EVENT_CLICKDOWN: + return handle_clickdown(menu, &ev->click); + break; + default: + break; + } + + return 0; +} + +void +gridmenu_draw(const struct gridmenu *menu) +{ + assert(menu); + + draw_frame(menu); + draw_labels(menu); +}