[GTK] Add initial gestures support
[WebKit-https.git] / Source / WebKit2 / UIProcess / gtk / GestureController.cpp
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 "NativeWebMouseEvent.h"
32 #include "NativeWebWheelEvent.h"
33 #include "WebPageProxy.h"
34 #include <WebCore/FloatPoint.h>
35 #include <WebCore/Scrollbar.h>
36 #include <gtk/gtk.h>
37
38 using namespace WebCore;
39
40 namespace WebKit {
41
42 GestureController::GestureController(WebPageProxy& page)
43     : m_dragGesture(page)
44     , m_zoomGesture(page)
45 {
46 }
47
48 bool GestureController::handleEvent(const GdkEvent* event)
49 {
50     bool wasProcessingGestures = isProcessingGestures();
51     m_dragGesture.handleEvent(event);
52     m_zoomGesture.handleEvent(event);
53     return event->type == GDK_TOUCH_END ? wasProcessingGestures : isProcessingGestures();
54 }
55
56 bool GestureController::isProcessingGestures() const
57 {
58     return m_dragGesture.isActive() || m_zoomGesture.isActive();
59 }
60
61 GestureController::Gesture::Gesture(GtkGesture* gesture, WebPageProxy& page)
62     : m_gesture(adoptGRef(gesture))
63     , m_page(page)
64 {
65     gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_gesture.get()), GTK_PHASE_NONE);
66 }
67
68 bool GestureController::Gesture::isActive() const
69 {
70     return gtk_gesture_is_active(m_gesture.get());
71 }
72
73 void GestureController::Gesture::handleEvent(const GdkEvent* event)
74 {
75     gtk_event_controller_handle_event(GTK_EVENT_CONTROLLER(m_gesture.get()), event);
76 }
77
78 void GestureController::DragGesture::handleDrag(const GdkEvent* event, double x, double y)
79 {
80     ASSERT(m_inDrag);
81     GUniquePtr<GdkEvent> scrollEvent(gdk_event_new(GDK_SCROLL));
82     scrollEvent->scroll.time = event->touch.time;
83     scrollEvent->scroll.x = m_start.x();
84     scrollEvent->scroll.y = m_start.y();
85     scrollEvent->scroll.x_root = event->touch.x_root;
86     scrollEvent->scroll.y_root = event->touch.y_root;
87     scrollEvent->scroll.direction = GDK_SCROLL_SMOOTH;
88     scrollEvent->scroll.delta_x = (m_offset.x() - x) / Scrollbar::pixelsPerLineStep();
89     scrollEvent->scroll.delta_y = (m_offset.y() - y) / Scrollbar::pixelsPerLineStep();
90     scrollEvent->scroll.state = event->touch.state;
91     m_page.handleWheelEvent(NativeWebWheelEvent(scrollEvent.get()));
92 }
93
94 void GestureController::DragGesture::handleTap(const GdkEvent* event)
95 {
96     ASSERT(!m_inDrag);
97     GUniquePtr<GdkEvent> pointerEvent(gdk_event_new(GDK_MOTION_NOTIFY));
98     pointerEvent->motion.time = event->touch.time;
99     pointerEvent->motion.x = event->touch.x;
100     pointerEvent->motion.y = event->touch.y;
101     pointerEvent->motion.x_root = event->touch.x_root;
102     pointerEvent->motion.y_root = event->touch.y_root;
103     pointerEvent->motion.state = event->touch.state;
104     m_page.handleMouseEvent(NativeWebMouseEvent(pointerEvent.get(), 0));
105
106     pointerEvent.reset(gdk_event_new(GDK_BUTTON_PRESS));
107     pointerEvent->button.button = 1;
108     pointerEvent->button.time = event->touch.time;
109     pointerEvent->button.x = event->touch.x;
110     pointerEvent->button.y = event->touch.y;
111     pointerEvent->button.x_root = event->touch.x_root;
112     pointerEvent->button.y_root = event->touch.y_root;
113     m_page.handleMouseEvent(NativeWebMouseEvent(pointerEvent.get(), 1));
114
115     pointerEvent->type = GDK_BUTTON_RELEASE;
116     m_page.handleMouseEvent(NativeWebMouseEvent(pointerEvent.get(), 0));
117 }
118
119 void GestureController::DragGesture::begin(DragGesture* dragGesture, double x, double y, GtkGesture* gesture)
120 {
121     GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
122     gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
123     dragGesture->m_inDrag = false;
124     dragGesture->m_start.set(x, y);
125     dragGesture->m_offset.set(0, 0);
126
127     GtkWidget* widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
128     unsigned delay;
129     g_object_get(gtk_widget_get_settings(widget), "gtk-long-press-time", &delay, nullptr);
130     dragGesture->m_longPressTimeout.scheduleAfterDelay("[WebKit] DragGesture long press timeout", [dragGesture]() {
131         dragGesture->m_inDrag = true;
132     }, std::chrono::milliseconds(delay));
133 }
134
135 void GestureController::DragGesture::update(DragGesture* dragGesture, double x, double y, GtkGesture* gesture)
136 {
137     GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
138     gtk_gesture_set_sequence_state(gesture, sequence, GTK_EVENT_SEQUENCE_CLAIMED);
139
140     GtkWidget* widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
141     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)) {
142         dragGesture->m_inDrag = true;
143         dragGesture->m_longPressTimeout.cancel();
144     }
145
146     if (dragGesture->m_inDrag)
147         dragGesture->handleDrag(gtk_gesture_get_last_event(gesture, sequence), x, y);
148     dragGesture->m_offset.set(x, y);
149 }
150
151 void GestureController::DragGesture::end(DragGesture* dragGesture, GdkEventSequence* sequence, GtkGesture* gesture)
152 {
153     dragGesture->m_longPressTimeout.cancel();
154     if (!dragGesture->m_inDrag) {
155         dragGesture->handleTap(gtk_gesture_get_last_event(gesture, sequence));
156         gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
157     } else if (!gtk_gesture_handles_sequence(gesture, sequence))
158         gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_DENIED);
159 }
160
161 GestureController::DragGesture::DragGesture(WebPageProxy& page)
162     : Gesture(gtk_gesture_drag_new(page.viewWidget()), page)
163     , m_inDrag(false)
164 {
165     gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_gesture.get()), TRUE);
166     g_signal_connect_swapped(m_gesture.get(), "drag-begin", G_CALLBACK(begin), this);
167     g_signal_connect_swapped(m_gesture.get(), "drag-update", G_CALLBACK(update), this);
168     g_signal_connect_swapped(m_gesture.get(), "end", G_CALLBACK(end), this);
169 }
170
171 void GestureController::ZoomGesture::begin(ZoomGesture* zoomGesture, GdkEventSequence*, GtkGesture* gesture)
172 {
173     gtk_gesture_set_state(gesture, GTK_EVENT_SEQUENCE_CLAIMED);
174
175     zoomGesture->m_initialScale = zoomGesture->m_page.pageScaleFactor();
176     double x, y;
177     gtk_gesture_get_bounding_box_center(gesture, &x, &y);
178     zoomGesture->m_point = IntPoint(x, y);
179 }
180
181 void GestureController::ZoomGesture::scaleChanged(ZoomGesture* zoomGesture, double scale, GtkGesture*)
182 {
183     zoomGesture->m_scale = zoomGesture->m_initialScale * scale;
184     if (zoomGesture->m_idle.isScheduled())
185         return;
186
187     zoomGesture->m_idle.schedule("[WebKit] Zoom Gesture Idle", [zoomGesture]() {
188         // FIXME: Zoomed area is not correctly centered.
189         zoomGesture->m_page.scalePage(zoomGesture->m_scale, zoomGesture->m_point);
190     });
191 }
192
193 GestureController::ZoomGesture::ZoomGesture(WebPageProxy& page)
194     : Gesture(gtk_gesture_zoom_new(page.viewWidget()), page)
195     , m_initialScale(0)
196     , m_scale(0)
197 {
198     g_signal_connect_swapped(m_gesture.get(), "begin", G_CALLBACK(begin), this);
199     g_signal_connect_swapped(m_gesture.get(), "scale-changed", G_CALLBACK(scaleChanged), this);
200 }
201
202 } // namespace WebKit
203
204 #endif // HAVE(GTK_GESTURES)