Root/eeshow/gui/input.c

1/*
2 * gui/input.c - Input operations
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13#include <stddef.h>
14#include <stdbool.h>
15#include <math.h>
16#include <assert.h>
17
18#include <gtk/gtk.h>
19
20#include "misc/util.h"
21#include "misc/diag.h"
22#include "gui/input.h"
23
24
25#define DRAG_RADIUS 5
26
27
28static struct input {
29    const struct input_ops *ops;
30    void *user;
31
32    enum state {
33        input_idle,
34        input_clicking,
35        input_ignoring, /* click rejected by moving the cursor */
36        input_hovering,
37        input_hovering_down, /* mouse button is pressed */
38        input_dragging,
39    } state;
40
41    struct input *next;
42} *sp = NULL;
43
44static int curr_x, curr_y; /* last mouse position */
45static double clicked_x, clicked_y; /* button down position */
46
47
48/* ----- Debugging tools --------------------------------------------------- */
49
50
51static const char *state(void)
52{
53    switch (sp->state) {
54    case input_idle:
55        return "IDLE";
56    case input_clicking:
57        return "CLICKING";
58    case input_ignoring:
59        return "IGNORING";
60    case input_hovering:
61        return "HOVERING";
62    case input_hovering_down:
63        return "HOVERING_DOWN";
64    case input_dragging:
65        return "DRAGGING";
66    default:
67        abort();
68    }
69}
70
71
72/* ----- Shared operations ------------------------------------------------- */
73
74
75static bool begin_drag(const GdkEventMotion *event)
76{
77    const struct input *old_sp = sp;
78
79    if (hypot(event->x - clicked_x, event->y - clicked_y) < DRAG_RADIUS)
80        return 0;
81    if (sp->ops->drag_begin &&
82        sp->ops->drag_begin(sp->user, clicked_x, clicked_y))
83        sp->state = input_dragging;
84    else
85        sp->state = input_ignoring;
86    assert(sp == old_sp);
87    return 1;
88}
89
90
91static void hover_consider(int x, int y)
92{
93    const struct input *old_sp = sp;
94
95    assert(sp->state == input_idle);
96
97    if (sp->ops->hover_begin && sp->ops->hover_begin(sp->user, x, y))
98        sp->state = input_hovering;
99    assert(sp == old_sp);
100}
101
102
103static void hover_update(int x, int y)
104{
105    const struct input *old_sp = sp;
106
107    assert(sp->state == input_hovering || sp->state == input_hovering_down);
108
109    if (!sp->ops->hover_update)
110        return;
111
112    /*
113     * Caution: hover_update may switch input layers. If this happens,
114     * hovering was already ended when cleaning up the old input layer.
115     */
116    if (sp->ops->hover_update(sp->user, x, y))
117        return;
118    if (sp != old_sp)
119        return;
120
121    progress(3, "hover_update %s", state());
122
123    switch (sp->state) {
124    case input_idle:
125    case input_hovering:
126    case input_ignoring:
127        sp->state = input_idle;
128        break;
129    case input_clicking:
130    case input_hovering_down:
131        sp->state = input_clicking;
132        break;
133    case input_dragging:
134    default:
135        abort();
136    }
137
138    if (sp->ops->hover_end)
139        sp->ops->hover_end(sp->user);
140}
141
142
143/* ----- Indirect update --------------------------------------------------- */
144
145/*
146 * Geometry changes may require a reassessment of the hover situation. This is
147 * roughly equivalent to what we would do on a mouse movement over distance
148 * zero.
149 */
150
151void input_update(void)
152{
153    switch (sp->state) {
154    case input_idle:
155        hover_consider(curr_x, curr_y);
156        break;
157    case input_hovering:
158        hover_update(curr_x, curr_y);
159        break;
160    case input_clicking:
161    case input_ignoring:
162    case input_hovering_down:
163    case input_dragging:
164        break;
165    default:
166        abort();
167    }
168}
169
170
171/* ----- Mouse button ------------------------------------------------------ */
172
173
174static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
175    gpointer data)
176{
177    curr_x = event->x;
178    curr_y = event->y;
179
180    if (!sp)
181        return TRUE;
182
183    progress(3, "motion %s", state());
184
185    switch (sp->state) {
186    case input_idle:
187        hover_consider(event->x, event->y);
188        break;
189    case input_clicking:
190        begin_drag(event);
191        break;
192    case input_ignoring:
193        break;
194    case input_hovering_down:
195        if (begin_drag(event)) {
196            if (sp->ops->hover_end)
197                sp->ops->hover_end(sp->user);
198            break;
199        }
200        /* fall through */
201    case input_hovering:
202        hover_update(event->x, event->y);
203        break;
204    case input_dragging:
205        if (sp->ops->drag_move)
206            sp->ops->drag_move(sp->user,
207                event->x - clicked_x, event->y - clicked_y);
208        clicked_x = event->x;
209        clicked_y = event->y;
210        break;
211    default:
212        abort();
213    }
214    return TRUE;
215}
216
217
218static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
219    gpointer data)
220{
221    if (event->button != 1)
222        return TRUE;
223
224    progress(3, "press %s", state());
225
226    switch (sp->state) {
227    case input_idle:
228        sp->state = input_clicking;
229        clicked_x = event->x;
230        clicked_y = event->y;
231        break;
232    case input_clicking:
233    case input_ignoring:
234    case input_dragging:
235    case input_hovering_down:
236        /* ignore double-click */
237        break;
238    case input_hovering:
239        sp->state = input_hovering_down;
240        clicked_x = event->x;
241        clicked_y = event->y;
242        break;
243    default:
244        abort();
245    }
246
247    return TRUE;
248}
249
250
251static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
252    gpointer data)
253{
254    const struct input *old_sp = sp;
255
256    if (event->button != 1)
257        return TRUE;
258
259    progress(3, "release %s", state());
260
261    switch (sp->state) {
262    case input_idle:
263        /* hover_click changed the input configuration */
264        break;
265    case input_clicking:
266        sp->state = input_idle;
267        if (sp->ops->click)
268            sp->ops->click(sp->user, clicked_x, clicked_y);
269        break;
270    case input_ignoring:
271        sp->state = input_idle;
272        break;
273    case input_dragging:
274        sp->state = input_idle;
275        if (sp->ops->drag_end)
276            sp->ops->drag_end(sp->user);
277        break;
278    case input_hovering:
279        break;
280    case input_hovering_down:
281        if (sp->ops->hover_click &&
282            sp->ops->hover_click(sp->user, event->x, event->y) &&
283            sp == old_sp) {
284            sp->state = input_idle;
285            if (sp->ops->hover_end)
286                sp->ops->hover_end(sp->user);
287        }
288        break;
289    default:
290        abort();
291    }
292
293    return TRUE;
294}
295
296
297/* ----- Scroll wheel ------------------------------------------------------ */
298
299
300static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event,
301    gpointer data)
302{
303    if (!sp || !sp->ops->scroll)
304        return TRUE;
305    switch (event->direction) {
306    case GDK_SCROLL_UP:
307        sp->ops->scroll(sp->user, event->x, event->y, -1);
308        break;
309    case GDK_SCROLL_DOWN:
310        sp->ops->scroll(sp->user, event->x, event->y, 1);
311        break;
312    default:
313        /* ignore */;
314    }
315    return TRUE;
316}
317
318
319/* ----- Keys -------------------------------------------------------------- */
320
321
322static gboolean key_press_event(GtkWidget *widget, GdkEventKey *event,
323    gpointer data)
324{
325    if (sp && sp->ops->key)
326        sp->ops->key(sp->user, curr_x, curr_y, event->keyval);
327    return TRUE;
328}
329
330
331/* ----- Covenience function for hover_begin and drag_begin ---------------- */
332
333
334bool input_accept(void *user, int x, int y)
335{
336    return 1;
337}
338
339
340/* ----- Adding/removing interaction layers -------------------------------- */
341
342
343static void cleanup(void)
344{
345    if (!sp)
346        return;
347
348    switch (sp->state) {
349    case input_hovering:
350        if (sp->ops->hover_end)
351            sp->ops->hover_end(sp->user);
352        break;
353    case input_dragging:
354        if (sp->ops->drag_end)
355            sp->ops->drag_end(sp->user);
356        break;
357    default:
358        ;
359    }
360
361    sp->state = input_idle;
362}
363
364
365void input_push(const struct input_ops *ops, void *user)
366{
367    struct input *new;
368
369    cleanup();
370
371    new = alloc_type(struct input);
372    new->ops = ops;
373    new->user = user;
374    new->state = input_idle;
375    new->next = sp;
376    sp = new;
377}
378
379
380void input_pop(void)
381{
382    struct input *next = sp->next;
383
384    cleanup();
385    free(sp);
386    sp = next;
387}
388
389
390/* ----- Initialization ---------------------------------------------------- */
391
392
393void input_setup(GtkWidget *da)
394{
395    gtk_widget_set_can_focus(da, TRUE);
396
397    gtk_widget_add_events(da,
398        GDK_KEY_PRESS_MASK |
399        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
400        GDK_SCROLL_MASK |
401        GDK_POINTER_MOTION_MASK);
402
403    g_signal_connect(G_OBJECT(da), "key_press_event",
404        G_CALLBACK(key_press_event), NULL);
405    g_signal_connect(G_OBJECT(da), "motion_notify_event",
406        G_CALLBACK(motion_notify_event), NULL);
407    g_signal_connect(G_OBJECT(da), "button_press_event",
408        G_CALLBACK(button_press_event), NULL);
409    g_signal_connect(G_OBJECT(da), "button_release_event",
410        G_CALLBACK(button_release_event), NULL);
411    g_signal_connect(G_OBJECT(da), "scroll_event",
412        G_CALLBACK(scroll_event), NULL);
413}
414

Archive Download this file

Branches:
master



interactive