diff --git a/CMakeLists.txt b/CMakeLists.txt index a618b32..5a8dab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ add_library(${NAME} STATIC src/jframe.c src/jlist.c src/jscrolledlist.c + src/jbutton.c src/vec.c src/keymap.c ${ASSETS_${FXSDK_PLATFORM}} diff --git a/include/justui/jbutton.h b/include/justui/jbutton.h new file mode 100644 index 0000000..71b0ef8 --- /dev/null +++ b/include/justui/jbutton.h @@ -0,0 +1,59 @@ +//--- +// JustUI.jbutton: Touchable multi-state click button +//--- + +#ifndef _J_JBUTTON +#define _J_JBUTTON + +#include +#include + +#include + +/* jbutton: Button with multiple states + + This widget is your everyday interactible button. It responds to touch by + changing its background color for idle, focused and active states. It also + has a disabled states which can be used to make a "selected" visual. + + TODO: Allow jbutton it to hold any widget inside. + TODO: jbutton's focus state is untested since I only tested touch. + + Events: + * JBUTTON_TRIGGERED when activated. */ + +enum { + JBUTTON_IDLE, + JBUTTON_ACTIVE, + JBUTTON_DISABLED, + + JBUTTON_STATE_NUM, +}; + +typedef struct { + jwidget widget; + + /* Text shown on the button; not owned by the button */ + char const *text; + /* Rendering font */ + font_t const *font; + /* Colors for all states */ + color_t fg_colors[JBUTTON_STATE_NUM]; + color_t bg_colors[JBUTTON_STATE_NUM]; + /* Current state */ + int state; + +} jbutton; + +/* Event IDs */ +extern uint16_t JBUTTON_TRIGGERED; + +/* jbutton_create(): Create a button */ +jbutton *jbutton_create(char const *text, void *parent); + +/* Trivial properties */ +void jbutton_set_text(jbutton *b, char const *text); +void jbutton_set_font(jbutton *b, font_t const *font); +void jbutton_set_disabled(jbutton *b, bool disabled); + +#endif /* _J_JBUTTON */ diff --git a/src/jbutton.c b/src/jbutton.c new file mode 100644 index 0000000..66349af --- /dev/null +++ b/src/jbutton.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include + +#include + +J_DEFINE_WIDGET(jbutton, csize, render, event) +J_DEFINE_EVENTS(JBUTTON_TRIGGERED) + +static void jbutton_set_state(jbutton *b, int state) +{ + if((uint)state >= JBUTTON_STATE_NUM || b->state == state) + return; + b->state = state; + jwidget_set_background(b, b->bg_colors[state]); + b->widget.update = 1; +} + +jbutton *jbutton_create(char const *text, void *parent) +{ + if(jbutton_type_id < 0) + return NULL; + + jbutton *b = malloc(sizeof *b); + if(!b) + return NULL; + + jwidget_init(&b->widget, jbutton_type_id, parent); + jwidget_set_focus_policy(b, J_FOCUS_POLICY_ACCEPT); + + b->text = text ? text : ""; + b->font = dfont_default(); + +#if GINT_RENDER_MONO + b->fg_colors[JBUTTON_IDLE] = C_WHITE; + b->fg_colors[JBUTTON_ACTIVE] = C_WHITE; + b->fg_colors[JBUTTON_DISABLED] = C_BLACK; + b->bg_colors[JBUTTON_IDLE] = C_BLACK; + b->bg_colors[JBUTTON_ACTIVE] = C_BLACK; + b->bg_colors[JBUTTON_DISABLED] = C_WHITE; + jwidget_set_padding(b, 1, 2, 1, 2); +#endif +#if GINT_RENDER_RGB + b->fg_colors[JBUTTON_IDLE] = C_WHITE; + b->fg_colors[JBUTTON_ACTIVE] = C_WHITE; + b->fg_colors[JBUTTON_DISABLED] = C_RGB(8, 8, 8); + b->bg_colors[JBUTTON_IDLE] = C_BLACK; + b->bg_colors[JBUTTON_ACTIVE] = C_RGB(8, 8, 8); + b->bg_colors[JBUTTON_DISABLED] = C_RGB(24, 24, 24); + jwidget_set_padding(b, 2, 4, 2, 4); +#endif + + jbutton_set_state(b, JBUTTON_IDLE); + return b; +} + +void jbutton_set_text(jbutton *b, char const *text) +{ + b->text = text ? text : ""; + b->widget.dirty = 1; +} + +void jbutton_set_font(jbutton *b, font_t const *font) +{ + b->font = font ? font : dfont_default(); + b->widget.dirty = 1; +} + +void jbutton_set_disabled(jbutton *b, bool disabled) +{ + if(disabled) + jbutton_set_state(b, JBUTTON_DISABLED); + else if(b->state == JBUTTON_DISABLED) + jbutton_set_state(b, JBUTTON_IDLE); +} + +//--- +// Polymorphic widget operations +//--- + +void jbutton_poly_csize(void *b0) +{ + jbutton *b = b0; + int w, h; + dsize(b->text, b->font ? b->font : dfont_default(), &w, &h); + b->widget.w = w; + b->widget.h = h; +} + +void jbutton_poly_render(void *b0, int x, int y) +{ + jbutton *b = b0; + int cw = jwidget_content_width(b); + int ch = jwidget_content_height(b); + + int fg = b->fg_colors[b->state]; + font_t const *old_font = dfont(b->font); + dtext_opt(x + cw / 2, y + ch / 2, fg, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE, + b->text, -1); + dfont(old_font); +} + +bool jbutton_poly_event(void *b0, jevent e) +{ + jbutton *b = b0; + key_event_t ev = { .type = KEYEV_NONE }; + if(e.type == JWIDGET_KEY) + ev = e.key; + +#if J_CONFIG_TOUCH + bool accepts_touch = b->state != JBUTTON_DISABLED; + if((ev.type == KEYEV_TOUCH_DOWN || ev.type == KEYEV_TOUCH_DRAG || + ev.type == KEYEV_TOUCH_UP) && accepts_touch) { + int lx = ev.x - jwidget_absolute_padding_x(b); + int ly = ev.y - jwidget_absolute_padding_y(b); + uint cw = jwidget_padding_width(b); + uint ch = jwidget_padding_height(b); + bool inside = (uint)lx < cw && (uint)ly < ch; + + if(ev.type == KEYEV_TOUCH_DOWN) + jbutton_set_state(b, JBUTTON_ACTIVE); + if(ev.type == KEYEV_TOUCH_DRAG) + jbutton_set_state(b, inside ? JBUTTON_ACTIVE : JBUTTON_IDLE); + if(ev.type == KEYEV_TOUCH_UP && b->state == JBUTTON_ACTIVE) { + jbutton_set_state(b, JBUTTON_IDLE); + jwidget_emit(b, (jevent){ .type = JBUTTON_TRIGGERED }); + } + return true; + } +#endif + + if(ev.type == KEYEV_DOWN && (ev.key == KEY_EXE || ev.key == KEY_OK)) { + jwidget_emit(b, (jevent){ .type = JBUTTON_TRIGGERED }); + return true; + } + + return jwidget_poly_event(b, e); +}