Merge branch 'gesture-events'

Integrate GSoC 2017 work by Prashant Kumar implementing support for
gesture events.

Closes https://github.com/wxWidgets/wxWidgets/pull/551

Closes https://github.com/wxWidgets/wxWidgets/pull/565
This commit is contained in:
Vadim Zeitlin
2017-12-02 18:46:53 +01:00
28 changed files with 2894 additions and 17 deletions

View File

@@ -227,6 +227,80 @@ static GList* gs_sizeRevalidateList;
static bool gs_inSizeAllocate;
#endif
#if GTK_CHECK_VERSION(3,14,0)
#define wxGTK_HAS_GESTURES_SUPPORT
#endif
#ifdef wxGTK_HAS_GESTURES_SUPPORT
#include "wx/hashmap.h"
#include "wx/private/extfield.h"
namespace
{
// Per-window data for gestures support.
class wxWindowGesturesData
{
public:
// This class has rather unusual "resurrectable" semantics: it is
// initialized by the ctor as usual, but may then be uninitialized by
// calling Free() and re-initialized again by calling Reinit().
wxWindowGesturesData(wxWindow* win, GtkWidget *widget, int eventsMask)
{
Reinit(win, widget, eventsMask);
}
~wxWindowGesturesData()
{
Free();
}
void Reinit(wxWindow* win, GtkWidget *widget, int eventsMask);
void Free();
unsigned int m_touchCount;
unsigned int m_lastTouchTime;
int m_gestureState;
int m_allowedGestures;
int m_activeGestures;
wxPoint m_lastTouchPoint;
GdkEventSequence* m_touchSequence;
GtkGesture* m_vertical_pan_gesture;
GtkGesture* m_horizontal_pan_gesture;
GtkGesture* m_zoom_gesture;
GtkGesture* m_rotate_gesture;
GtkGesture* m_long_press_gesture;
};
WX_DECLARE_HASH_MAP(wxWindow*, wxWindowGesturesData*,
wxPointerHash, wxPointerEqual,
wxWindowGesturesMap);
typedef wxExternalField<wxWindow,
wxWindowGesturesData,
wxWindowGesturesMap> wxWindowGestures;
} // anonymous namespace
// This is true when the gesture has just started (currently used for pan gesture only)
static bool gs_gestureStart = false;
// Last offset for the pan gesture, this is used to calculate deltas for pan gesture event
static double gs_lastOffset = 0;
// Last scale provided by GTK
static gdouble gs_lastScale = 1.0;
// This is used to set the angle when rotate gesture ends.
static gdouble gs_lastAngle = 0;
// Last Zoom/Rotate gesture point
static wxPoint gs_lastGesturePoint;
#endif // wxGTK_HAS_GESTURES_SUPPORT
//-----------------------------------------------------------------------------
// debug
//-----------------------------------------------------------------------------
@@ -2624,6 +2698,10 @@ wxWindowGTK::~wxWindowGTK()
gs_sizeRevalidateList = g_list_remove_all(gs_sizeRevalidateList, this);
#endif
#ifdef wxGTK_HAS_GESTURES_SUPPORT
wxWindowGestures::EraseForObject(this);
#endif // wxGTK_HAS_GESTURES_SUPPORT
if (m_widget)
{
// Note that gtk_widget_destroy() does not destroy the widget, it just
@@ -2825,6 +2903,676 @@ static gboolean source_dispatch(GSource*, GSourceFunc, void*)
}
}
#ifdef wxGTK_HAS_GESTURES_SUPPORT
// Currently used for Press and Tap gesture only
enum GestureStates
{
begin = 1,
update,
end
};
enum TrackedGestures
{
two_finger_tap = 0x0001,
press_and_tap = 0x0002,
horizontal_pan = 0x0004,
vertical_pan = 0x0008
};
static void
pan_gesture_begin_callback(GtkGesture* WXUNUSED(gesture), GdkEventSequence* WXUNUSED(sequence), wxWindowGTK* WXUNUSED(win))
{
gs_gestureStart = true;
// Set it to 0, as this will be used to calculate the deltas for new pan gesture
gs_lastOffset = 0;
}
static void
horizontal_pan_gesture_end_callback(GtkGesture* gesture, GdkEventSequence* sequence, wxWindowGTK* win)
{
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
// Do not process horizontal pan, if there was no "pan" signal for it.
if ( !(data->m_allowedGestures & horizontal_pan) )
{
return;
}
gdouble x, y;
if ( !gtk_gesture_get_point(gesture, sequence, &x, &y) )
{
return;
}
data->m_allowedGestures &= ~horizontal_pan;
wxPanGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureEnd();
win->GTKProcessEvent(event);
}
static void
vertical_pan_gesture_end_callback(GtkGesture* gesture, GdkEventSequence* sequence, wxWindowGTK* win)
{
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
// Do not process vertical pan, if there was no "pan" signal for it.
if ( !(data->m_allowedGestures & vertical_pan) )
{
return;
}
gdouble x, y;
if ( !gtk_gesture_get_point(gesture, sequence, &x, &y) )
{
return;
}
data->m_allowedGestures &= ~vertical_pan;
wxPanGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureEnd();
win->GTKProcessEvent(event);
}
static void
pan_gesture_callback(GtkGesture* gesture, GtkPanDirection direction, gdouble offset, wxWindowGTK* win)
{
// The function that retrieves the GdkEventSequence (which will further be used to get the gesture point)
// should be called only when the gestrure is active
if ( !gtk_gesture_is_active(gesture) )
{
return;
}
GdkEventSequence* sequence = gtk_gesture_single_get_current_sequence(GTK_GESTURE_SINGLE(gesture));
gdouble x, y;
if ( !gtk_gesture_get_point(gesture, sequence, &x, &y) )
{
return;
}
wxPanGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
// This is the difference between this and the last pan gesture event in the current sequence
int delta = wxRound(offset - gs_lastOffset);
switch ( direction )
{
case GTK_PAN_DIRECTION_UP:
data->m_allowedGestures |= vertical_pan;
event.SetDelta(wxPoint(0, -delta));
break;
case GTK_PAN_DIRECTION_DOWN:
data->m_allowedGestures |= vertical_pan;
event.SetDelta(wxPoint(0, delta));
break;
case GTK_PAN_DIRECTION_RIGHT:
data->m_allowedGestures |= horizontal_pan;
event.SetDelta(wxPoint(delta, 0));
break;
case GTK_PAN_DIRECTION_LEFT:
data->m_allowedGestures |= horizontal_pan;
event.SetDelta(wxPoint(-delta, 0));
break;
}
// Update gs_lastOffset
gs_lastOffset = offset;
if ( gs_gestureStart )
{
event.SetGestureStart();
gs_gestureStart = false;
}
// Cancel press and tap gesture if it is not active during "pan" signal.
if( !(data->m_activeGestures & press_and_tap) )
{
data->m_allowedGestures &= ~press_and_tap;
}
win->GTKProcessEvent(event);
}
static void
zoom_gesture_callback(GtkGesture* gesture, gdouble scale, wxWindowGTK* win)
{
gdouble x, y;
if ( !gtk_gesture_get_bounding_box_center(gesture, &x, &y) )
{
return;
}
wxZoomGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetZoomFactor(scale);
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
// Cancel "Two FInger Tap Event" if scale has changed
if ( wxRound(scale * 1000) != wxRound(gs_lastScale * 1000) )
{
data->m_allowedGestures &= ~two_finger_tap;
}
gs_lastScale = scale;
// Save this point because the point obtained through gtk_gesture_get_bounding_box_center()
// in the "end" signal is not a zoom center
gs_lastGesturePoint = wxPoint(wxRound(x), wxRound(y));
win->GTKProcessEvent(event);
}
static void
zoom_gesture_begin_callback(GtkGesture* gesture, GdkEventSequence* WXUNUSED(sequence), wxWindowGTK* win)
{
gdouble x, y;
if ( !gtk_gesture_get_bounding_box_center(gesture, &x, &y) )
{
return;
}
gs_lastScale = 1.0;
wxZoomGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureStart();
// Save this point because the point obtained through gtk_gesture_get_bounding_box_center()
// in the "end" signal is not a zoom center
gs_lastGesturePoint = wxPoint(wxRound(x), wxRound(y));
win->GTKProcessEvent(event);
}
static void
zoom_gesture_end_callback(GtkGesture* WXUNUSED(gesture), GdkEventSequence* WXUNUSED(sequence), wxWindowGTK* win)
{
wxZoomGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(gs_lastGesturePoint);
event.SetGestureEnd();
event.SetZoomFactor(gs_lastScale);
win->GTKProcessEvent(event);
}
static void
rotate_gesture_begin_callback(GtkGesture* gesture, GdkEventSequence* WXUNUSED(sequence), wxWindowGTK* win)
{
gdouble x, y;
if ( !gtk_gesture_get_bounding_box_center(gesture, &x, &y) )
{
return;
}
wxRotateGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureStart();
// Save this point because the point obtained through gtk_gesture_get_bounding_box_center()
// in the "end" signal is not a rotation center
gs_lastGesturePoint = wxPoint(wxRound(x), wxRound(y));
win->GTKProcessEvent(event);
}
static void
rotate_gesture_callback(GtkGesture* gesture, gdouble WXUNUSED(angle_delta), gdouble angle, wxWindowGTK* win)
{
gdouble x, y;
if ( !gtk_gesture_get_bounding_box_center(gesture, &x, &y) )
{
return;
}
wxRotateGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetRotationAngle(angle);
// Save the angle to set it when the gesture ends.
gs_lastAngle = angle;
// Save this point because the point obtained through gtk_gesture_get_bounding_box_center()
// in the "end" signal is not a rotation center
gs_lastGesturePoint = wxPoint(wxRound(x), wxRound(y));
win->GTKProcessEvent(event);
}
static void
rotate_gesture_end_callback(GtkGesture* WXUNUSED(gesture), GdkEventSequence* WXUNUSED(sequence), wxWindowGTK* win)
{
wxRotateGestureEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(gs_lastGesturePoint);
event.SetGestureEnd();
event.SetRotationAngle(gs_lastAngle);
win->GTKProcessEvent(event);
}
static void
long_press_gesture_callback(GtkGesture* WXUNUSED(gesture), gdouble x, gdouble y, wxWindowGTK* win)
{
wxLongPressEvent event(win->GetId());
event.SetEventObject(win);
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureStart();
event.SetGestureEnd();
win->GTKProcessEvent(event);
}
static void
wxEmitTwoFingerTapEvent(GdkEventTouch* gdk_event, wxWindowGTK* win)
{
wxTwoFingerTapEvent event(win->GetId());
event.SetEventObject(win);
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
double lastX = data->m_lastTouchPoint.x;
double lastY = data->m_lastTouchPoint.y;
// Calculate smaller of x coordinate between 2 touches
double left = lastX <= gdk_event->x ? lastX : gdk_event->x;
// Calculate smaller of y coordinate between 2 touches
double up = lastY <= gdk_event->y ? lastY : gdk_event->y;
// Calculate gesture point .i.e center of the box formed by two touches
double x = left + abs(lastX - gdk_event->x)/2;
double y = up + abs(lastY - gdk_event->y)/2;
event.SetPosition(wxPoint(wxRound(x), wxRound(y)));
event.SetGestureStart();
event.SetGestureEnd();
win->GTKProcessEvent(event);
}
static void
wxEmitPressAndTapEvent(GdkEventTouch* gdk_event, wxWindowGTK* win)
{
wxPressAndTapEvent event(win->GetId());
event.SetEventObject(win);
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
switch ( data->m_gestureState )
{
case begin:
event.SetGestureStart();
break;
case update:
// Update touch point as the touch corresponding to "press" is moving
if ( data->m_touchSequence == gdk_event->sequence )
{
data->m_lastTouchPoint.x = gdk_event->x;
data->m_lastTouchPoint.y = gdk_event->y;
}
break;
case end:
event.SetGestureEnd();
break;
}
event.SetPosition(data->m_lastTouchPoint);
win->GTKProcessEvent(event);
}
static void
touch_callback(GtkWidget* WXUNUSED(widget), GdkEventTouch* gdk_event, wxWindowGTK* win)
{
wxWindowGesturesData* const data = wxWindowGestures::FromObject(win);
if ( !data )
return;
switch ( gdk_event->type )
{
case GDK_TOUCH_BEGIN:
data->m_touchCount++;
data->m_allowedGestures &= ~two_finger_tap;
if ( data->m_touchCount == 1 )
{
data->m_lastTouchTime = gdk_event->time;
data->m_lastTouchPoint.x = gdk_event->x;
data->m_lastTouchPoint.y = gdk_event->y;
// Save the sequence which identifies touch corresponding to "press"
data->m_touchSequence = gdk_event->sequence;
// "Press and Tap Event" may occur in future
data->m_allowedGestures |= press_and_tap;
}
// Check if two fingers are placed together .i.e difference between their time stamps is <= 200 milliseconds
else if ( data->m_touchCount == 2 && gdk_event->time - data->m_lastTouchTime <= wxTwoFingerTimeInterval )
{
// "Two Finger Tap Event" may be possible in the future
data->m_allowedGestures |= two_finger_tap;
// Cancel "Press and Tap Event"
data->m_allowedGestures &= ~press_and_tap;
}
break;
case GDK_TOUCH_UPDATE:
// If press and tap gesture is active and touch corresponding to that gesture is moving
if ( (data->m_activeGestures & press_and_tap) && gdk_event->sequence == data->m_touchSequence )
{
data->m_gestureState = update;
wxEmitPressAndTapEvent(gdk_event, win);
}
break;
case GDK_TOUCH_END:
case GDK_TOUCH_CANCEL:
data->m_touchCount--;
if ( data->m_touchCount == 1 )
{
data->m_lastTouchTime = gdk_event->time;
// If the touch corresponding to "press" is present and "tap" is produced by some ather touch
if ( (data->m_allowedGestures & press_and_tap) && gdk_event->sequence != data->m_touchSequence )
{
// Press and Tap gesture becomes active now
if ( !(data->m_activeGestures & press_and_tap) )
{
data->m_gestureState = begin;
data->m_activeGestures |= press_and_tap;
}
else
{
data->m_gestureState = update;
}
wxEmitPressAndTapEvent(gdk_event, win);
}
}
// Check if "Two Finger Tap Event" is possible and both the fingers have been lifted up together
else if ( (data->m_allowedGestures & two_finger_tap) && !data->m_touchCount
&& gdk_event->time - data->m_lastTouchTime <= wxTwoFingerTimeInterval )
{
// Process Two Finger Tap Event
wxEmitTwoFingerTapEvent(gdk_event, win);
}
// If the gesture was active and the touch corresponding to "press" is no longer on the screen
if ( (data->m_activeGestures & press_and_tap) && gdk_event->sequence == data->m_touchSequence )
{
data->m_gestureState = end;
data->m_activeGestures &= ~press_and_tap;
data->m_allowedGestures &= ~press_and_tap;
wxEmitPressAndTapEvent(gdk_event, win);
}
break;
default:
break;
}
}
void wxWindowGesturesData::Reinit(wxWindowGTK* win,
GtkWidget *widget,
int eventsMask)
{
m_touchCount = 0;
m_lastTouchTime = 0;
m_gestureState = 0;
m_allowedGestures = 0;
m_activeGestures = 0;
m_touchSequence = NULL;
if ( eventsMask & wxTOUCH_VERTICAL_PAN_GESTURE )
{
eventsMask &= ~wxTOUCH_VERTICAL_PAN_GESTURE;
m_vertical_pan_gesture = gtk_gesture_pan_new(widget, GTK_ORIENTATION_VERTICAL);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER(m_vertical_pan_gesture), GTK_PHASE_TARGET);
g_signal_connect (m_vertical_pan_gesture, "begin",
G_CALLBACK(pan_gesture_begin_callback), win);
g_signal_connect (m_vertical_pan_gesture, "pan",
G_CALLBACK(pan_gesture_callback), win);
g_signal_connect (m_vertical_pan_gesture, "end",
G_CALLBACK(vertical_pan_gesture_end_callback), win);
g_signal_connect (m_vertical_pan_gesture, "cancel",
G_CALLBACK(vertical_pan_gesture_end_callback), win);
}
else
{
m_vertical_pan_gesture = NULL;
}
if ( eventsMask & wxTOUCH_HORIZONTAL_PAN_GESTURE )
{
eventsMask &= ~wxTOUCH_HORIZONTAL_PAN_GESTURE;
m_horizontal_pan_gesture = gtk_gesture_pan_new(widget, GTK_ORIENTATION_HORIZONTAL);
// Pan signals are also generated in case of "left mouse down + mouse move". This can be disabled by
// calling gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_horizontal_pan_gesture), TRUE) and
// gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(verticaal_pan_gesture), TRUE) which will allow
// pan signals only for Touch events.
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER(m_horizontal_pan_gesture), GTK_PHASE_TARGET);
g_signal_connect (m_horizontal_pan_gesture, "begin",
G_CALLBACK(pan_gesture_begin_callback), win);
g_signal_connect (m_horizontal_pan_gesture, "pan",
G_CALLBACK(pan_gesture_callback), win);
g_signal_connect (m_horizontal_pan_gesture, "end",
G_CALLBACK(horizontal_pan_gesture_end_callback), win);
g_signal_connect (m_horizontal_pan_gesture, "cancel",
G_CALLBACK(horizontal_pan_gesture_end_callback), win);
}
else
{
m_horizontal_pan_gesture = NULL;
}
if ( eventsMask & wxTOUCH_ZOOM_GESTURE )
{
eventsMask &= ~wxTOUCH_ZOOM_GESTURE;
m_zoom_gesture = gtk_gesture_zoom_new(widget);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER(m_zoom_gesture), GTK_PHASE_TARGET);
g_signal_connect (m_zoom_gesture, "begin",
G_CALLBACK(zoom_gesture_begin_callback), win);
g_signal_connect (m_zoom_gesture, "scale-changed",
G_CALLBACK(zoom_gesture_callback), win);
g_signal_connect (m_zoom_gesture, "end",
G_CALLBACK(zoom_gesture_end_callback), win);
g_signal_connect (m_zoom_gesture, "cancel",
G_CALLBACK(zoom_gesture_end_callback), win);
}
else
{
m_zoom_gesture = NULL;
}
if ( eventsMask & wxTOUCH_ROTATE_GESTURE )
{
eventsMask &= ~wxTOUCH_ROTATE_GESTURE;
m_rotate_gesture = gtk_gesture_rotate_new(widget);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER(m_rotate_gesture), GTK_PHASE_TARGET);
g_signal_connect (m_rotate_gesture, "begin",
G_CALLBACK(rotate_gesture_begin_callback), win);
g_signal_connect (m_rotate_gesture, "angle-changed",
G_CALLBACK(rotate_gesture_callback), win);
g_signal_connect (m_rotate_gesture, "end",
G_CALLBACK(rotate_gesture_end_callback), win);
g_signal_connect (m_rotate_gesture, "cancel",
G_CALLBACK(rotate_gesture_end_callback), win);
}
else
{
m_rotate_gesture = NULL;
}
if ( eventsMask & wxTOUCH_PRESS_GESTURES )
{
eventsMask &= ~wxTOUCH_PRESS_GESTURES;
m_long_press_gesture = gtk_gesture_long_press_new(widget);
// "pressed" signal is also generated when left mouse is down for some minimum duration of time.
// This can be disable by calling gtk_gesture_single_set_touch_only(GTK_GESTURE_SINGLE(m_long_press_gesture), TRUE)
// which will allow "pressed" signal only for Touch events.
gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(m_long_press_gesture), GTK_PHASE_TARGET);
g_signal_connect (m_long_press_gesture, "pressed",
G_CALLBACK(long_press_gesture_callback), win);
}
else
{
m_long_press_gesture = NULL;
}
wxASSERT_MSG( eventsMask == 0, "Unknown touch event mask bit specified" );
gtk_widget_add_events(widget, GDK_TOUCHPAD_GESTURE_MASK);
g_signal_connect (widget, "touch-event",
G_CALLBACK(touch_callback), win);
}
void wxWindowGesturesData::Free()
{
g_clear_object(&m_vertical_pan_gesture);
g_clear_object(&m_horizontal_pan_gesture);
g_clear_object(&m_zoom_gesture);
g_clear_object(&m_rotate_gesture);
g_clear_object(&m_long_press_gesture);
// We don't current remove GDK_TOUCHPAD_GESTURE_MASK as this can't be done
// for a window as long as it's realized, and this might still be the case
// if we're called from EnableTouchEvents(wxTOUCH_NONE) and not from the
// dtor, but it shouldn't really be a problem.
}
#endif // wxGTK_HAS_GESTURES_SUPPORT
// This method must be always defined for GTK+ 3 as it's declared in the
// header, where we can't (easily) test for wxGTK_HAS_GESTURES_SUPPORT.
#ifdef __WXGTK3__
bool wxWindowGTK::EnableTouchEvents(int eventsMask)
{
#ifdef wxGTK_HAS_GESTURES_SUPPORT
// Check if gestures support is also available during run-time.
if ( gtk_check_version(3, 14, 0) == NULL )
{
wxWindowGesturesData* const dataOld = wxWindowGestures::FromObject(this);
if ( eventsMask == wxTOUCH_NONE )
{
// Reset the gestures data used by this object, but don't destroy
// it, as we could be called from an event handler, in which case
// this object could be still used after the event handler returns.
if ( dataOld )
dataOld->Free();
}
else
{
GtkWidget* const widget = GetConnectWidget();
if ( dataOld )
{
dataOld->Reinit(this, widget, eventsMask);
}
else
{
wxWindowGesturesData* const
dataNew = new wxWindowGesturesData(this, widget, eventsMask);
wxWindowGestures::StoreForObject(this, dataNew);
}
}
return true;
}
#endif // wxGTK_HAS_GESTURES_SUPPORT
return wxWindowBase::EnableTouchEvents(eventsMask);
}
#endif // __WXGTK3__
void wxWindowGTK::ConnectWidget( GtkWidget *widget )
{
static bool isSourceAttached;