···11+/***************************************************************************
22+ * __________ __ ___.
33+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
44+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
55+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
66+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
77+ * \/ \/ \/ \/ \/
88+ * $Id$
99+ *
1010+ * Copyright (C) 2022 by Aidan MacDonald
1111+ *
1212+ * This program is free software; you can redistribute it and/or
1313+ * modify it under the terms of the GNU General Public License
1414+ * as published by the Free Software Foundation; either version 2
1515+ * of the License, or (at your option) any later version.
1616+ *
1717+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1818+ * KIND, either express or implied.
1919+ *
2020+ ****************************************************************************/
2121+2222+#include "gesture.h"
2323+#include "button.h"
2424+#include "viewport.h"
2525+#include "system.h"
2626+2727+/* could be a setting */
2828+#define TOUCH_LONG_PRESS_TIME (30 * HZ / 100)
2929+3030+void gesture_reset(struct gesture *g)
3131+{
3232+ g->flags = 0;
3333+}
3434+3535+void gesture_process(struct gesture *g, const struct touchevent *ev)
3636+{
3737+ /* wait for the first press if we haven't seen it */
3838+ if (!gesture_is_pressed(g) && ev->type != TOUCHEVENT_PRESS)
3939+ return;
4040+4141+ int dx, dy, dist_sqr;
4242+ switch (ev->type)
4343+ {
4444+ case TOUCHEVENT_PRESS:
4545+ g->flags |= GESTURE_F_PRESSED | GESTURE_F_VALID;
4646+ g->id = GESTURE_NONE;
4747+ g->ox = g->x = ev->x;
4848+ g->oy = g->y = ev->y;
4949+ g->start_tick = ev->tick;
5050+ g->last_tick = ev->tick;
5151+ break;
5252+5353+ case TOUCHEVENT_CONTACT:
5454+ g->x = ev->x;
5555+ g->y = ev->y;
5656+ g->last_tick = ev->tick;
5757+5858+ if (g->id == GESTURE_LONG_PRESS)
5959+ g->id = GESTURE_HOLD;
6060+ else if (g->id == GESTURE_DRAGSTART)
6161+ g->id = GESTURE_DRAG;
6262+ else if (g->id != GESTURE_DRAG)
6363+ {
6464+ dx = ev->x - g->ox;
6565+ dy = ev->y - g->oy;
6666+ dist_sqr = dx*dx + dy*dy;
6767+6868+ /* if squared distance exceeds a threshold, report as a DRAG. */
6969+ const int thresh = touchscreen_get_scroll_threshold();
7070+ if (dist_sqr > thresh*thresh)
7171+ g->id = GESTURE_DRAGSTART;
7272+7373+ /* report a LONG_PRESS if no motion occurs within a timeout */
7474+ if (g->id == GESTURE_NONE &&
7575+ TIME_AFTER(ev->tick, g->start_tick + TOUCH_LONG_PRESS_TIME))
7676+ g->id = GESTURE_LONG_PRESS;
7777+ }
7878+7979+ break;
8080+8181+ case TOUCHEVENT_RELEASE:
8282+ /* report a RELEASE event after a continuous HOLD or DRAG */
8383+ if (g->id == GESTURE_HOLD ||
8484+ g->id == GESTURE_DRAGSTART ||
8585+ g->id == GESTURE_DRAG)
8686+ g->id = GESTURE_RELEASE;
8787+8888+ /* report a TAP event if we got a press & release without
8989+ * triggering any other gestures */
9090+ else if (g->id == GESTURE_NONE)
9191+ g->id = GESTURE_TAP;
9292+9393+ g->flags &= ~GESTURE_F_PRESSED;
9494+ g->last_tick = ev->tick;
9595+ break;
9696+ }
9797+}
9898+9999+bool gesture_get_event_in_vp(struct gesture *g, struct gesture_event *gevt,
100100+ const struct viewport *vp)
101101+{
102102+ if (!gesture_is_valid(g))
103103+ return false;
104104+105105+ gevt->id = g->id;
106106+ gevt->x = g->x;
107107+ gevt->y = g->y;
108108+ gevt->ox = g->ox;
109109+ gevt->oy = g->oy;
110110+ gevt->start_tick = g->start_tick;
111111+ gevt->last_tick = g->last_tick;
112112+113113+ if (vp) {
114114+ gevt->x -= vp->x;
115115+ gevt->y -= vp->y;
116116+ gevt->ox -= vp->x;
117117+ gevt->oy -= vp->y;
118118+ }
119119+120120+ return !vp || viewport_point_within_vp(vp, g->ox, g->oy);
121121+}
+123
apps/gesture.h
···11+/***************************************************************************
22+ * __________ __ ___.
33+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
44+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
55+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
66+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
77+ * \/ \/ \/ \/ \/
88+ * $Id$
99+ *
1010+ * Copyright (C) 2022 Aidan MacDonald
1111+ *
1212+ * This program is free software; you can redistribute it and/or
1313+ * modify it under the terms of the GNU General Public License
1414+ * as published by the Free Software Foundation; either version 2
1515+ * of the License, or (at your option) any later version.
1616+ *
1717+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1818+ * KIND, either express or implied.
1919+ *
2020+ ****************************************************************************/
2121+#ifndef _GESTURE_H_
2222+#define _GESTURE_H_
2323+2424+#include <stddef.h>
2525+#include <stdbool.h>
2626+2727+struct viewport;
2828+struct touchevent;
2929+3030+/** Events which can be detected by the gesture API.
3131+ *
3232+ * The gesture state machine, informally, looks like this:
3333+ *
3434+ * NONE --+--> TAP ------------------------------------+
3535+ * | |
3636+ * +--> LONG_PRESS -----------------------------+
3737+ * | | |
3838+ * | +---> HOLD ---+ |
3939+ * | | | |
4040+ * +-------------+---> DRAG ---+--> RELEASE ----+--> NONE
4141+ *
4242+ * State transitions occur from gesture_process() in response to touch events.
4343+ *
4444+ * - The NONE "event" is returned prior to any other gesture being detected.
4545+ * Although the graph above depicts a transition back to NONE at the end of
4646+ * an event chain, that transition in fact happens on the TOUCHEVENT_PRESS
4747+ * after the end of the last event.
4848+ *
4949+ * - TAP events are reported after getting a TOUCHEVENT_RELEASE event if no
5050+ * other gestures were detected between the press and release.
5151+ *
5252+ * - LONG_PRESS events are reported on a TOUCHEVENT_CONTACT event if the long
5353+ * press timeout has expired and the touch point hasn't moved too far from
5454+ * its initial position.
5555+ *
5656+ * - HOLD events are reported on TOUCHEVENT_CONTACT events after a LONG_PRESS,
5757+ * provided the touch point hasn't moved too far from its initial position.
5858+ *
5959+ * - DRAG events are reported on TOUCHEVENT_CONTACT events after the touch
6060+ * point first moves a certain distance from its initial position. The first
6161+ * time this happens, it is reported as DRAGSTART.
6262+ *
6363+ * - RELEASE events are reported on the TOUCHEVENT_RELEASE event following a
6464+ * HOLD or DRAG gesture.
6565+ */
6666+enum gesture_id
6767+{
6868+ GESTURE_NONE = 0, /** No gesture */
6969+ GESTURE_TAP, /** Quick press & release */
7070+ GESTURE_LONG_PRESS, /** Start of a long press */
7171+ GESTURE_HOLD, /** Continuation of a long press */
7272+ GESTURE_DRAGSTART, /** Start of a DRAG event */
7373+ GESTURE_DRAG, /** Press and drag on the screen */
7474+ GESTURE_RELEASE, /** End of a HOLD or DRAG event */
7575+};
7676+7777+enum gesture_flags
7878+{
7979+ GESTURE_F_VALID = 0x01,
8080+ GESTURE_F_PRESSED = 0x02,
8181+};
8282+8383+struct gesture
8484+{
8585+ unsigned int flags;
8686+ int id;
8787+ short x, y;
8888+ short ox, oy;
8989+ long start_tick;
9090+ long last_tick;
9191+};
9292+9393+struct gesture_event
9494+{
9595+ int id;
9696+ short x, y;
9797+ short ox, oy;
9898+ long start_tick;
9999+ long last_tick;
100100+};
101101+102102+void gesture_reset(struct gesture *g);
103103+void gesture_process(struct gesture *g, const struct touchevent *ev);
104104+bool gesture_get_event_in_vp(struct gesture *g, struct gesture_event *gevt,
105105+ const struct viewport *vp);
106106+107107+static inline bool gesture_get_event(struct gesture *g,
108108+ struct gesture_event *gevt)
109109+{
110110+ return gesture_get_event_in_vp(g, gevt, NULL);
111111+}
112112+113113+static inline bool gesture_is_valid(struct gesture *g)
114114+{
115115+ return !!(g->flags & GESTURE_F_VALID);
116116+}
117117+118118+static inline bool gesture_is_pressed(struct gesture *g)
119119+{
120120+ return !!(g->flags & GESTURE_F_PRESSED);
121121+}
122122+123123+#endif /* _GESTURE_H_ */