diff --git a/include/justui/config.h.in b/include/justui/config.h.in index 68a4b76..51bd296 100644 --- a/include/justui/config.h.in +++ b/include/justui/config.h.in @@ -5,6 +5,17 @@ #ifndef _JUSTUI_CONFIG #define _JUSTUI_CONFIG +#include + #define J_VERSION "@JustUI_VERSION@" +#if GINT_HW_CP +#define J_CONFIG_TOUCH 1 +#else +#define J_CONFIG_TOUCH 0 +#endif + +/* Set to 1 to enable a touch-following widget inspector. */ +#define J_CONFIG_TOUCH_INSPECTOR (J_CONFIG_TOUCH && 1) + #endif /* _JUSTUI_CONFIG */ diff --git a/include/justui/jscene.h b/include/justui/jscene.h index 76c9c49..1024f17 100644 --- a/include/justui/jscene.h +++ b/include/justui/jscene.h @@ -8,6 +8,7 @@ #include #include #include +#include #define JSCENE_QUEUE_SIZE 32 @@ -36,6 +37,9 @@ typedef struct { /* Whether jscene_run() will autopaint */ bool autopaint; + /* Last coordinates a touch cursor was seen at */ + int16_t touch_last_x, touch_last_y; + } jscene; /* Events */ @@ -65,6 +69,21 @@ jscene *jscene_owning(void *widget); (x,y) point. */ void jscene_render(jscene *scene); +/* jscene_widget_at(): Find top-most widget at specified location + + Returns the top-most widget at the given scene-local location. + * If there is no widget in the scene covering this location, returns NULL. + * If there is exactly one, returns it. + * If there are multiple, this function returns the first found by depth- + first search where children are enumerated floating first, non-floating + 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); + //--- // Events sent from the scene to the user //--- diff --git a/include/justui/jwidget.h b/include/justui/jwidget.h index 957279c..e1f6345 100644 --- a/include/justui/jwidget.h +++ b/include/justui/jwidget.h @@ -339,6 +339,18 @@ bool jwidget_layout_dirty(void *scene_root); need to keep the layout up-to-date before doing it. */ 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_content_x(): Absolute x-position of a widget's content box + jwidget_absolute_content_y(): Absolute y-position of a widget's content box + + These are actually based on the root's coordinates, which are typically 0,0 + with a full-screen scene. */ +int jwidget_absolute_x(void *w); +int jwidget_absolute_y(void *w); +int jwidget_absolute_content_x(void *w); +int jwidget_absolute_content_y(void *w); + /* jwidget_width(): With of a widget's content box jwidget_height(): Height of a widget's content box diff --git a/src/jscene.c b/src/jscene.c index 146a1be..b7ac3a9 100644 --- a/src/jscene.c +++ b/src/jscene.c @@ -62,6 +62,9 @@ jscene *jscene_create(int x, int y, int w, int h, void *parent) s->poweroff = true; s->autopaint = false; + s->touch_last_x = -1; + s->touch_last_y = -1; + /* Prepare first layout/paint operation */ s->widget.dirty = 1; @@ -84,10 +87,94 @@ jscene *jscene_owning(void *w0) return NULL; } -void jscene_render(jscene *scene) +void jscene_render(jscene *s) { - jwidget_layout(scene); - jwidget_render(scene, scene->x, scene->y); + jwidget_layout(s); + jwidget_render(s, s->x, s->y); + +#if J_CONFIG_TOUCH_INSPECTOR + if(s->touch_last_x >= 0 && s->touch_last_y >= 0) { + 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; + drect_border(x1, y1, x2, y2, C_NONE, 1, C_RED); + + dprint_opt(DWIDTH-1, 0, C_WHITE, C_RED, DTEXT_RIGHT, DTEXT_TOP, + "%s", jwidget_type(w)); + } + } +#endif +} + +/* 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) +{ + if(!w) + return NULL; + jwidget *found; + + jwidget_geometry const *g = jwidget_geometry_r(w); + int xC = g->margin.left + g->border.left + g->padding.left; + int yC = w->y + g->margin.top + g->border.top + g->padding.top; + uint wC = jwidget_content_width(w); + uint hC = jwidget_content_height(w); + 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); + else + intersects = (uint)x < wF && (uint)y < hF; + if(!intersects) + return NULL; + + /* If we have a stacked widget, only consider the visible child */ + jlayout_stack *stack; + if((stack = jlayout_get_stack(w))) { + 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); + return found ? found : w; + } + + /* Try to find a descendant of a floating child that intersects... */ + 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); + if(found) + return found; + } + } + + /* ... or a descendant of a non-floating child */ + 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); + if(found) + return found; + } + } + + /* If no descendants do, then the best match is w itself. */ + return w; +} + +jwidget *jscene_widget_at(jscene *scene, int x, int y, bool content_only) +{ + return jscene_widget_at_rec(&scene->widget, x, y, content_only, true); } //--- @@ -260,6 +347,19 @@ static jevent poll_next_unhandled_event(keydev_t *d, jscene *s) /* Then try to dequeue keyboard events, if there are any. */ while((k = keydev_read(d, false, NULL)).type != KEYEV_NONE) { + /* Keep track of where the touch cursor was last seen. */ +#if J_CONFIG_TOUCH_INSPECTOR + if(k.type == KEYEV_TOUCH_DOWN || k.type == KEYEV_TOUCH_DRAG) { + s->touch_last_x = k.x - s->x; + s->touch_last_y = k.y - s->y; + s->widget.update = true; + } + if(k.type == KEYEV_TOUCH_UP) { + s->touch_last_x = -1; + s->touch_last_y = -1; + s->widget.update = true; + } +#endif /* Auto return-to-menu */ if(k.type == KEYEV_DOWN && k.key == KEY_MENU && !k.shift && !k.alpha) { if(s->mainmenu) { diff --git a/src/jwidget.c b/src/jwidget.c index 4b01871..fa1fd75 100644 --- a/src/jwidget.c +++ b/src/jwidget.c @@ -567,6 +567,38 @@ void jwidget_layout(void *root0) jwidget_layout_apply(root); } +int jwidget_absolute_x(void *w0) +{ + J_CAST(w) + return w ? w->x + jwidget_absolute_x(w->parent) : 0; +} + +int jwidget_absolute_y(void *w0) +{ + J_CAST(w) + return w ? w->y + jwidget_absolute_y(w->parent) : 0; +} + +int jwidget_absolute_content_x(void *w0) +{ + J_CAST(w) + if(!w) + return 0; + jwidget_geometry const *g = jwidget_geometry_r(w); + return jwidget_absolute_content_x(w->parent) + + w->x + g->margin.left + g->border.left + g->padding.left; +} + +int jwidget_absolute_content_y(void *w0) +{ + J_CAST(w) + if(!w) + return 0; + jwidget_geometry const *g = jwidget_geometry_r(w); + return jwidget_absolute_content_y(w->parent) + + w->y + g->margin.top + g->border.top + g->padding.top; +} + int jwidget_content_width(void *w0) { J_CAST(w)