mii_emu/libmui/mui/mui_cdef_listbox.c
Michel Pollet ad86adfea4
Mostly libmui changes
+ Updated libmui
+ Typehead in the standard file dialog
+ More documentation in the header file
+ Made regexp optional, added a new way to specify suffix list

Signed-off-by: Michel Pollet <buserror@gmail.com>
2024-02-04 09:09:28 +00:00

365 lines
9.8 KiB
C

/*
* mui_cdef_listbox.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include "mui.h"
#include "cg.h"
typedef struct mui_listbox_control_t {
mui_control_t control;
struct mui_control_t * scrollbar;
int32_t scroll;
uint8_t elem_height;
mui_listbox_elems_t elems;
mui_ldef_p ldef;
// to handle double-click
mui_time_t last_click;
// typehead search related
struct {
uint8_t enabled;
uint8_t timer;
char buf[32];
uint8_t index;
} typehead;
} mui_listbox_control_t;
extern const mui_control_color_t mui_control_color[MUI_CONTROL_STATE_COUNT];
static void
mui_listbox_draw(
mui_window_t * win,
mui_control_t * c,
mui_drawable_t *dr )
{
c2_rect_t f = c->frame;
c2_rect_offset(&f, win->content.l, win->content.t);
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(mui_control_color[c->state].frame));
cg_rectangle(cg, f.l, f.t,
c2_rect_width(&f), c2_rect_height(&f));
cg_stroke(cg);
{
c2_rect_t clip = f;
c2_rect_inset(&clip, 1, 1);
mui_drawable_clip_push(dr, &clip);
}
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
uint32_t top_element = lb->scroll / lb->elem_height;
uint32_t bottom_element = top_element + 1 +
(c2_rect_height(&f) / lb->elem_height);
// printf("%s draw from %d to %d\n", __func__, top_element, bottom_element);
mui_font_t * icons = mui_font_find(win->ui, "icon_small");
mui_font_t * main = mui_font_find(win->ui, "main");
mui_color_t highlight = win->ui->color.highlight;
for (unsigned int ii = top_element;
ii < lb->elems.count && ii < bottom_element; ii++) {
c2_rect_t ef = f;
ef.b = ef.t + lb->elem_height;
c2_rect_offset(&ef, 0, ii * lb->elem_height - lb->scroll);
if (ii == c->value) {
struct cg_ctx_t * cg = mui_drawable_get_cg(dr);
cg_set_line_width(cg, 1);
cg_set_source_color(cg, &CG_COLOR(highlight));
cg_rectangle(cg, ef.l, ef.t,
c2_rect_width(&ef), c2_rect_height(&ef));
cg_fill(cg);
}
ef.l += 8;
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (lb->elems.e[ii].icon[0])
mui_font_text_draw(icons, dr, ef.tl, lb->elems.e[ii].icon, 0,
mui_control_color[e->disabled ?
MUI_CONTROL_STATE_DISABLED : 0].text);
ef.l += 26;
mui_font_text_draw(main, dr, ef.tl, e->elem, 0,
mui_control_color[e->disabled ?
MUI_CONTROL_STATE_DISABLED : 0].text);
}
mui_drawable_clip_pop(dr);
}
/* mui_timer used to 'cancel' the typehead if a certain delay has lapsed */
static mui_time_t
mui_listbox_typehead_timer(
struct mui_t * mui,
mui_time_t now,
void * param)
{
mui_listbox_control_t *lb = param;
lb->typehead.enabled = 0;
lb->typehead.timer = 0xff;
lb->typehead.index = 0;
// printf("typehead: cancelled\n");
return 0;
}
static int32_t
mui_listbox_typehead(
mui_listbox_control_t *lb,
mui_event_t * ev)
{
if (ev->key.key < 32 || ev->key.key > 127)
return false;
if (!lb->typehead.enabled) {
lb->typehead.enabled = 1;
lb->typehead.index = 0;
lb->typehead.timer = mui_timer_register(
lb->control.win->ui,
mui_listbox_typehead_timer,
lb, MUI_TIME_MS * 1000);
}
// add character to the current prefix
if (lb->typehead.index < sizeof(lb->typehead.buf) - 1)
lb->typehead.buf[lb->typehead.index++] = ev->key.key;
lb->typehead.buf[lb->typehead.index] = 0;
// printf("typehead: %d '%s'\n", lb->typehead.index, lb->typehead.buf);
// reset cancel timer
mui_timer_reset(lb->control.win->ui,
lb->typehead.timer, mui_listbox_typehead_timer,
MUI_TIME_MS * 1000);
// we do the lookup twice, once with the case sensitive test, and if
// that find something that matches, we're good. If not, try to match
// the prefix in a non-case sensitive way in case the user doesn't know
// what he wants...
for (unsigned int ii = 0; ii < lb->elems.count; ii++) {
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (strncmp(e->elem, lb->typehead.buf, lb->typehead.index) == 0)
return ii - lb->control.value;
}
for (unsigned int ii = 0; ii < lb->elems.count; ii++) {
mui_listbox_elem_t *e = &lb->elems.e[ii];
if (strncasecmp(e->elem, lb->typehead.buf, lb->typehead.index) == 0)
return ii - lb->control.value;
}
// printf("typehead: no match\n");
return 0;
}
static bool
mui_listbox_key(
mui_control_t * c,
mui_event_t * ev)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
// printf("%s key: %d '%c'\n", __func__, ev->key.key, ev->key.key);
c2_rect_t f = c->frame;
c2_rect_offset(&f, -f.l, -f.t);
uint32_t page_size = (c2_rect_height(&f) / lb->elem_height)-1;
int delta = 0;
if (ev->modifiers & (MUI_MODIFIER_SUPER | MUI_MODIFIER_CTRL))
return false;
if (isalpha(ev->key.key))
delta = mui_listbox_typehead(lb, ev);
switch (ev->key.key) {
case MUI_KEY_UP: delta = -1; break;
case MUI_KEY_DOWN: delta = 1; break;
case MUI_KEY_PAGEUP: delta = -page_size; break;
case MUI_KEY_PAGEDOWN: delta = page_size; break;
}
if (!delta)
return false;
int nsel = c->value + delta;
if (nsel < 0)
nsel = 0;
if (nsel >= (int)lb->elems.count)
nsel = lb->elems.count - 1;
if (nsel != (int)c->value) {
c->value = nsel;
c2_rect_t e = c->frame;
e.b = e.t + lb->elem_height;
c2_rect_offset(&e, -e.l,
-e.t + (c->value * lb->elem_height));
c2_rect_t w = f;
c2_rect_offset(&w, 0, lb->scroll);
// printf(" e:%s f:%s\n", c2_rect_as_str(&e), c2_rect_as_str(&w));
if (e.b > w.b) {
lb->scroll = (e.b - c2_rect_height(&c->frame));
// printf(" over %d\n", lb->scroll);
}
if (e.t < w.t)
lb->scroll = e.t;
// printf(" scroll:%d\n", lb->scroll);
mui_control_set_value(lb->scrollbar, lb->scroll);
mui_control_inval(c);
mui_control_action(c, MUI_CONTROL_ACTION_VALUE_CHANGED,
&lb->elems.e[nsel]);
return true;
}
return false;
}
static bool
mui_cdef_event(
struct mui_control_t * c,
mui_event_t *ev)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
switch (ev->type) {
case MUI_EVENT_BUTTONDOWN: {
c2_rect_t f = c->frame;
c2_rect_offset(&f, c->win->content.l, c->win->content.t);
// uint32_t page_size = (c2_rect_height(&f) / lb->elem_height)-1;
int nsel = lb->scroll + (ev->mouse.where.y - f.t);
nsel /= lb->elem_height;
if (nsel < 0)
nsel = 0;
if (nsel >= (int)lb->elems.count)
nsel = lb->elems.count - 1;
if (nsel != (int)c->value) {
mui_control_set_value(c, nsel);
mui_control_action(c,
MUI_CONTROL_ACTION_VALUE_CHANGED,
&lb->elems.e[nsel]);
}
{
mui_time_t now = mui_get_time();
if ((now - lb->last_click) <
(MUI_TIME_MS * 300)) {
lb->last_click = 0;
if (lb->elems.e[nsel].disabled)
return true;
mui_control_action(c,
MUI_CONTROL_ACTION_SELECT,
&lb->elems.e[nsel]);
} else
lb->last_click = now;
}
return true;
} break;
case MUI_EVENT_KEYUP: {
if (mui_listbox_key(c, ev))
return true;
} break;
case MUI_EVENT_WHEEL: {
// printf("%s wheel delta %d\n", __func__, ev->wheel.delta);
lb->scroll += ev->wheel.delta * 20;
if (lb->scroll < 0)
lb->scroll = 0;
if (lb->scroll >
(int32_t)((lb->elems.count * lb->elem_height) -
c2_rect_height(&c->frame)))
lb->scroll = (lb->elems.count * lb->elem_height) -
c2_rect_height(&c->frame);
mui_control_set_value(lb->scrollbar, lb->scroll);
mui_control_inval(c);
return true;
} break;
}
return false;
}
bool
mui_cdef_listbox(
struct mui_control_t * c,
uint8_t what,
void * param)
{
switch (what) {
case MUI_CDEF_INIT:
break;
case MUI_CDEF_DISPOSE:
break;
case MUI_CDEF_DRAW: {
mui_drawable_t * dr = param;
mui_listbox_draw(c->win, c, dr);
} break;
case MUI_CDEF_EVENT: {
mui_event_t *ev = param;
return mui_cdef_event(c, ev);
} break;
}
return false;
}
static int
mui_listbox_sbar_action(
mui_control_t * c,
void * cb_param,
uint32_t what,
void * param)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)cb_param;
lb->scroll = mui_control_get_value(lb->scrollbar);
// printf("%s scroll %d\n", __func__, lb->scroll);
mui_control_inval(&lb->control);
return 0;
}
mui_listbox_elems_t *
mui_listbox_get_elems(
mui_control_t * c)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
return &lb->elems;
}
mui_control_t *
mui_listbox_new(
mui_window_t * win,
c2_rect_t frame,
uint32_t uid )
{
c2_rect_t lbf = frame;
c2_rect_t sb = frame;
mui_font_t * main = mui_font_find(win->ui, "main");
lbf.r -= main->size;
sb.l = sb.r - main->size;
mui_control_t *c = mui_control_new(
win, MUI_CONTROL_LISTBOX, mui_cdef_listbox,
lbf, NULL, uid, sizeof(mui_listbox_control_t));
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
lb->scrollbar = mui_scrollbar_new(win, sb, 0);
mui_control_set_action(lb->scrollbar, mui_listbox_sbar_action, c);
lb->elem_height = main->size + 2;
return c;
}
void
mui_listbox_prepare(
mui_control_t * c)
{
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
c2_rect_t content = C2_RECT_WH(0, 0,
c2_rect_width(&c->frame), c2_rect_height(&c->frame));
content.b = lb->elems.count * lb->elem_height;
c2_rect_offset(&content, 0, lb->scroll);
if (content.b < c2_rect_height(&c->frame)) {
c2_rect_offset(&content, 0, c2_rect_height(&c->frame) - content.b);
}
if (content.t > 0) {
c2_rect_offset(&content, 0, -content.t);
}
lb->scroll = content.t;
if (c2_rect_height(&content) > c2_rect_height(&c->frame)) {
mui_scrollbar_set_max(lb->scrollbar,
c2_rect_height(&content));
mui_control_set_value(lb->scrollbar, -lb->scroll);
mui_scrollbar_set_page(lb->scrollbar, c2_rect_height(&c->frame));
} else {
mui_scrollbar_set_max(lb->scrollbar, 0);
mui_control_set_value(lb->scrollbar, 0);
mui_scrollbar_set_page(lb->scrollbar, 0);
}
mui_control_inval(lb->scrollbar);
mui_control_inval(c);
}