2024-01-20 12:05:26 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2024-02-04 09:09:28 +00:00
|
|
|
// typehead search related
|
|
|
|
struct {
|
|
|
|
uint8_t enabled;
|
|
|
|
uint8_t timer;
|
|
|
|
char buf[32];
|
|
|
|
uint8_t index;
|
|
|
|
} typehead;
|
2024-01-20 12:05:26 +00:00
|
|
|
} 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");
|
2024-02-04 09:09:28 +00:00
|
|
|
mui_color_t highlight = win->ui->color.highlight;
|
2024-01-20 12:05:26 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-02-04 09:09:28 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2024-01-20 12:05:26 +00:00
|
|
|
static bool
|
|
|
|
mui_listbox_key(
|
|
|
|
mui_control_t * c,
|
|
|
|
mui_event_t * ev)
|
|
|
|
{
|
|
|
|
mui_listbox_control_t *lb = (mui_listbox_control_t *)c;
|
2024-02-04 09:09:28 +00:00
|
|
|
// printf("%s key: %d '%c'\n", __func__, ev->key.key, ev->key.key);
|
2024-01-20 12:05:26 +00:00
|
|
|
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;
|
2024-02-04 09:09:28 +00:00
|
|
|
if (isalpha(ev->key.key))
|
|
|
|
delta = mui_listbox_typehead(lb, ev);
|
2024-01-20 12:05:26 +00:00
|
|
|
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);
|
2024-02-04 09:09:28 +00:00
|
|
|
// printf(" e:%s f:%s\n", c2_rect_as_str(&e), c2_rect_as_str(&w));
|
2024-01-20 12:05:26 +00:00
|
|
|
if (e.b > w.b) {
|
|
|
|
lb->scroll = (e.b - c2_rect_height(&c->frame));
|
2024-02-04 09:09:28 +00:00
|
|
|
// printf(" over %d\n", lb->scroll);
|
2024-01-20 12:05:26 +00:00
|
|
|
}
|
|
|
|
if (e.t < w.t)
|
|
|
|
lb->scroll = e.t;
|
2024-02-04 09:09:28 +00:00
|
|
|
// printf(" scroll:%d\n", lb->scroll);
|
2024-01-20 12:05:26 +00:00
|
|
|
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;
|
2024-02-04 09:09:28 +00:00
|
|
|
case MUI_EVENT_KEYUP: {
|
2024-01-20 12:05:26 +00:00
|
|
|
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);
|
|
|
|
}
|