Compare commits

...

3 commits

9 changed files with 292 additions and 25 deletions

View file

@ -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}}

59
include/justui/jbutton.h Normal file
View file

@ -0,0 +1,59 @@
//---
// JustUI.jbutton: Touchable multi-state click button
//---
#ifndef _J_JBUTTON
#define _J_JBUTTON
#include <justui/defs.h>
#include <justui/jwidget.h>
#include <gint/display.h>
/* 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 */

View file

@ -79,10 +79,10 @@ void jscene_render(jscene *scene);
next, and increasing within child index order within each group.
* For widgets with a stacked layout, only the active child is considered.
If content_only is set, this function only intersects with the content box.
Otherwise, an intersection with the full box (padding, border and margin
included) is performed. */
jwidget *jscene_widget_at(jscene *scene, int x, int y, bool content_only);
If exclude_mp is set, this function only intersects with the content and
padding box, excluding margins and borders. Otherwise, an intersection with
the full box (padding, border and margin included) is performed. */
jwidget *jscene_widget_at(jscene *scene, int x, int y, bool exclude_mp);
//---
// Events sent from the scene to the user

View file

@ -341,6 +341,8 @@ void jwidget_layout(void *scene_root);
/* jwidget_absolute_x(): Absolute x-position of a widget
jwidget_absolute_y(): Absolute y-position of a widget
jwidget_absolute_padding_x(): Absolute x-position of a widget's padding box
jwidget_absolute_padding_y(): Absolute y-position of a widget's padding box
jwidget_absolute_content_x(): Absolute x-position of a widget's content box
jwidget_absolute_content_y(): Absolute y-position of a widget's content box
@ -348,6 +350,8 @@ void jwidget_layout(void *scene_root);
with a full-screen scene. */
int jwidget_absolute_x(void *w);
int jwidget_absolute_y(void *w);
int jwidget_absolute_padding_x(void *w);
int jwidget_absolute_padding_y(void *w);
int jwidget_absolute_content_x(void *w);
int jwidget_absolute_content_y(void *w);
@ -361,6 +365,17 @@ int jwidget_absolute_content_y(void *w);
int jwidget_content_width(void *w);
int jwidget_content_height(void *w);
/* jwidget_width(): With of a widget's padding box
jwidget_height(): Height of a widget's padding box
These functions return the size of the padding box of a widget, which
includes the content and padding. These are the dimensions that appear as
the widget's size when there is a background. These dimensions are known
only after layout; calling these functions when the layout is not up-to-date
will return funny results. */
int jwidget_padding_width(void *w);
int jwidget_padding_height(void *w);
/* jwidget_full_width(): Width of a widget's margin box
jwidget_full_height(): Height of a widget's margin box

139
src/jbutton.c Normal file
View file

@ -0,0 +1,139 @@
#include <justui/jwidget.h>
#include <justui/jwidget-api.h>
#include <justui/jbutton.h>
#include <justui/config.h>
#include <stdlib.h>
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);
}

View file

@ -21,7 +21,7 @@ static int jfileselect_type_id = -1;
# define JFILESELECT_SCROLLBAR_WIDTH 1
# define JFILESELECT_INFO_SHORT 1
#elif GINT_RENDER_RGB
# define JFILESELECT_LINE_SPACING 4
# define JFILESELECT_LINE_SPACING (J_CONFIG_TOUCH ? 6 : 4)
# define JFILESELECT_SCROLLBAR_WIDTH 2
# define JFILESELECT_INFO_SHORT 0
#endif
@ -353,6 +353,8 @@ bool jfileselect_default_filter(struct dirent const *ent)
return false;
if(!strcmp(ent->d_name, "SAVE-F"))
return false;
if(!strcmp(ent->d_name, "AutoImport"))
return false;
if(!strcmp(ent->d_name, "."))
return false;
if(!strcmp(ent->d_name, ".."))
@ -360,7 +362,7 @@ bool jfileselect_default_filter(struct dirent const *ent)
return true;
}
static void jfileselect_select(jfileselect *fs, int index)
GUNUSED static void jfileselect_select(jfileselect *fs, int index)
{
/* Normalize out-of-bounds to -1 */
if(index < 0 || index >= fs->entry_count)

View file

@ -167,7 +167,7 @@ jrect jlist_selected_region(jlist *l)
// Polymorphic widget operations
//---
static int item_index_at(void *l0, int ly)
GUNUSED static int item_index_at(void *l0, int ly)
{
jlist *l = l0;
int y = 0;

View file

@ -97,10 +97,10 @@ void jscene_render(jscene *s)
jwidget *w =
jscene_widget_at(s, s->touch_last_x, s->touch_last_y, true);
if(w) {
int x1 = jwidget_absolute_content_x(w);
int y1 = jwidget_absolute_content_y(w);
int x2 = x1 + jwidget_content_width(w) - 1;
int y2 = y1 + jwidget_content_height(w) - 1;
int x1 = jwidget_absolute_padding_x(w);
int y1 = jwidget_absolute_padding_y(w);
int x2 = x1 + jwidget_padding_width(w) - 1;
int y2 = y1 + jwidget_padding_height(w) - 1;
drect_border(x1, y1, x2, y2, C_NONE, 1, C_RED);
dprint_opt(DWIDTH-1, 0, C_WHITE, C_RED, DTEXT_RIGHT, DTEXT_TOP,
@ -112,24 +112,31 @@ void jscene_render(jscene *s)
/* Find sub-widget based on w-local coordinates. */
static jwidget *jscene_widget_at_rec(
jwidget *w, int x, int y, bool content_only, bool visible_only)
jwidget *w, int x, int y, bool exclude_mp, bool visible_only)
{
if(!w)
return NULL;
jwidget *found;
jwidget_geometry const *g = jwidget_geometry_r(w);
/* Content box (this is what children's xy are measured against) */
int xC = g->margin.left + g->border.left + g->padding.left;
int yC = w->y + g->margin.top + g->border.top + g->padding.top;
int yC = g->margin.top + g->border.top + g->padding.top;
uint wC = jwidget_content_width(w);
uint hC = jwidget_content_height(w);
/* Padding box */
int xP = xC - g->padding.left;
int yP = yC - g->padding.top;
uint wP = wC + g->padding.left + g->padding.right;
uint hP = hC + g->padding.top + g->padding.bottom;
/* Full box (implicitly starts at 0,0) */
uint wF = jwidget_full_width(w);
uint hF = jwidget_full_height(w);
/* Check if we intersect w at all */
bool intersects;
if(content_only)
intersects = ((uint)(x - xC) < wC) && ((uint)(y - yC) < hC);
if(exclude_mp)
intersects = ((uint)(x - xP) < wP) && ((uint)(y - yP) < hP);
else
intersects = (uint)x < wF && (uint)y < hF;
if(!intersects)
@ -141,8 +148,8 @@ static jwidget *jscene_widget_at_rec(
if(stack->active < 0)
return w;
jwidget *child = w->children[stack->active];
found = jscene_widget_at_rec(child, x - xC, y - yC, content_only,
visible_only);
found = jscene_widget_at_rec(child, x - xC - child->x,
y - yC - child->y, exclude_mp, visible_only);
return found ? found : w;
}
@ -150,8 +157,8 @@ static jwidget *jscene_widget_at_rec(
for(int k = 0; k < w->child_count; k++) {
jwidget *child = w->children[k];
if(child->visible >= visible_only && child->floating) {
found = jscene_widget_at_rec(child, x - xC, y - yC, content_only,
visible_only);
found = jscene_widget_at_rec(child, x - xC - child->x,
y - yC - child->y, exclude_mp, visible_only);
if(found)
return found;
}
@ -161,8 +168,8 @@ static jwidget *jscene_widget_at_rec(
for(int k = 0; k < w->child_count; k++) {
jwidget *child = w->children[k];
if(child->visible >= visible_only && !child->floating) {
found = jscene_widget_at_rec(child, x - xC, y - yC, content_only,
visible_only);
found = jscene_widget_at_rec(child, x - xC - child->x,
y - yC - child->y, exclude_mp, visible_only);
if(found)
return found;
}
@ -172,9 +179,9 @@ static jwidget *jscene_widget_at_rec(
return w;
}
jwidget *jscene_widget_at(jscene *scene, int x, int y, bool content_only)
jwidget *jscene_widget_at(jscene *scene, int x, int y, bool exclude_mp)
{
return jscene_widget_at_rec(&scene->widget, x, y, content_only, true);
return jscene_widget_at_rec(&scene->widget, x, y, exclude_mp, true);
}
//---

View file

@ -570,13 +570,39 @@ void jwidget_layout(void *root0)
int jwidget_absolute_x(void *w0)
{
J_CAST(w)
return w ? w->x + jwidget_absolute_x(w->parent) : 0;
if(!w)
return 0;
jwidget_geometry const *g = jwidget_geometry_r(w);
return jwidget_absolute_content_x(w) -
g->padding.left - g->border.left - g->margin.left;
}
int jwidget_absolute_y(void *w0)
{
J_CAST(w)
return w ? w->y + jwidget_absolute_y(w->parent) : 0;
if(!w)
return 0;
jwidget_geometry const *g = jwidget_geometry_r(w);
return jwidget_absolute_content_y(w) -
g->padding.top - g->border.top - g->margin.top;
}
int jwidget_absolute_padding_x(void *w0)
{
J_CAST(w)
if(!w)
return 0;
jwidget_geometry const *g = jwidget_geometry_r(w);
return jwidget_absolute_content_x(w) - g->padding.left;
}
int jwidget_absolute_padding_y(void *w0)
{
J_CAST(w)
if(!w)
return 0;
jwidget_geometry const *g = jwidget_geometry_r(w);
return jwidget_absolute_content_y(w) - g->padding.top;
}
int jwidget_absolute_content_x(void *w0)
@ -617,6 +643,24 @@ int jwidget_content_height(void *w0)
- g->margin.bottom - g->border.bottom - g->padding.bottom;
}
int jwidget_padding_width(void *w0)
{
J_CAST(w)
jwidget_geometry const *g = jwidget_geometry_r(w);
return w->w - g->margin.left - g->border.left
- g->margin.right - g->border.right;
}
int jwidget_padding_height(void *w0)
{
J_CAST(w)
jwidget_geometry const *g = jwidget_geometry_r(w);
return w->h - g->margin.top - g->border.top
- g->margin.bottom - g->border.bottom;
}
int jwidget_full_width(void *w0)
{
J_CAST(w)