1/*
2 * Copyright (C) 2014 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "GestureController.h"
28
29#if HAVE(GTK_GESTURES)
30
31#include <WebCore/Scrollbar.h>
32#include <gtk/gtk.h>
33
34namespace WebKit {
35using namespace WebCore;
36
37static const double maximumZoom = 3.0;
38
39GestureController::GestureController(GtkWidget* widget, std::unique_ptr<GestureControllerClient>&& client)
40 : m_client(WTFMove(client))
41 , m_dragGesture(widget, *m_client)
42 , m_swipeGesture(widget, *m_client)
43 , m_zoomGesture(widget, *m_client)
44 , m_longpressGesture(widget, *m_client)
45{
46}
47
48bool GestureController::handleEvent(GdkEvent* event)
49{
50 bool wasProcessingGestures = isProcessingGestures();
51 bool touchEnd;
52 m_dragGesture.handleEvent(event);
53 m_swipeGesture.handleEvent(event);
54 m_zoomGesture.handleEvent(event);
55 m_longpressGesture.handleEvent(event);
56 touchEnd = (event->type == GDK_TOUCH_END) || (event->type == GDK_TOUCH_CANCEL);
57 return touchEnd ? wasProcessingGestures : isProcessingGestures();
58}
59
60bool GestureController::isProcessingGestures() const
61{
62 return m_dragGesture.isActive() || m_swipeGesture.isActive() || m_zoomGesture.isActive() || m_longpressGesture.isActive();
63}
64
65GestureController::Gesture::Gesture(GtkGesture* gesture, GestureControllerClient& client)
66 : m_gesture(adoptGRef(gesture))
67 , m_client(client)
68{
69 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_gesture.get()), GTK_PHASE_NONE);
70}
71
72void GestureController::Gesture::reset()
73{
74 gtk_event_controller_reset(GTK_EVENT_CONTROLLER(m_gesture.get()));
75}
76
77bool GestureController::Gesture::isActive() const
78{
79 return gtk_gesture_is_active(m_gesture.get());
80}
81
82void GestureController::Gesture::handleEvent(GdkEvent* event)
83{
84 gtk_event_controller_handle_event(GTK_EVENT_CONTROLLER(m_gesture.get()), event);
85}
86
87void GestureController::DragGesture::startDrag(GdkEvent* event)
88{
89 ASSERT(!m_inDrag);
90 m_client.startDrag(reinterpret_cast<GdkEventTouch*>(event), m_start);
91}
92
93void GestureController::DragGesture::handleDrag(GdkEvent* event, double x, double y)
94{
95 ASSERT(m_inDrag);
96 m_client.drag(reinterpret_cast<GdkEventTouch*>(event), m_start,
97 FloatPoint::narrowPrecision((m_offset.x() - x) / Scrollbar::pixelsPerLineStep(), (m_offset.y() - y) / Scrollbar::pixelsPerLineStep()));
98}
99
100void GestureController::DragGesture::handleTap(GdkEvent* event)
101{
102 ASSERT(!m_inDrag);
103 m_client.tap(reinterpret_cast<GdkEventTouch*>(event));
104}
105
106void GestureController::DragGesture::begin(DragGesture* dragGesture, double x, double y, GtkGesture* gesture)
107{
108 GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
109 gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
110 dragGesture->m_inDrag = false;
111 dragGesture->m_start.set(x, y);
112 dragGesture->m_offset.set(0, 0);
113
114 GtkWidget* widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
115 unsigned delay;
116 g_object_get(gtk_widget_get_settings(widget), "gtk-long-press-time", &delay, nullptr);
117 dragGesture->m_longPressTimeout.startOneShot(1_ms * delay);
118 dragGesture->startDrag(const_cast<GdkEvent*>(gtk_gesture_get_last_event(gesture, sequence)));
119}
120
121void GestureController::DragGesture::update(DragGesture* dragGesture, double x, double y, GtkGesture* gesture)
122{
123 GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
124 gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
125
126 GtkWidget* widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
127 if (!dragGesture->m_inDrag && gtk_drag_check_threshold(widget, dragGesture->m_start.x(), dragGesture->m_start.y(), dragGesture->m_start.x() + x, dragGesture->m_start.y() + y)) {
128 dragGesture->m_inDrag = true;
129 dragGesture->m_longPressTimeout.stop();
130 }
131
132 if (dragGesture->m_inDrag)
133 dragGesture->handleDrag(const_cast<GdkEvent*>(gtk_gesture_get_last_event(gesture, sequence)), x, y);
134 dragGesture->m_offset.set(x, y);
135}
136
137void GestureController::DragGesture::end(DragGesture* dragGesture, GdkEventSequence* sequence, GtkGesture* gesture)
138{
139 dragGesture->m_longPressTimeout.stop();
140 if (!gtk_gesture_handles_sequence(gesture, sequence)) {
141 gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
142 return;
143 }
144 if (!dragGesture->m_inDrag) {
145 dragGesture->handleTap(const_cast<GdkEvent*>(gtk_gesture_get_last_event(gesture, sequence)));
146 gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
147 }
148}
149
150void GestureController::DragGesture::longPressFired()
151{
152 m_inDrag = true;
153}
154
155GestureController::DragGesture::DragGesture(GtkWidget* widget, GestureControllerClient& client)
156 : Gesture(gtk_gesture_drag_new(widget), client)
157 , m_longPressTimeout(RunLoop::main(), this, &GestureController::DragGesture::longPressFired)
158{
159 gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_gesture.get()), TRUE);
160 g_signal_connect_swapped(m_gesture.get(), "drag-begin", G_CALLBACK(begin), this);
161 g_signal_connect_swapped(m_gesture.get(), "drag-update", G_CALLBACK(update), this);
162 g_signal_connect_swapped(m_gesture.get(), "end", G_CALLBACK(end), this);
163}
164
165void GestureController::SwipeGesture::startMomentumScroll(GdkEvent* event, double velocityX, double velocityY)
166{
167 m_client.swipe(reinterpret_cast<GdkEventTouch*>(event), FloatPoint::narrowPrecision(velocityX, velocityY));
168}
169
170void GestureController::SwipeGesture::swipe(SwipeGesture* swipeGesture, double velocityX, double velocityY, GtkGesture* gesture)
171{
172 GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
173 if (!gtk_gesture_handles_sequence(gesture, sequence))
174 return;
175
176 gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
177
178 swipeGesture->startMomentumScroll(const_cast<GdkEvent*>(gtk_gesture_get_last_event(gesture, sequence)), velocityX, velocityY);
179}
180
181GestureController::SwipeGesture::SwipeGesture(GtkWidget* widget, GestureControllerClient& client)
182 : Gesture(gtk_gesture_swipe_new(widget), client)
183{
184 gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_gesture.get()), TRUE);
185 g_signal_connect_swapped(m_gesture.get(), "swipe", G_CALLBACK(swipe), this);
186}
187
188void GestureController::ZoomGesture::begin(ZoomGesture* zoomGesture, GdkEventSequence*, GtkGesture* gesture)
189{
190 gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_CLAIMED);
191 zoomGesture->startZoom();
192}
193
194IntPoint GestureController::ZoomGesture::center() const
195{
196 double x, y;
197 gtk_gesture_get_bounding_box_center(m_gesture.get(), &x, &y);
198 return IntPoint(x, y);
199}
200
201void GestureController::ZoomGesture::startZoom()
202{
203 m_client.startZoom(center(), m_initialScale, m_initialPoint);
204}
205
206void GestureController::ZoomGesture::handleZoom()
207{
208 FloatPoint scaledZoomCenter(m_initialPoint);
209 scaledZoomCenter.scale(m_scale);
210
211 m_client.zoom(m_scale, WebCore::roundedIntPoint(FloatPoint(scaledZoomCenter - m_viewPoint)));
212}
213
214void GestureController::ZoomGesture::scaleChanged(ZoomGesture* zoomGesture, double scale, GtkGesture*)
215{
216 zoomGesture->m_scale = zoomGesture->m_initialScale * scale;
217 if (zoomGesture->m_scale < 1.0)
218 zoomGesture->m_scale = 1.0;
219 if (zoomGesture->m_scale > maximumZoom)
220 zoomGesture->m_scale = maximumZoom;
221
222 zoomGesture->m_viewPoint = zoomGesture->center();
223
224 if (zoomGesture->m_idle.isActive())
225 return;
226
227 zoomGesture->m_idle.startOneShot(0_s);
228}
229
230GestureController::ZoomGesture::ZoomGesture(GtkWidget* widget, GestureControllerClient& client)
231 : Gesture(gtk_gesture_zoom_new(widget), client)
232 , m_idle(RunLoop::main(), this, &GestureController::ZoomGesture::handleZoom)
233{
234 g_signal_connect_swapped(m_gesture.get(), "begin", G_CALLBACK(begin), this);
235 g_signal_connect_swapped(m_gesture.get(), "scale-changed", G_CALLBACK(scaleChanged), this);
236}
237
238void GestureController::LongPressGesture::longPressed(GdkEvent* event)
239{
240 m_client.longPress(reinterpret_cast<GdkEventTouch*>(event));
241}
242
243void GestureController::LongPressGesture::pressed(LongPressGesture* longpressGesture, double x, double y, GtkGesture* gesture)
244{
245 GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
246 if (!gtk_gesture_handles_sequence(gesture, sequence))
247 return;
248
249 gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
250
251 longpressGesture->longPressed(const_cast<GdkEvent*>(gtk_gesture_get_last_event(gesture, sequence)));
252}
253
254GestureController::LongPressGesture::LongPressGesture(GtkWidget* widget, GestureControllerClient& client)
255 : Gesture(gtk_gesture_long_press_new(widget), client)
256{
257 gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_gesture.get()), TRUE);
258 g_signal_connect_swapped(m_gesture.get(), "pressed", G_CALLBACK(pressed), this);
259}
260
261} // namespace WebKit
262
263#endif // HAVE(GTK_GESTURES)
264