/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * Copyright (C) 2022 by Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "gesture.h" #include "button.h" #include "viewport.h" #include "system.h" /* could be a setting */ #define TOUCH_LONG_PRESS_TIME (30 * HZ / 100) void gesture_reset(struct gesture *g) { g->flags = 0; } void gesture_process(struct gesture *g, const struct touchevent *ev) { /* wait for the first press if we haven't seen it */ if (!gesture_is_pressed(g) && ev->type != TOUCHEVENT_PRESS) return; int dx, dy, dist_sqr; switch (ev->type) { case TOUCHEVENT_PRESS: g->flags |= GESTURE_F_PRESSED | GESTURE_F_VALID; g->id = GESTURE_NONE; g->ox = g->x = ev->x; g->oy = g->y = ev->y; g->start_tick = ev->tick; g->last_tick = ev->tick; break; case TOUCHEVENT_CONTACT: g->x = ev->x; g->y = ev->y; g->last_tick = ev->tick; if (g->id == GESTURE_LONG_PRESS) g->id = GESTURE_HOLD; else if (g->id == GESTURE_DRAGSTART) g->id = GESTURE_DRAG; else if (g->id != GESTURE_DRAG) { dx = ev->x - g->ox; dy = ev->y - g->oy; dist_sqr = dx*dx + dy*dy; /* if squared distance exceeds a threshold, report as a DRAG. */ const int thresh = touchscreen_get_scroll_threshold(); if (dist_sqr > thresh*thresh) g->id = GESTURE_DRAGSTART; /* report a LONG_PRESS if no motion occurs within a timeout */ if (g->id == GESTURE_NONE && TIME_AFTER(ev->tick, g->start_tick + TOUCH_LONG_PRESS_TIME)) g->id = GESTURE_LONG_PRESS; } break; case TOUCHEVENT_RELEASE: /* report a RELEASE event after a continuous HOLD or DRAG */ if (g->id == GESTURE_HOLD || g->id == GESTURE_DRAGSTART || g->id == GESTURE_DRAG) g->id = GESTURE_RELEASE; /* report a TAP event if we got a press & release without * triggering any other gestures */ else if (g->id == GESTURE_NONE) g->id = GESTURE_TAP; g->flags &= ~GESTURE_F_PRESSED; g->last_tick = ev->tick; break; } } bool gesture_get_event_in_vp(struct gesture *g, struct gesture_event *gevt, const struct viewport *vp) { if (!gesture_is_valid(g)) return false; gevt->id = g->id; gevt->x = g->x; gevt->y = g->y; gevt->ox = g->ox; gevt->oy = g->oy; gevt->start_tick = g->start_tick; gevt->last_tick = g->last_tick; if (vp) { gevt->x -= vp->x; gevt->y -= vp->y; gevt->ox -= vp->x; gevt->oy -= vp->y; } return !vp || viewport_point_within_vp(vp, g->ox, g->oy); } /* * gesture velocity helper */ void gesture_vel_reset(struct gesture_vel *gv) { gv->idx = 0; gv->cnt = 0; } void gesture_vel_process(struct gesture_vel *gv, const struct touchevent *ev) { if (ev->type != TOUCHEVENT_PRESS && ev->type != TOUCHEVENT_CONTACT) return; gv->xsamp[gv->idx] = ev->x; gv->ysamp[gv->idx] = ev->y; gv->tsamp[gv->idx] = ev->tick; gv->idx++; gv->cnt++; if (gv->idx >= ARRAYLEN(gv->xsamp)) gv->idx = 0; } bool gesture_vel_get(struct gesture_vel *gv, int *xvel, int *yvel) { if (gv->cnt <= 1) { *xvel = 0; *yvel = 0; return false; } int dx = 0, dy = 0, dt = 0; size_t n = MIN(gv->cnt, ARRAYLEN(gv->xsamp)) - 1; size_t i = gv->cnt < ARRAYLEN(gv->xsamp) ? 0 : gv->idx; size_t ip = i + 1; while (n-- > 0) { if (ip >= ARRAYLEN(gv->xsamp)) ip = 0; dx += gv->xsamp[ip] - gv->xsamp[i]; dy += gv->ysamp[ip] - gv->ysamp[i]; dt += gv->tsamp[ip] - gv->tsamp[i]; i = ip; ip++; } if (dt == 0) { *xvel = 0; *yvel = 0; } else { *xvel = dx * HZ / dt; *yvel = dy * HZ / dt; } return gv->cnt >= ARRAYLEN(gv->xsamp); } /* * flick detector */ int gesture_flick_get_in_vp(const struct gesture_event *gevt, const struct viewport *vp) { const int vp_height = vp ? vp->height : LCD_HEIGHT; const int vp_width = vp ? vp->width : LCD_WIDTH; /* margin at edge where flicks must originate */ const int margin = touchscreen_get_scroll_threshold() * 2; /* minimum distance from edge before a flick is reported */ const int x_dist = 1 + vp_width/4; const int y_dist = 1 + vp_height/4; /* a flick only triggers if the velocity along the main axis * exceeds the minimum and the velocity on the other axis is * below the maximum */ const int xv_min = 2*vp_width; const int xv_max = vp_width; const int yv_min = 2*vp_height; const int yv_max = vp_height; /* a flick is only detected if it occurs within this timeout */ const long timeout = HZ/2; long dt = gevt->last_tick - gevt->start_tick; if (dt <= 0 || dt > timeout) return GESTURE_FLICK_NONE; int xvel = (gevt->x - gevt->ox) * HZ / dt; int yvel = (gevt->y - gevt->oy) * HZ / dt; if (gevt->ox < margin && gevt->x >= x_dist && xvel >= xv_min && abs(yvel) <= yv_max) return GESTURE_FLICK_LEFT; if (gevt->ox >= LCD_WIDTH - margin && gevt->x < LCD_WIDTH - x_dist && xvel <= -xv_min && abs(yvel) <= yv_max) return GESTURE_FLICK_RIGHT; if (gevt->oy < margin && gevt->y >= y_dist && yvel >= yv_min && abs(xvel) <= xv_max) return GESTURE_FLICK_TOP; if (gevt->oy >= LCD_HEIGHT - margin && gevt->y < LCD_HEIGHT - y_dist && yvel <= -yv_min && abs(xvel) < xv_max) return GESTURE_FLICK_BOTTOM; return GESTURE_FLICK_NONE; }