diff --git a/include/wx/defs.h b/include/wx/defs.h index bdf417e3ce..2c5cdeeeb6 100644 --- a/include/wx/defs.h +++ b/include/wx/defs.h @@ -3104,6 +3104,12 @@ DECLARE_WXCOCOA_OBJC_CLASS(NSView); DECLARE_WXCOCOA_OBJC_CLASS(NSOpenGLContext); DECLARE_WXCOCOA_OBJC_CLASS(NSOpenGLPixelFormat); DECLARE_WXCOCOA_OBJC_CLASS( NSPrintInfo ); +DECLARE_WXCOCOA_OBJC_CLASS(NSGestureRecognizer); +DECLARE_WXCOCOA_OBJC_CLASS(NSPanGestureRecognizer); +DECLARE_WXCOCOA_OBJC_CLASS(NSMagnificationGestureRecognizer); +DECLARE_WXCOCOA_OBJC_CLASS(NSRotationGestureRecognizer); +DECLARE_WXCOCOA_OBJC_CLASS(NSPressGestureRecognizer); +DECLARE_WXCOCOA_OBJC_CLASS(NSTouch); #endif /* __WXMAC__ &__DARWIN__ */ #ifdef __WXMAC__ @@ -3267,6 +3273,7 @@ typedef struct _GdkDragContext GdkDragContext; #if defined(__WXGTK3__) typedef struct _GdkWindow GdkWindow; + typedef struct _GdkEventSequence GdkEventSequence; #elif defined(__WXGTK20__) typedef struct _GdkDrawable GdkWindow; typedef struct _GdkDrawable GdkPixmap; diff --git a/include/wx/event.h b/include/wx/event.h index e6e58464cc..c9d95501d8 100644 --- a/include/wx/event.h +++ b/include/wx/event.h @@ -631,6 +631,13 @@ class WXDLLIMPEXP_FWD_CORE wxInitDialogEvent; class WXDLLIMPEXP_FWD_CORE wxUpdateUIEvent; class WXDLLIMPEXP_FWD_CORE wxClipboardTextEvent; class WXDLLIMPEXP_FWD_CORE wxHelpEvent; +class WXDLLIMPEXP_FWD_CORE wxGestureEvent; +class WXDLLIMPEXP_FWD_CORE wxPanGestureEvent; +class WXDLLIMPEXP_FWD_CORE wxZoomGestureEvent; +class WXDLLIMPEXP_FWD_CORE wxRotateGestureEvent; +class WXDLLIMPEXP_FWD_CORE wxTwoFingerTapEvent; +class WXDLLIMPEXP_FWD_CORE wxLongPressEvent; +class WXDLLIMPEXP_FWD_CORE wxPressAndTapEvent; // Command events @@ -736,6 +743,14 @@ wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_SCROLLWIN_PAGEDOWN, wxScrollWin wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_SCROLLWIN_THUMBTRACK, wxScrollWinEvent); wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_SCROLLWIN_THUMBRELEASE, wxScrollWinEvent); + // Gesture events +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_GESTURE_PAN, wxPanGestureEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_GESTURE_ZOOM, wxZoomGestureEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_GESTURE_ROTATE, wxRotateGestureEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_TWO_FINGER_TAP, wxTwoFingerTapEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_LONG_PRESS, wxLongPressEvent); +wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_PRESS_AND_TAP, wxPressAndTapEvent); + // System events wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_SIZE, wxSizeEvent); wxDECLARE_EXPORTED_EVENT(WXDLLIMPEXP_CORE, wxEVT_MOVE, wxMoveEvent); @@ -1850,6 +1865,197 @@ private: wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxSetCursorEvent); }; + // Gesture Event + +const unsigned int wxTwoFingerTimeInterval = 200; + +class WXDLLIMPEXP_CORE wxGestureEvent : public wxEvent +{ +public: + wxGestureEvent(wxWindowID winid = 0, wxEventType type = wxEVT_NULL) + : wxEvent(winid, type) + { + m_isStart = false; + m_isEnd = false; + } + + wxGestureEvent(const wxGestureEvent& event) : wxEvent(event) + { + m_pos = event.m_pos; + m_isStart = event.m_isStart; + m_isEnd = event.m_isEnd; + } + + const wxPoint& GetPosition() const { return m_pos; } + void SetPosition(const wxPoint& pos) { m_pos = pos; } + bool IsGestureStart() const { return m_isStart; } + void SetGestureStart(bool isStart = true) { m_isStart = isStart; } + bool IsGestureEnd() const { return m_isEnd; } + void SetGestureEnd(bool isEnd = true) { m_isEnd = isEnd; } + + virtual wxEvent *Clone() const { return new wxGestureEvent(*this); } + +protected: + wxPoint m_pos; + bool m_isStart, m_isEnd; + + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxGestureEvent); + +}; + + // Pan Gesture Event + + /* + wxEVT_GESTURE_PAN + */ + +class WXDLLIMPEXP_CORE wxPanGestureEvent : public wxGestureEvent +{ +public: + wxPanGestureEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_GESTURE_PAN) + { + } + + wxPanGestureEvent(const wxPanGestureEvent& event) + : wxGestureEvent(event), + m_delta(event.m_delta) + { + } + + wxPoint GetDelta() const { return m_delta; } + void SetDelta(const wxPoint& delta) { m_delta = delta; } + + virtual wxEvent *Clone() const { return new wxPanGestureEvent(*this); } + +private: + wxPoint m_delta; + + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxPanGestureEvent); +}; + + // Zoom Gesture Event + + /* + wxEVT_GESTURE_ZOOM + */ + +class WXDLLIMPEXP_CORE wxZoomGestureEvent : public wxGestureEvent +{ +public: + wxZoomGestureEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_GESTURE_ZOOM) + { m_zoomFactor = 1.0; } + + wxZoomGestureEvent(const wxZoomGestureEvent& event) : wxGestureEvent(event) + { + m_zoomFactor = event.m_zoomFactor; + } + + double GetZoomFactor() const { return m_zoomFactor; } + void SetZoomFactor(double zoomFactor) { m_zoomFactor = zoomFactor; } + + virtual wxEvent *Clone() const { return new wxZoomGestureEvent(*this); } + +private: + double m_zoomFactor; + + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxZoomGestureEvent); +}; + + // Rotate Gesture Event + + /* + wxEVT_GESTURE_ROTATE + */ + +class WXDLLIMPEXP_CORE wxRotateGestureEvent : public wxGestureEvent +{ +public: + wxRotateGestureEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_GESTURE_ROTATE) + { m_rotationAngle = 0.0; } + + wxRotateGestureEvent(const wxRotateGestureEvent& event) : wxGestureEvent(event) + { + m_rotationAngle = event.m_rotationAngle; + } + + double GetRotationAngle() const { return m_rotationAngle; } + void SetRotationAngle(double rotationAngle) { m_rotationAngle = rotationAngle; } + + virtual wxEvent *Clone() const { return new wxRotateGestureEvent(*this); } + +private: + double m_rotationAngle; + + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxRotateGestureEvent); +}; + + // Two Finger Tap Gesture Event + + /* + wxEVT_TWO_FINGER_TAP + */ + +class WXDLLIMPEXP_CORE wxTwoFingerTapEvent : public wxGestureEvent +{ +public: + wxTwoFingerTapEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_TWO_FINGER_TAP) + { } + + wxTwoFingerTapEvent(const wxTwoFingerTapEvent& event) : wxGestureEvent(event) + { } + + virtual wxEvent *Clone() const { return new wxTwoFingerTapEvent(*this); } + +private: + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxTwoFingerTapEvent); +}; + + // Long Press Gesture Event + + /* + wxEVT_LONG_PRESS + */ + +class WXDLLIMPEXP_CORE wxLongPressEvent : public wxGestureEvent +{ +public: + wxLongPressEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_LONG_PRESS) + { } + + wxLongPressEvent(const wxLongPressEvent& event) : wxGestureEvent(event) + { } + + virtual wxEvent *Clone() const { return new wxLongPressEvent(*this); } +private: + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxLongPressEvent); +}; + + // Press And Tap Gesture Event + + /* + wxEVT_PRESS_AND_TAP + */ + +class WXDLLIMPEXP_CORE wxPressAndTapEvent : public wxGestureEvent +{ +public: + wxPressAndTapEvent(wxWindowID winid = 0) + : wxGestureEvent(winid, wxEVT_PRESS_AND_TAP) + { } + + wxPressAndTapEvent(const wxPressAndTapEvent& event) : wxGestureEvent(event) + { } + + virtual wxEvent *Clone() const { return new wxPressAndTapEvent(*this); } +private: + wxDECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxPressAndTapEvent); +}; + // Keyboard input event class /* @@ -3908,7 +4114,12 @@ typedef void (wxEvtHandler::*wxContextMenuEventFunction)(wxContextMenuEvent&); typedef void (wxEvtHandler::*wxMouseCaptureChangedEventFunction)(wxMouseCaptureChangedEvent&); typedef void (wxEvtHandler::*wxMouseCaptureLostEventFunction)(wxMouseCaptureLostEvent&); typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent&); - +typedef void (wxEvtHandler::*wxPanGestureEventFunction)(wxPanGestureEvent&); +typedef void (wxEvtHandler::*wxZoomGestureEventFunction)(wxZoomGestureEvent&); +typedef void (wxEvtHandler::*wxRotateGestureEventFunction)(wxRotateGestureEvent&); +typedef void (wxEvtHandler::*wxTwoFingerTapEventFunction)(wxTwoFingerTapEvent&); +typedef void (wxEvtHandler::*wxLongPressEventFunction)(wxLongPressEvent&); +typedef void (wxEvtHandler::*wxPressAndTapEventFunction)(wxPressAndTapEvent&); #define wxCommandEventHandler(func) \ wxEVENT_HANDLER_CAST(wxCommandEventFunction, func) @@ -3983,6 +4194,18 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent& wxEVENT_HANDLER_CAST(wxMouseCaptureLostEventFunction, func) #define wxClipboardTextEventHandler(func) \ wxEVENT_HANDLER_CAST(wxClipboardTextEventFunction, func) +#define wxPanGestureEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxPanGestureEventFunction, func) +#define wxZoomGestureEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxZoomGestureEventFunction, func) +#define wxRotateGestureEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxRotateGestureEventFunction, func) +#define wxTwoFingerTapEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxTwoFingerTapEventFunction, func) +#define wxLongPressEventHandler(func) \ + wxEVENT_HANDLER_CAST(wxLongPressEventFunction, func) +#define wxPressAndTapEvent(func) \ + wxEVENT_HANDLER_CAST(wxPressAndTapEventFunction, func) #endif // wxUSE_GUI @@ -4317,6 +4540,14 @@ typedef void (wxEvtHandler::*wxClipboardTextEventFunction)(wxClipboardTextEvent& EVT_COMMAND_SCROLL_THUMBRELEASE(winid, func) \ EVT_COMMAND_SCROLL_CHANGED(winid, func) +// Gesture events +#define EVT_GESTURE_PAN(winid, func) wx__DECLARE_EVT1(wxEVT_GESTURE_PAN, winid, wxPanGestureEventHandler(func)) +#define EVT_GESTURE_ZOOM(winid, func) wx__DECLARE_EVT1(wxEVT_GESTURE_ZOOM, winid, wxZoomGestureEventHandler(func)) +#define EVT_GESTURE_ROTATE(winid, func) wx__DECLARE_EVT1(wxEVT_GESTURE_ROTATE, winid, wxRotateGestureEventHandler(func)) +#define EVT_TWO_FINGER_TAP(winid, func) wx__DECLARE_EVT1(wxEVT_TWO_FINGER_TAP, winid, wxTwoFingerTapEventHandler(func)) +#define EVT_LONG_PRESS(winid, func) wx__DECLARE_EVT1(wxEVT_LONG_PRESS, winid, wxLongPressEventHandler(func)) +#define EVT_PRESS_AND_TAP(winid, func) wx__DECLARE_EVT1(wxEVT_PRESS_AND_TAP, winid, wxPressAndTapEvent(func)) + // Convenience macros for commonly-used commands #define EVT_CHECKBOX(winid, func) wx__DECLARE_EVT1(wxEVT_CHECKBOX, winid, wxCommandEventHandler(func)) #define EVT_CHOICE(winid, func) wx__DECLARE_EVT1(wxEVT_CHOICE, winid, wxCommandEventHandler(func)) diff --git a/include/wx/gtk/window.h b/include/wx/gtk/window.h index e4041d9a3b..fd3f683491 100644 --- a/include/wx/gtk/window.h +++ b/include/wx/gtk/window.h @@ -77,6 +77,9 @@ public: virtual bool Reparent( wxWindowBase *newParent ) wxOVERRIDE; virtual void WarpPointer(int x, int y) wxOVERRIDE; +#ifdef __WXGTK3__ + virtual bool EnableTouchEvents(int eventsMask) wxOVERRIDE; +#endif // __WXGTK3__ virtual void Refresh( bool eraseBackground = true, const wxRect *rect = (const wxRect *) NULL ) wxOVERRIDE; diff --git a/include/wx/msw/window.h b/include/wx/msw/window.h index 286d9855ad..1a6af48f5b 100644 --- a/include/wx/msw/window.h +++ b/include/wx/msw/window.h @@ -100,6 +100,7 @@ public: virtual bool Reparent(wxWindowBase *newParent) wxOVERRIDE; virtual void WarpPointer(int x, int y) wxOVERRIDE; + virtual bool EnableTouchEvents(int eventsMask) wxOVERRIDE; virtual void Refresh( bool eraseBackground = true, const wxRect *rect = (const wxRect *) NULL ) wxOVERRIDE; @@ -360,6 +361,16 @@ public: bool HandleMouseWheel(wxMouseWheelAxis axis, WXWPARAM wParam, WXLPARAM lParam); + // Common gesture event initialization, returns true if it is the initial + // event (GF_BEGIN set in flags), false otherwise. + bool InitGestureEvent(wxGestureEvent& event, const wxPoint& pt, WXDWORD flags); + + bool HandlePanGesture(const wxPoint& pt, WXDWORD flags); + bool HandleZoomGesture(const wxPoint& pt, WXDWORD fingerDistance, WXDWORD flags); + bool HandleRotateGesture(const wxPoint& pt, WXDWORD angleArgument, WXDWORD flags); + bool HandleTwoFingerTap(const wxPoint& pt, WXDWORD flags); + bool HandlePressAndTap(const wxPoint& pt, WXDWORD flags); + bool HandleChar(WXWPARAM wParam, WXLPARAM lParam); bool HandleKeyDown(WXWPARAM wParam, WXLPARAM lParam); bool HandleKeyUp(WXWPARAM wParam, WXLPARAM lParam); diff --git a/include/wx/osx/cocoa/private.h b/include/wx/osx/cocoa/private.h index 7900204a31..db182e4479 100644 --- a/include/wx/osx/cocoa/private.h +++ b/include/wx/osx/cocoa/private.h @@ -128,6 +128,7 @@ public : void SetToolTip( wxToolTip* tooltip ); void InstallEventHandler( WXWidget control = NULL ); + bool EnableTouchEvents(int eventsMask); virtual bool ShouldHandleKeyNavigation(const wxKeyEvent &event) const; bool DoHandleKeyNavigation(const wxKeyEvent &event); @@ -143,6 +144,15 @@ public : void SetupCoordinates(wxCoord &x, wxCoord &y, NSEvent *nsEvent); virtual bool SetupCursor(NSEvent* event); +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + virtual void PanGestureEvent(NSPanGestureRecognizer *panGestureRecognizer); + virtual void ZoomGestureEvent(NSMagnificationGestureRecognizer *magnificationGestureRecognizer); + virtual void RotateGestureEvent(NSRotationGestureRecognizer *rotationGestureRecognizer); + virtual void LongPressEvent(NSPressGestureRecognizer *pressGestureRecognizer); + virtual void TouchesBegan(NSEvent *event); + virtual void TouchesMoved(NSEvent *event); + virtual void TouchesEnded(NSEvent *event); +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 #if !wxOSX_USE_NATIVE_FLIPPED void SetFlipped(bool flipped); diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index a372aa529a..5b012f9048 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -326,6 +326,8 @@ public : virtual void InstallEventHandler( WXWidget control = NULL ) = 0; + virtual bool EnableTouchEvents(int eventsMask) = 0; + // Mechanism used to keep track of whether a change should send an event // Do SendEvents(false) when starting actions that would trigger programmatic events // and SendEvents(true) at the end of the block. diff --git a/include/wx/osx/iphone/private.h b/include/wx/osx/iphone/private.h index b88a863a54..484e7525ee 100644 --- a/include/wx/osx/iphone/private.h +++ b/include/wx/osx/iphone/private.h @@ -105,6 +105,7 @@ public : void SetFont( const wxFont & font , const wxColour& foreground , long windowStyle, bool ignoreBlack = true ); void InstallEventHandler( WXWidget control = NULL ); + bool EnableTouchEvents(int WXUNUSED(eventsMask)) { return false; } virtual void DoNotifyFocusEvent(bool receivedFocus, wxWidgetImpl* otherWindow); diff --git a/include/wx/osx/window.h b/include/wx/osx/window.h index 7db20f4810..caee9c1659 100644 --- a/include/wx/osx/window.h +++ b/include/wx/osx/window.h @@ -77,6 +77,7 @@ public: virtual void SetFocus() wxOVERRIDE; virtual void WarpPointer( int x, int y ) wxOVERRIDE; + virtual bool EnableTouchEvents(int eventsMask) wxOVERRIDE; virtual void Refresh( bool eraseBackground = true, const wxRect *rect = NULL ) wxOVERRIDE; diff --git a/include/wx/private/extfield.h b/include/wx/private/extfield.h new file mode 100644 index 0000000000..01451e83eb --- /dev/null +++ b/include/wx/private/extfield.h @@ -0,0 +1,84 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/private/extfield.h +// Purpose: Declare wxExternalField helper +// Author: Vadim Zeitlin +// Created: 2017-11-21 +// Copyright: (c) 2017 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_PRIVATE_EXTFIELD_H_ +#define _WX_PRIVATE_EXTFIELD_H_ + +// ---------------------------------------------------------------------------- +// wxExternalField: store object data outside of it +// ---------------------------------------------------------------------------- + +// This class allows to store some data without consuming space for the objects +// that don't need it and can be useful for avoiding to add rarely used fields +// to the classes that are used by many objects, e.g. wxWindow. +// +// Note that using this class costs in speed and convenience of access to the +// field, which requires a hash lookup instead of accessing it directly. It +// also only currently works for heap-allocated fields as it's probably never +// worth using it for fields of simple types. +// +// Template parameter Object is the class that "contains" the field, Field is +// the type of the field itself and FieldMap is the hash map, defined +// separately using WX_DECLARE_HASH_MAP(), with Object* as the key and Field* +// as the value type. +template +class wxExternalField +{ +public: + typedef Object ObjectType; + typedef Field FieldType; + typedef FieldMap MapType; + + // Store the field object to be used for the given object, replacing the + // existing one, if any. + // + // This method takes ownership of the field pointer which will be destroyed + // by EraseForObject(). + static void StoreForObject(ObjectType* obj, FieldType* field) + { + const typename MapType::iterator it = ms_map.find(obj); + if ( it != ms_map.end() ) + { + delete it->second; + it->second = field; + } + else + { + ms_map.insert(typename MapType::value_type(obj, field)); + } + } + + // Find the object for the corresponding window. + static FieldType* FromObject(ObjectType* obj) + { + const typename MapType::const_iterator it = ms_map.find(obj); + return it == ms_map.end() ? NULL : it->second; + } + + // Erase the object used for the corresponding window, return true if there + // was one or false otherwise. + static bool EraseForObject(ObjectType* obj) + { + const typename MapType::iterator it = ms_map.find(obj); + if ( it == ms_map.end() ) + return false; + + delete it->second; + ms_map.erase(it); + return true; + } + +private: + static FieldMap ms_map; +}; + +template +M wxExternalField::ms_map; + +#endif // _WX_PRIVATE_EXTFIELD_H_ diff --git a/include/wx/window.h b/include/wx/window.h index 440f097a21..38d70ab7a8 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -133,6 +133,20 @@ enum wxShowEffect wxSHOW_EFFECT_MAX }; +// Values for EnableTouchEvents() mask. +enum +{ + wxTOUCH_NONE = 0x0000, + wxTOUCH_VERTICAL_PAN_GESTURE = 0x0001, + wxTOUCH_HORIZONTAL_PAN_GESTURE = 0x0002, + wxTOUCH_PAN_GESTURES = wxTOUCH_VERTICAL_PAN_GESTURE | + wxTOUCH_HORIZONTAL_PAN_GESTURE, + wxTOUCH_ZOOM_GESTURE = 0x0004, + wxTOUCH_ROTATE_GESTURE = 0x0008, + wxTOUCH_PRESS_GESTURES = 0x0010, + wxTOUCH_ALL_GESTURES = 0x001f +}; + // flags for SendSizeEvent() enum { @@ -1033,6 +1047,13 @@ public: virtual bool HasCapture() const { return (wxWindow *)this == GetCapture(); } + // enable the specified touch events for this window, return false if + // the requested events are not supported + virtual bool EnableTouchEvents(int WXUNUSED(eventsMask)) + { + return false; + } + // painting the window // ------------------- diff --git a/interface/wx/event.h b/interface/wx/event.h index 3d92c2d0db..ae1eaeee9a 100644 --- a/interface/wx/event.h +++ b/interface/wx/event.h @@ -3644,6 +3644,251 @@ public: void SetPosition(int pos); }; + + +/** @class wxGestureEvent + This is the base class for all supported gesture events. + + @note Gesture events are not generated by default, you must call + wxWindow::EnableTouchEvents() with the appropriate parameter to + request their generation. + + @library{wxcore} + @category{events} + + @see wxPanGestureEvent, wxZoomGestureEvent, wxRotateGestureEvent + + @since 3.1.1 +*/ +class wxGestureEvent : public wxEvent +{ +public: + /** + Constructor. + */ + wxGestureEvent(wxWindowID winid = 0, wxEventType type = wxEVT_NULL); + + /** + Returns the position where the event took effect, in client coordinates: position of Pan event, + center of zoom for Zoom event, center of rotation for Rotate event, center of box formed by 2 fingers + for Two Finger Tap event and position of the pressed finger for Press and Tap Event. + */ + const wxPoint& GetPosition() const; + + /** + Returns true if the event was the first in a gesture sequence. + */ + bool IsGestureStart() const; + + /** + Returns true if the event was the last in a gesture sequence. + */ + bool IsGestureEnd() const; + + /** + Sets the position where the event took effect, in client coordinates: position of Pan event, + center of zoom for Zoom event, center of rotation for Rotate event. + */ + void SetPosition(const wxPoint& pos); + + /** + Sets the event to be the first in a gesture sequence. + */ + void SetGestureStart(bool isStart = true); + + /** + Sets the event to be the last in a gesture sequence. + */ + void SetGestureEnd(bool isEnd = true); +}; + + +/** @class wxPanGestureEvent + + This event is generated when the user moves a finger on the surface. + + wxGTK also generates this event during mouse dragging (mouse motion while a left mouse button is depressed). + + Note that OSX requires the primary mouse button to be pressed while performing the finger movement. + + @beginEventTable{wxPanGestureEvent} + @event{EVT_GESTURE_PAN(id, func)} + Process a @c wxEVT_GESTURE_PAN. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxPanGestureEvent : class wxGestureEvent +{ +public: + /** + Constructor. + */ + wxPanGestureEvent(wxWindowID winid = 0); + + /** + Returns the distance covered since the previous panning event. + */ + wxPoint GetDelta() const; + + /** + Sets the distance covered since the previous panning event. + */ + void SetDelta(const wxPoint& delta); +}; + + +/** @class wxZoomGestureEvent + + This event is generated when two fingers pinch the surface to zoom in or out. + + @beginEventTable{wxZoomGestureEvent} + @event{EVT_GESTURE_ZOOM(id, func)} + Process a @c wxEVT_GESTURE_ZOOM. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxZoomGestureEvent : public wxGestureEvent +{ +public: + /** + Constructor. + */ + wxZoomGestureEvent(wxWindowID windid = 0); + + /** + Returns the zoom Factor since the gesture started. Hence, starting of the gesture + is considered as 1:1. A value greater than 1.0 means we should enlarge + (or zoom in), a value less than 1.0 means we should shrink (or zoom out). + */ + double GetZoomFactor() const; + + /** + Sets the zoom Factor. + */ + double SetZoomFactor() const; +}; + + +/** @class wxRotateGestureEvent + + This event is generated when two fingers move in opposite directions on the surface. + + @beginEventTable{wxRotateGestureEvent} + @event{EVT_GESTURE_ROTATE(id, func)} + Process a @c wxEVT_GESTURE_ROTATE. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxRotateGestureEvent : public wxGestureEvent +{ +public: + /** + Constructor. + */ + wxRotateGestureEvent(wxWindowID windid = 0); + + /** + Returns the total angle of rotation in radians in clockwise direction since the + gesture was first started i.e. when IsGestureStart() returned true. This value is always + greater than or equal to zero. + */ + double GetRotationAngle() const; + + /** + Sets the total angle of rotation in radians in clockwise direction since the + gesture was first started i.e. when IsGestureStart() returned true. This value is always + greater than or equal to zero. + */ + void SetRotationAngle(double rotationAngle); +}; + +/** @class wxTwoFingerTapEvent + + This event is generated when two fingers touch the surface at the same time. + + @beginEventTable{wxTwoFingerTapEvent} + @event{EVT_TWO_FINGER_TAP(id, func)} + Process a @c wxEVT_TWO_FINGER_TAP. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxTwoFingerTapEvent : public wxGestureEvent +{ +public: + /** + Constructor. + */ + wxTwoFingerTapEvent(wxWindowID windid = 0); +}; + +/** @class wxLongPressEvent + + This event is generated when one finger touches the surface and remains stationary. + + Note that currently it is only generated under wxGTK and wxOSX. + + wxGTK also generates this event when left mouse button is being pressed for some minimum duration of time. + + @beginEventTable{wxLongPressEvent} + @event{EVT_LONG_PRESS(id, func)} + Process a @c wxEVT_LONG_PRESS. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxLongPressEvent : public wxGestureEvent +{ +public: + /** + Constructor. + */ + wxLongPressEvent(wxWindowID windid = 0); +}; + +/** @class wxPressAndTapEvent + + This event is generated when the user press the surface with one finger and taps with another. + + Note that once started the event will also be generated when the finger that was pressed moves on surface. + + @beginEventTable{wxPressAndTapEvent} + @event{EVT_PRESS_AND_TAP(id, func)} + Process a @c wxEVT_PRESS_AND_TAP. + @endEventTable + + @library{wxcore} + @category{events} + + @since 3.1.1 +*/ +class wxPressAndTapEvent : public wxGestureEvent +{ +public: + /** + Constructor. + */ + wxPressAndTapEvent(wxWindowID windid = 0); +}; + #endif // wxUSE_GUI #if wxUSE_BASE diff --git a/interface/wx/window.h b/interface/wx/window.h index 0fa2a58c50..672a6f94e9 100644 --- a/interface/wx/window.h +++ b/interface/wx/window.h @@ -52,6 +52,68 @@ enum wxShowEffect }; +/** + Values for wxWindow::EnableTouchEvents() mask. + + The values other than ::wxTOUCH_NONE and ::wxTOUCH_ALL_GESTURES can be + combined together to request enabling events for the specified gestures and + for them only. + + @since 3.1.1 + */ +enum +{ + /** + Don't generate any touch events. + */ + wxTOUCH_NONE, + + /** + Generate wxPanGestureEvent for vertical pans. + + Note that under macOS horizontal pan events are also enabled when this + flag is specified. + */ + wxTOUCH_VERTICAL_PAN_GESTURE, + + /** + Generate wxPanGestureEvent for horizontal pans. + + Note that under macOS vertical pan events are also enabled when this + flag is specified. + */ + wxTOUCH_HORIZONTAL_PAN_GESTURE, + + /** + Generate wxPanGestureEvent for any pans. + + This is just a convenient combination of wxTOUCH_VERTICAL_PAN_GESTURE + and wxTOUCH_HORIZONTAL_PAN_GESTURE. + */ + wxTOUCH_PAN_GESTURES, + + /** + Generate wxZoomGestureEvent. + */ + wxTOUCH_ZOOM_GESTURE, + + /** + Generate wxRotateGestureEvent. + */ + wxTOUCH_ROTATE_GESTURE, + + /** + Generate events for press or tap gestures such as wxTwoFingerTapEvent, + wxLongPressEvent and wxPressAndTapEvent. + */ + wxTOUCH_PRESS_GESTURES, + + /** + Enable all supported gesture events. + */ + wxTOUCH_ALL_GESTURES +}; + /** flags for SendSizeEvent() */ @@ -3424,6 +3486,28 @@ public: */ virtual void WarpPointer(int x, int y); + /** + Request generation of touch events for this window. + + Each call to this function supersedes the previous ones, i.e. if you + want to receive events for both zoom and rotate gestures, you need to + call + @code + EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE); + @endcode + instead of calling it twice in a row as the second call would disable + the first gesture. + + @param eventsMask Either wxTOUCH_NONE or wxTOUCH_ALL_GESTURES to + disable or enable gesture events for this window. + + @return @true if the specified events were enabled or @false if the + current platform doesn't support touch events. + + @since 3.1.1 + */ + virtual bool EnableTouchEvents(int eventsMask); + //@} diff --git a/samples/event/Makefile.in b/samples/event/Makefile.in index a790ba23cd..ea6a06a19e 100644 --- a/samples/event/Makefile.in +++ b/samples/event/Makefile.in @@ -49,7 +49,8 @@ EVENT_CXXFLAGS = -D__WX$(TOOLKIT)__ $(__WXUNIV_DEFINE_p) $(__DEBUG_DEFINE_p) \ $(SAMPLES_CXXFLAGS) $(CPPFLAGS) $(CXXFLAGS) EVENT_OBJECTS = \ $(__event___win32rc) \ - event_event.o + event_event.o \ + event_gestures.o ### Conditionally set variables: ### @@ -177,6 +178,8 @@ event_sample_rc.o: $(srcdir)/../../samples/sample.rc event_event.o: $(srcdir)/event.cpp $(CXXC) -c -o $@ $(EVENT_CXXFLAGS) $(srcdir)/event.cpp +event_gestures.o: $(srcdir)/gestures.cpp + $(CXXC) -c -o $@ $(EVENT_CXXFLAGS) $(srcdir)/gestures.cpp # Include dependency info, if present: @IF_GNU_MAKE@-include ./.deps/*.d diff --git a/samples/event/event.bkl b/samples/event/event.bkl index e5b558fac3..ab8ee98c5a 100644 --- a/samples/event/event.bkl +++ b/samples/event/event.bkl @@ -4,7 +4,8 @@ - event.cpp + event.cpp gestures.cpp + gestures.h core base diff --git a/samples/event/event.cpp b/samples/event/event.cpp index ef19b8bb63..8cc96efd18 100644 --- a/samples/event/event.cpp +++ b/samples/event/event.cpp @@ -35,6 +35,7 @@ #include #include +#include "gestures.h" // ---------------------------------------------------------------------------- // event constants @@ -144,6 +145,9 @@ public: void OnClickDynamicHandlerButton(wxCommandEvent& event); void OnClickStaticHandlerFrame(wxCommandEvent& event); + // Gesture + void OnGesture(wxCommandEvent& event); + private: // symbolic names for the status bar fields enum @@ -178,6 +182,8 @@ private: // the button used to highlight the event handlers execution order MyEvtTestButton *m_testBtn; + wxWindowRef m_gestureFrame; + // any class wishing to process wxWidgets events must use this macro wxDECLARE_EVENT_TABLE(); @@ -220,7 +226,8 @@ enum Event_Push, Event_Pop, Event_Custom, - Event_Test + Event_Test, + Event_Gesture }; // ---------------------------------------------------------------------------- @@ -252,6 +259,7 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU(Event_Test, MyFrame::OnTest) EVT_MENU(Event_Push, MyFrame::OnPushEventHandler) EVT_MENU(Event_Pop, MyFrame::OnPopEventHandler) + EVT_MENU(Event_Gesture, MyFrame::OnGesture) EVT_UPDATE_UI(Event_Pop, MyFrame::OnUpdateUIPop) @@ -381,6 +389,8 @@ MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) menuEvent->AppendSeparator(); menuEvent->Append(Event_Custom, wxT("Fire c&ustom event\tCtrl-U"), wxT("Generate a custom event")); + menuEvent->Append(Event_Gesture, wxT("&Gesture events\tCtrl-G"), + wxT("Gesture event")); // now append the freshly created menu to the menu bar... wxMenuBar *menuBar = new wxMenuBar(); @@ -447,7 +457,8 @@ MyFrame::~MyFrame() void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) { - // true is to force the frame to close + if ( m_gestureFrame ) + m_gestureFrame->Close(true); Close(true); } @@ -577,6 +588,19 @@ void MyFrame::OnPopEventHandler(wxCommandEvent& WXUNUSED(event)) #endif // wxUSE_STATUSBAR } +void MyFrame::OnGesture(wxCommandEvent& WXUNUSED(event)) +{ + if ( m_gestureFrame ) + { + m_gestureFrame->Raise(); + } + else + { + m_gestureFrame = new MyGestureFrame(); + m_gestureFrame->Show(true); + } +} + void MyFrame::OnTest(wxCommandEvent& WXUNUSED(event)) { wxLogMessage(wxT("This is the test event handler in the main frame")); @@ -602,4 +626,3 @@ void MyFrame::OnProcessCustom(wxCommandEvent& WXUNUSED(event)) { wxLogMessage(wxT("Got a custom event!")); } - diff --git a/samples/event/event_vc7.vcproj b/samples/event/event_vc7.vcproj index cbf52c337f..0036a2c140 100644 --- a/samples/event/event_vc7.vcproj +++ b/samples/event/event_vc7.vcproj @@ -286,6 +286,17 @@ + + + + + + + + + + + + + + + + + + Add(myPanel, wxSizerFlags(1).Expand()); + sizer->Add(m_logText, wxSizerFlags().Expand()); + SetSizer(sizer); + + // Log to the text control + delete wxLog::SetActiveTarget(new wxLogTextCtrl(m_logText)); + + // Bind all gestures to the same event handler, which must run before + // the other handlers, to clear the log window + myPanel->Bind(wxEVT_GESTURE_PAN, &MyGestureFrame::OnGesture, this); + myPanel->Bind(wxEVT_GESTURE_ZOOM, &MyGestureFrame::OnGesture, this); + myPanel->Bind(wxEVT_GESTURE_ROTATE, &MyGestureFrame::OnGesture, this); + myPanel->Bind(wxEVT_TWO_FINGER_TAP, &MyGestureFrame::OnGesture, this); + myPanel->Bind(wxEVT_LONG_PRESS, &MyGestureFrame::OnGesture, this); + myPanel->Bind(wxEVT_PRESS_AND_TAP, &MyGestureFrame::OnGesture, this); + + Bind(wxEVT_CLOSE_WINDOW, &MyGestureFrame::OnQuit, this); +} + +MyGesturePanel::MyGesturePanel(MyGestureFrame *parent) + : wxPanel(parent, wxID_ANY), + m_bitmap(horse_xpm) +{ + Bind(wxEVT_PAINT, &MyGesturePanel::OnPaint, this); + + if ( !EnableTouchEvents(wxTOUCH_ALL_GESTURES) ) + { + wxLogError("Failed to enable touch events"); + + // Still bind event handlers just in case they still work? + } + + // Event handlers + Bind(wxEVT_GESTURE_PAN, &MyGesturePanel::OnPan, this); + Bind(wxEVT_GESTURE_ZOOM, &MyGesturePanel::OnZoom, this); + Bind(wxEVT_GESTURE_ROTATE, &MyGesturePanel::OnRotate, this); + Bind(wxEVT_TWO_FINGER_TAP, &MyGesturePanel::OnTwoFingerTap, this); + Bind(wxEVT_LONG_PRESS, &MyGesturePanel::OnLongPress, this); + Bind(wxEVT_PRESS_AND_TAP, &MyGesturePanel::OnPressAndTap, this); + + m_lastZoomFactor = 1.0; +} + +void MyGestureFrame::OnQuit(wxCloseEvent& WXUNUSED(event)) +{ + Destroy(); +} + +void MyGestureFrame::OnGesture(wxGestureEvent& event) +{ + if ( event.IsGestureStart() ) + m_logText->Clear(); + + event.Skip(); +} + +void MyGesturePanel::OnPaint(wxPaintEvent& WXUNUSED(event)) +{ + wxPaintDC paintDC(this); + + wxGCDC dc(paintDC); + + dc.Clear(); + + dc.SetTransformMatrix(m_affineMatrix); + dc.DrawBitmap(m_bitmap, wxRound(m_translateDistance.m_x), wxRound(m_translateDistance.m_y)); +} + +void MyGesturePanel::OnPan(wxPanGestureEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Pan gesture started\n"); + } + + const wxPoint delta = event.GetDelta(); + wxLogMessage("Pan gesture performed with delta = (%d,%d), " + "with current position (%d,%d)", + delta.x, delta.y, + event.GetPosition().x, event.GetPosition().y); + + // Transform the distance using the transpose of the matrix, + // in order to translate the image to match the screen coordinates + wxMatrix2D m; + m_affineMatrix.Get(&m, NULL); + + wxPoint2DDouble deltaD(m.m_11 * delta.x + m.m_12 * delta.y, + m.m_21 * delta.x + m.m_22 * delta.y); + + // Add it to the total translation + m_translateDistance += deltaD; + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Pan gesture Ended\n"); + } + + Refresh(); +} + +void MyGesturePanel::OnZoom(wxZoomGestureEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Zoom gesture started\n"); + + m_lastZoomFactor = 1.0; + } + + wxLogMessage("Zoom gesture performed with zoom center at (%d, %d) and zoom Factor = %f\n", + event.GetPosition().x, event.GetPosition().y, event.GetZoomFactor()); + + const wxPoint& zoomCenter = event.GetPosition(); + + // Translate to zoom center + m_affineMatrix.Translate(zoomCenter.x, zoomCenter.y); + // Scale + m_affineMatrix.Scale(event.GetZoomFactor() / m_lastZoomFactor, event.GetZoomFactor() / m_lastZoomFactor); + // Translate back + m_affineMatrix.Translate(-zoomCenter.x, -zoomCenter.y); + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Zoom gesture Ended\n"); + } + + m_lastZoomFactor = event.GetZoomFactor(); + + Refresh(); +} + +void MyGesturePanel::OnRotate(wxRotateGestureEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Rotate gesture started\n"); + + m_lastRotationAngle = 0.0; + } + + wxLogMessage("Rotate gesture performed with rotation center at (%d, %d) and cumulative rotation angle = %f\n", + event.GetPosition().x, event.GetPosition().y, event.GetRotationAngle()); + + const wxPoint& rotationCenter = event.GetPosition(); + + // Translate to rotation center + m_affineMatrix.Translate(rotationCenter.x, rotationCenter.y); + // Rotate + m_affineMatrix.Rotate(event.GetRotationAngle() - m_lastRotationAngle); + // Translate back + m_affineMatrix.Translate(-rotationCenter.x, -rotationCenter.y); + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Rotate gesture Ended\n"); + } + + m_lastRotationAngle = event.GetRotationAngle(); + + Refresh(); +} + +void MyGesturePanel::OnTwoFingerTap(wxTwoFingerTapEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Two Finger Tap gesture gesture started\n"); + } + + wxLogMessage("Two Finger Tap gesture performed at (%d, %d)\n", event.GetPosition().x, event.GetPosition().y); + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Two Finger Tap gesture Ended\n"); + } +} + +void MyGesturePanel::OnLongPress(wxLongPressEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Long Press gesture started\n"); + } + + wxLogMessage("Long Press gesture performed at (%d,%d)\n", event.GetPosition().x, event.GetPosition().y); + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Long Press gesture Ended\n"); + } +} + +void MyGesturePanel::OnPressAndTap(wxPressAndTapEvent& event) +{ + if ( event.IsGestureStart() ) + { + wxLogMessage("Press and Tap gesture started\n"); + } + + wxLogMessage("Press and Tap gesture performed at (%d,%d)\n", event.GetPosition().x, event.GetPosition().y); + + if ( event.IsGestureEnd() ) + { + wxLogMessage("Press and Tap gesture Ended\n"); + } +} diff --git a/samples/event/gestures.h b/samples/event/gestures.h new file mode 100644 index 0000000000..c2add7f344 --- /dev/null +++ b/samples/event/gestures.h @@ -0,0 +1,39 @@ +#ifndef _WX_GESTURES_H_ +#define _WX_GESTURES_H_ + +#include "wx/wx.h" + +class MyGestureFrame : public wxFrame +{ +public: + MyGestureFrame(); + + void OnGesture(wxGestureEvent& event); + void OnQuit(wxCloseEvent& event); + +private: + wxTextCtrl *m_logText; +}; + +class MyGesturePanel : public wxPanel +{ +public: + MyGesturePanel(MyGestureFrame* parent); + + void OnPaint(wxPaintEvent& event); + void OnPan(wxPanGestureEvent& event); + void OnZoom(wxZoomGestureEvent& event); + void OnRotate(wxRotateGestureEvent& event); + void OnTwoFingerTap(wxTwoFingerTapEvent& event); + void OnLongPress(wxLongPressEvent& event); + void OnPressAndTap(wxPressAndTapEvent& event); + +private: + wxBitmap m_bitmap; + wxPoint2DDouble m_translateDistance; + wxAffineMatrix2D m_affineMatrix; + double m_lastZoomFactor; + double m_lastRotationAngle; +}; + +#endif // _WX_GESTURES_H_ diff --git a/samples/event/makefile.bcc b/samples/event/makefile.bcc index c079d2d558..62364b9296 100644 --- a/samples/event/makefile.bcc +++ b/samples/event/makefile.bcc @@ -36,7 +36,8 @@ EVENT_CXXFLAGS = $(__RUNTIME_LIBS_7) -I$(BCCDIR)\include $(__DEBUGINFO) \ -I$(SETUPHDIR) -I.\..\..\include $(____CAIRO_INCLUDEDIR_FILENAMES_p) -I. \ $(__DLLFLAG_p) -I.\..\..\samples -DNOPCH $(CPPFLAGS) $(CXXFLAGS) EVENT_OBJECTS = \ - $(OBJS)\event_event.obj + $(OBJS)\event_event.obj \ + $(OBJS)\event_gestures.obj ### Conditionally set variables: ### @@ -237,3 +238,5 @@ $(OBJS)\event_sample.res: .\..\..\samples\sample.rc $(OBJS)\event_event.obj: .\event.cpp $(CXX) -q -c -P -o$@ $(EVENT_CXXFLAGS) .\event.cpp +$(OBJS)\event_gestures.obj: .\gestures.cpp + $(CXX) -q -c -P -o$@ $(EVENT_CXXFLAGS) .\gestures.cpp diff --git a/samples/event/makefile.gcc b/samples/event/makefile.gcc index 2e3508cef9..105c791901 100644 --- a/samples/event/makefile.gcc +++ b/samples/event/makefile.gcc @@ -30,7 +30,8 @@ EVENT_CXXFLAGS = $(__DEBUGINFO) $(__OPTIMIZEFLAG_2) $(__THREADSFLAG) \ $(__EXCEPTIONSFLAG_6) -Wno-ctor-dtor-privacy $(CPPFLAGS) $(CXXFLAGS) EVENT_OBJECTS = \ $(OBJS)\event_sample_rc.o \ - $(OBJS)\event_event.o + $(OBJS)\event_event.o \ + $(OBJS)\event_gestures.o ### Conditionally set variables: ### @@ -226,6 +227,9 @@ $(OBJS)\event_sample_rc.o: ./../../samples/sample.rc $(OBJS)\event_event.o: ./event.cpp $(CXX) -c -o $@ $(EVENT_CXXFLAGS) $(CPPDEPS) $< +$(OBJS)\event_gestures.o: ./gestures.cpp + $(CXX) -c -o $@ $(EVENT_CXXFLAGS) $(CPPDEPS) $< + .PHONY: all clean diff --git a/samples/event/makefile.vc b/samples/event/makefile.vc index cf6ad41a6c..bbf86a28b9 100644 --- a/samples/event/makefile.vc +++ b/samples/event/makefile.vc @@ -31,7 +31,8 @@ EVENT_CXXFLAGS = /M$(__RUNTIME_LIBS_10)$(__DEBUGRUNTIME_4) /DWIN32 \ /I.\..\..\samples /DNOPCH $(__RTTIFLAG_11) $(__EXCEPTIONSFLAG_12) \ $(CPPFLAGS) $(CXXFLAGS) EVENT_OBJECTS = \ - $(OBJS)\event_event.obj + $(OBJS)\event_event.obj \ + $(OBJS)\event_gestures.obj EVENT_RESOURCES = \ $(OBJS)\event_sample.res @@ -360,3 +361,5 @@ $(OBJS)\event_sample.res: .\..\..\samples\sample.rc $(OBJS)\event_event.obj: .\event.cpp $(CXX) /c /nologo /TP /Fo$@ $(EVENT_CXXFLAGS) .\event.cpp +$(OBJS)\event_gestures.obj: .\gestures.cpp + $(CXX) /c /nologo /TP /Fo$@ $(EVENT_CXXFLAGS) .\gestures.cpp diff --git a/src/common/event.cpp b/src/common/event.cpp index ee9f69aec1..d32a21d9d6 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -103,6 +103,13 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxMouseCaptureChangedEvent, wxEvent); wxIMPLEMENT_DYNAMIC_CLASS(wxMouseCaptureLostEvent, wxEvent); wxIMPLEMENT_DYNAMIC_CLASS(wxClipboardTextEvent, wxCommandEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxGestureEvent, wxEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxPanGestureEvent, wxGestureEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxZoomGestureEvent, wxGestureEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxRotateGestureEvent, wxGestureEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxTwoFingerTapEvent, wxGestureEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxLongPressEvent, wxGestureEvent); + wxIMPLEMENT_DYNAMIC_CLASS(wxPressAndTapEvent, wxGestureEvent); #endif // wxUSE_GUI #if wxUSE_BASE @@ -258,6 +265,14 @@ wxDEFINE_EVENT( wxEVT_SCROLLWIN_PAGEDOWN, wxScrollWinEvent ); wxDEFINE_EVENT( wxEVT_SCROLLWIN_THUMBTRACK, wxScrollWinEvent ); wxDEFINE_EVENT( wxEVT_SCROLLWIN_THUMBRELEASE, wxScrollWinEvent ); +// Gesture events +wxDEFINE_EVENT( wxEVT_GESTURE_PAN, wxPanGestureEvent ); +wxDEFINE_EVENT( wxEVT_GESTURE_ZOOM, wxZoomGestureEvent ); +wxDEFINE_EVENT( wxEVT_GESTURE_ROTATE, wxRotateGestureEvent ); +wxDEFINE_EVENT( wxEVT_TWO_FINGER_TAP, wxTwoFingerTapEvent ); +wxDEFINE_EVENT( wxEVT_LONG_PRESS, wxLongPressEvent ); +wxDEFINE_EVENT( wxEVT_PRESS_AND_TAP, wxPressAndTapEvent ); + // System events wxDEFINE_EVENT( wxEVT_SIZE, wxSizeEvent ); wxDEFINE_EVENT( wxEVT_SIZING, wxSizeEvent ); diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 5b1b0605ac..2943ae2272 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -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 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; diff --git a/src/msw/window.cpp b/src/msw/window.cpp index dedfc8a303..c6abb2fce2 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -51,6 +51,7 @@ #include "wx/textctrl.h" #include "wx/menuitem.h" #include "wx/module.h" + #include "wx/vector.h" #endif #if wxUSE_OWNER_DRAWN && !defined(__WXUNIVERSAL__) @@ -205,6 +206,82 @@ bool gs_insideCaptureChanged = false; } // anonymous namespace +#ifdef WM_GESTURE + +namespace +{ + +// Class used to dynamically load gestures related API functions. +class GestureFuncs +{ +public: + // Must be called before using any other methods of this class (and they + // can't be used if this one returns false). + static bool IsOk() + { + if ( !ms_gestureSymbolsLoaded ) + { + ms_gestureSymbolsLoaded = true; + LoadGestureSymbols(); + } + + return ms_pfnGetGestureInfo && + ms_pfnCloseGestureInfoHandle && + ms_pfnSetGestureConfig; + } + + typedef BOOL (WINAPI *GetGestureInfo_t)(HGESTUREINFO, PGESTUREINFO); + + static GetGestureInfo_t GetGestureInfo() + { + return ms_pfnGetGestureInfo; + } + + typedef BOOL (WINAPI *CloseGestureInfoHandle_t)(HGESTUREINFO); + + static CloseGestureInfoHandle_t CloseGestureInfoHandle() + { + return ms_pfnCloseGestureInfoHandle; + } + + typedef BOOL + (WINAPI *SetGestureConfig_t)(HWND, DWORD, UINT, PGESTURECONFIG, UINT); + + static SetGestureConfig_t SetGestureConfig() + { + return ms_pfnSetGestureConfig; + } + +private: + static void LoadGestureSymbols() + { + wxLoadedDLL dll(wxS("user32.dll")); + + wxDL_INIT_FUNC(ms_pfn, GetGestureInfo, dll); + wxDL_INIT_FUNC(ms_pfn, CloseGestureInfoHandle, dll); + wxDL_INIT_FUNC(ms_pfn, SetGestureConfig, dll); + } + + static GetGestureInfo_t ms_pfnGetGestureInfo; + static CloseGestureInfoHandle_t ms_pfnCloseGestureInfoHandle; + static SetGestureConfig_t ms_pfnSetGestureConfig; + + static bool ms_gestureSymbolsLoaded; +}; + +GestureFuncs::GetGestureInfo_t + GestureFuncs::ms_pfnGetGestureInfo = NULL; +GestureFuncs::CloseGestureInfoHandle_t + GestureFuncs::ms_pfnCloseGestureInfoHandle = NULL; +GestureFuncs::SetGestureConfig_t + GestureFuncs::ms_pfnSetGestureConfig = NULL; + +bool GestureFuncs::ms_gestureSymbolsLoaded = false; + +} // anonymous namespace + +#endif // WM_GESTURE + // --------------------------------------------------------------------------- // private functions // --------------------------------------------------------------------------- @@ -830,6 +907,123 @@ void wxWindowMSW::WarpPointer(int x, int y) } } +bool wxWindowMSW::EnableTouchEvents(int eventsMask) +{ +#ifdef WM_GESTURE + if ( GestureFuncs::IsOk() ) + { + // Static struct used when we need to use just a single configuration. + GESTURECONFIG config = {0, 0, 0}; + + GESTURECONFIG* ptrConfigs = &config; + UINT numConfigs = 1; + + // This is used only if we need to allocate the configurations + // dynamically. + wxVector configs; + + // There are two simple cases: enabling or disabling all gestures. + if ( eventsMask == wxTOUCH_NONE ) + { + config.dwBlock = GC_ALLGESTURES; + } + else if ( eventsMask == wxTOUCH_ALL_GESTURES ) + { + config.dwWant = GC_ALLGESTURES; + } + else // Need to enable the individual gestures + { + int wantedPan = 0; + switch ( eventsMask & wxTOUCH_PAN_GESTURES ) + { + case wxTOUCH_VERTICAL_PAN_GESTURE: + wantedPan = GC_PAN_WITH_SINGLE_FINGER_VERTICALLY; + break; + + case wxTOUCH_HORIZONTAL_PAN_GESTURE: + wantedPan = GC_PAN_WITH_SINGLE_FINGER_HORIZONTALLY; + break; + + case wxTOUCH_PAN_GESTURES: + wantedPan = GC_PAN; + break; + + case 0: + // This is the only other possibility and wantedPan is + // already initialized to 0 anyhow, so don't do anything, + // just list it for completeness. + break; + } + + if ( wantedPan ) + { + eventsMask &= ~wxTOUCH_PAN_GESTURES; + + config.dwID = GID_PAN; + config.dwWant = wantedPan; + configs.push_back(config); + } + + if ( eventsMask & wxTOUCH_ZOOM_GESTURE ) + { + eventsMask &= ~wxTOUCH_ZOOM_GESTURE; + + config.dwID = GID_ZOOM; + config.dwWant = GC_ZOOM; + configs.push_back(config); + } + + if ( eventsMask & wxTOUCH_ROTATE_GESTURE ) + { + eventsMask &= ~wxTOUCH_ROTATE_GESTURE; + + config.dwID = GID_ROTATE; + config.dwWant = GC_ROTATE; + configs.push_back(config); + } + + if ( eventsMask & wxTOUCH_PRESS_GESTURES ) + { + eventsMask &= ~wxTOUCH_PRESS_GESTURES; + + config.dwID = GID_TWOFINGERTAP; + config.dwWant = GC_TWOFINGERTAP; + configs.push_back(config); + + config.dwID = GID_PRESSANDTAP; + config.dwWant = GC_PRESSANDTAP; + configs.push_back(config); + } + + // As we clear all the known bits if they're set in the code above, + // there should be nothing left. + wxCHECK_MSG( eventsMask == 0, false, + wxS("Unknown touch event mask bit specified") ); + + ptrConfigs = &configs[0]; + } + + if ( !GestureFuncs::SetGestureConfig() + ( + m_hWnd, + 0, // Reserved, must be always 0. + numConfigs, // Number of gesture configurations. + ptrConfigs, // Pointer to the first one. + sizeof(GESTURECONFIG) // Size of each configuration. + ) + ) + { + wxLogLastError("SetGestureConfig"); + return false; + } + + return true; + } +#endif // WM_GESTURE + + return wxWindowBase::EnableTouchEvents(eventsMask); +} + void wxWindowMSW::MSWUpdateUIState(int action, int state) { // we send WM_CHANGEUISTATE so if nothing needs changing then the system @@ -3124,6 +3318,84 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, } break; +#ifdef WM_GESTURE + case WM_GESTURE: + { + if ( !GestureFuncs::IsOk() ) + break; + + HGESTUREINFO hGestureInfo = reinterpret_cast(lParam); + + WinStruct gestureInfo; + if ( !GestureFuncs::GetGestureInfo()(hGestureInfo, &gestureInfo) ) + { + wxLogLastError("GetGestureInfo"); + break; + } + + if ( gestureInfo.hwndTarget != GetHWND() ) + { + wxLogDebug("This is Not the window targeted by this gesture!"); + } + + const wxPoint pt = ScreenToClient + ( + wxPoint(gestureInfo.ptsLocation.x, + gestureInfo.ptsLocation.y) + ); + + // dwID field is used to determine the type of gesture + switch ( gestureInfo.dwID ) + { + case GID_PAN: + // Point contains the current position of the pan. + processed = HandlePanGesture(pt, gestureInfo.dwFlags); + break; + + case GID_ZOOM: + // Point is the mid-point of 2 fingers and ullArgument + // contains the distance between the fingers in its lower + // half + processed = HandleZoomGesture + ( + pt, + static_cast(gestureInfo.ullArguments), + gestureInfo.dwFlags + ); + break; + + case GID_ROTATE: + // Point is the center point of rotation and ullArguments + // contains the angle of rotation + processed = HandleRotateGesture + ( + pt, + static_cast(gestureInfo.ullArguments), + gestureInfo.dwFlags + ); + break; + + case GID_TWOFINGERTAP: + processed = HandleTwoFingerTap(pt, gestureInfo.dwFlags); + break; + + case GID_PRESSANDTAP: + processed = HandlePressAndTap(pt, gestureInfo.dwFlags); + break; + } + + if ( processed ) + { + // If processed, we must call this to avoid memory leaks + if ( !GestureFuncs::CloseGestureInfoHandle()(hGestureInfo) ) + { + wxLogLastError("CloseGestureInfoHandle"); + } + } + } + break; +#endif // WM_GESTURE + // CTLCOLOR messages are sent by children to query the parent for their // colors case WM_CTLCOLORMSGBOX: @@ -5474,6 +5746,144 @@ void wxWindowMSW::GenerateMouseLeave() (void)HandleWindowEvent(event); } +#ifdef WM_GESTURE +// --------------------------------------------------------------------------- +// Gesture events +// --------------------------------------------------------------------------- + +bool wxWindowMSW::InitGestureEvent(wxGestureEvent& event, + const wxPoint& pt, + WXDWORD flags) +{ + event.SetEventObject(this); + event.SetTimestamp(::GetMessageTime()); + event.SetPosition(pt); + + if ( flags & GF_BEGIN ) + event.SetGestureStart(); + + if ( flags & GF_END ) + event.SetGestureEnd(); + + return (flags & GF_BEGIN) != 0; +} + +bool wxWindowMSW::HandlePanGesture(const wxPoint& pt, WXDWORD flags) +{ + // wxEVT_GESTURE_PAN + wxPanGestureEvent event(GetId()); + + // This is used to calculate the pan delta. + static wxPoint s_previousLocation; + + // If the gesture has just started, store the current point to determine + // the pan delta later on. + if ( InitGestureEvent(event, pt, flags) ) + { + s_previousLocation = pt; + } + + // Determine the horizontal and vertical changes + event.SetDelta(pt - s_previousLocation); + + // Update the last gesture event point + s_previousLocation = pt; + + return HandleWindowEvent(event); +} + +bool wxWindowMSW::HandleZoomGesture(const wxPoint& pt, + WXDWORD fingerDistance, + WXDWORD flags) +{ + // wxEVT_GESTURE_ZOOM + wxZoomGestureEvent event(GetId()); + + // These are used to calculate the center of the zoom and zoom factor + static wxPoint s_previousLocation; + static int s_intialFingerDistance; + + // This flag indicates that the gesture has just started, store the current + // point and distance between the fingers for future calculations. + if ( InitGestureEvent(event, pt, flags) ) + { + s_previousLocation = pt; + s_intialFingerDistance = fingerDistance; + } + + // Calculate center point of the zoom. Human beings are not very good at + // moving two fingers at exactly the same rate outwards/inwards and there + // is usually some error, which can cause the center to shift slightly. So, + // it is recommended to take the average of center of fingers in the + // current and last positions. + const wxPoint ptCenter = (s_previousLocation + pt)/2; + + const double zoomFactor = (double) fingerDistance / s_intialFingerDistance; + + event.SetZoomFactor(zoomFactor); + + event.SetPosition(ptCenter); + + // Update gesture event point + s_previousLocation = pt; + + return HandleWindowEvent(event); +} + +bool wxWindowMSW::HandleRotateGesture(const wxPoint& pt, + WXDWORD angleArgument, + WXDWORD flags) +{ + // wxEVT_GESTURE_ROTATE + wxRotateGestureEvent event(GetId()); + + if ( InitGestureEvent(event, pt, flags) ) + { + event.SetRotationAngle(angleArgument); + } + else // Not the first event. + { + // Use angleArgument to obtain the cumulative angle since the gesture + // was first started. This angle is in radians and MSW returns negative + // angle for clockwise rotation and positive otherwise, so, multiply + // angle by -1 for positive angle for clockwise and negative in case of + // counterclockwise. + double angle = -GID_ROTATE_ANGLE_FROM_ARGUMENT(angleArgument); + + // If the rotation is anti-clockwise convert the angle to its + // corresponding positive value in a clockwise sense. + if ( angle < 0 ) + { + angle += 2 * M_PI; + } + + // Set the angle + event.SetRotationAngle(angle); + } + + return HandleWindowEvent(event); +} + +bool wxWindowMSW::HandleTwoFingerTap(const wxPoint& pt, WXDWORD flags) +{ + // wxEVT_TWO_FINGER_TAP + wxTwoFingerTapEvent event(GetId()); + + InitGestureEvent(event, pt, flags); + + return HandleWindowEvent(event); +} + +bool wxWindowMSW::HandlePressAndTap(const wxPoint& pt, WXDWORD flags) +{ + wxPressAndTapEvent event(GetId()); + + InitGestureEvent(event, pt, flags); + + return HandleWindowEvent(event); +} +#endif // WM_GESTURE + // --------------------------------------------------------------------------- // keyboard handling // --------------------------------------------------------------------------- diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 3a34b6fc65..b218573f9e 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -528,29 +528,35 @@ bool g_lastButtonWasFakeRight = false ; - (CGFloat)scrollingDeltaY; @end -void wxWidgetCocoaImpl::SetupCoordinates(wxCoord &x, wxCoord &y, NSEvent* nsEvent) +static void +wxSetupCoordinates(NSView* view, wxCoord &x, wxCoord &y, NSEvent* nsEvent) { NSRect locationInWindow = NSZeroRect; locationInWindow.origin = [nsEvent locationInWindow]; // adjust coordinates for the window of the target view - if ( [nsEvent window] != [m_osxView window] ) + if ( [nsEvent window] != [view window] ) { if ( [nsEvent window] != nil ) locationInWindow = [[nsEvent window] convertRectToScreen:locationInWindow]; - if ( [m_osxView window] != nil ) - locationInWindow = [[m_osxView window] convertRectFromScreen:locationInWindow]; + if ( [view window] != nil ) + locationInWindow = [[view window] convertRectFromScreen:locationInWindow]; } - NSPoint locationInView = [m_osxView convertPoint:locationInWindow.origin fromView:nil]; - wxPoint locationInViewWX = wxFromNSPoint( m_osxView, locationInView ); + NSPoint locationInView = [view convertPoint:locationInWindow.origin fromView:nil]; + wxPoint locationInViewWX = wxFromNSPoint( view, locationInView ); x = locationInViewWX.x; y = locationInViewWX.y; } +void wxWidgetCocoaImpl::SetupCoordinates(wxCoord &x, wxCoord &y, NSEvent* nsEvent) +{ + wxSetupCoordinates(m_osxView, x, y, nsEvent); +} + void wxWidgetCocoaImpl::SetupMouseEvent( wxMouseEvent &wxevent , NSEvent * nsEvent ) { int eventType = [nsEvent type]; @@ -1042,6 +1048,71 @@ void wxOSX_insertText(NSView* self, SEL _cmd, NSString* text) impl->insertText(text, self, _cmd); } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 +void wxOSX_panGestureEvent(NSView* self, SEL _cmd, NSPanGestureRecognizer* panGestureRecognizer) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->PanGestureEvent(panGestureRecognizer); +} + +void wxOSX_zoomGestureEvent(NSView* self, SEL _cmd, NSMagnificationGestureRecognizer* magnificationGestureRecognizer) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->ZoomGestureEvent(magnificationGestureRecognizer); +} + +void wxOSX_rotateGestureEvent(NSView* self, SEL _cmd, NSRotationGestureRecognizer* rotationGestureRecognizer) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->RotateGestureEvent(rotationGestureRecognizer); +} + +void wxOSX_longPressEvent(NSView* self, SEL _cmd, NSPressGestureRecognizer* pressGestureRecognizer) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl ==NULL ) + return; + + impl->LongPressEvent(pressGestureRecognizer); +} + +void wxOSX_touchesBegan(NSView* self, SEL _cmd, NSEvent *event) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->TouchesBegan(event); +} + +void wxOSX_touchesMoved(NSView* self, SEL _cmd, NSEvent *event) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->TouchesMoved(event); +} + +void wxOSX_touchesEnded(NSView* self, SEL _cmd, NSEvent *event) +{ + wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); + if ( impl == NULL ) + return; + + impl->TouchesEnded(event); +} +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + BOOL wxOSX_performKeyEquivalent(NSView* self, SEL _cmd, NSEvent *event) { wxWidgetCocoaImpl* impl = (wxWidgetCocoaImpl* ) wxWidgetImpl::FindFromWXWidget( self ); @@ -1428,6 +1499,559 @@ void wxWidgetCocoaImpl::keyEvent(WX_NSEvent event, WXWidget slf, void *_cmd) m_lastKeyDownEvent = NULL; } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + +// Class containing data used for gestures support. +class wxCocoaGesturesImpl +{ +public: + wxCocoaGesturesImpl(wxWidgetCocoaImpl* impl, NSView* view, int eventsMask) + : m_win(impl->GetWXPeer()), + m_view(view) + { + m_touchCount = 0; + m_lastTouchTime = 0; + m_allowedGestures = 0; + m_activeGestures = 0; + m_initialTouch = NULL; + + Class cls = [m_view class]; + + if ( eventsMask & wxTOUCH_PAN_GESTURES ) + { + eventsMask &= ~wxTOUCH_PAN_GESTURES; + + m_panGestureRecognizer = + [[NSPanGestureRecognizer alloc] initWithTarget:m_view action: @selector(handlePanGesture:)]; + if ( !class_respondsToSelector(cls, @selector(handlePanGesture:)) ) + class_addMethod(cls, @selector(handlePanGesture:), (IMP) wxOSX_panGestureEvent, "v@:@" ); + [m_view addGestureRecognizer:m_panGestureRecognizer]; + } + else + { + m_panGestureRecognizer = nil; + } + + if ( eventsMask & wxTOUCH_ZOOM_GESTURE ) + { + eventsMask &= ~wxTOUCH_ZOOM_GESTURE; + + m_magnificationGestureRecognizer = + [[NSMagnificationGestureRecognizer alloc] initWithTarget:m_view action: @selector(handleZoomGesture:)]; + if ( !class_respondsToSelector(cls, @selector(handleZoomGesture:)) ) + class_addMethod(cls, @selector(handleZoomGesture:), (IMP) wxOSX_zoomGestureEvent, "v@:@" ); + [m_view addGestureRecognizer:m_magnificationGestureRecognizer]; + } + else + { + m_magnificationGestureRecognizer = nil; + } + + if ( eventsMask & wxTOUCH_ROTATE_GESTURE ) + { + eventsMask &= ~wxTOUCH_ROTATE_GESTURE; + + m_rotationGestureRecognizer = + [[NSRotationGestureRecognizer alloc] initWithTarget:m_view action: @selector(handleRotateGesture:)]; + if ( !class_respondsToSelector(cls, @selector(handleRotateGesture:)) ) + class_addMethod(cls, @selector(handleRotateGesture:), (IMP) wxOSX_rotateGestureEvent, "v@:@" ); + [m_view addGestureRecognizer:m_rotationGestureRecognizer]; + } + else + { + m_rotationGestureRecognizer = nil; + } + + if ( eventsMask & wxTOUCH_PRESS_GESTURES ) + { + eventsMask &= ~wxTOUCH_PRESS_GESTURES; + + m_pressGestureRecognizer = + [[NSPressGestureRecognizer alloc] initWithTarget:m_view action: @selector(handleLongPressGesture:)]; + if ( !class_respondsToSelector(cls, @selector(handleLongPressGesture:)) ) + class_addMethod(cls, @selector(handleLongPressGesture:), (IMP) wxOSX_longPressEvent, "v@:@" ); + [m_view addGestureRecognizer:m_pressGestureRecognizer]; + } + else + { + m_pressGestureRecognizer = nil; + } + + wxASSERT_MSG( eventsMask == 0, "Unknown touch event mask bit specified" ); + + if ( !class_respondsToSelector(cls, @selector(touchesBeganWithEvent:)) ) + class_addMethod(cls, @selector(touchesBeganWithEvent:), (IMP) wxOSX_touchesBegan, "v@:@" ); + if ( !class_respondsToSelector(cls, @selector(touchesMovedWithEvent:)) ) + class_addMethod(cls, @selector(touchesMovedWithEvent:), (IMP) wxOSX_touchesMoved, "v@:@" ); + if ( !class_respondsToSelector(cls, @selector(touchesEndedWithEvent:)) ) + class_addMethod(cls, @selector(touchesEndedWithEvent:), (IMP) wxOSX_touchesEnded, "v@:@" ); + } + + ~wxCocoaGesturesImpl() + { + [m_panGestureRecognizer release]; + [m_magnificationGestureRecognizer release]; + [m_rotationGestureRecognizer release]; + [m_pressGestureRecognizer release]; + [m_initialTouch release]; + } + + void TouchesBegan(NSEvent* event); + void TouchesMoved(NSEvent* event); + void TouchesEnded(NSEvent* event); + +private: + wxWindowMac* const m_win; + NSView* const m_view; + + NSPanGestureRecognizer *m_panGestureRecognizer; + NSMagnificationGestureRecognizer *m_magnificationGestureRecognizer; + NSRotationGestureRecognizer *m_rotationGestureRecognizer; + NSPressGestureRecognizer *m_pressGestureRecognizer; + + int m_allowedGestures; + int m_activeGestures; + unsigned int m_touchCount; + unsigned int m_lastTouchTime; + + // Used to keep track of the touch corresponding to "press" in Press and Tap gesture + NSTouch* m_initialTouch; + + wxDECLARE_NO_COPY_CLASS(wxCocoaGesturesImpl); +}; + +// We keep all existing wxCocoaGesturesImpl objects in a +// wxWidgetCocoaImpl-indexed map. We do this instead of just having a data +// member containing wxCocoaGesturesImpl pointer in wxWidgetCocoaImpl +// itself because most windows don't need it and it seems wasteful to +// always increase their size unnecessarily. + +#include "wx/hashmap.h" +WX_DECLARE_HASH_MAP(wxWidgetCocoaImpl*, wxCocoaGesturesImpl*, + wxPointerHash, wxPointerEqual, + wxCocoaGesturesImplMap); + +#include "wx/private/extfield.h" +typedef wxExternalField wxCocoaGestures; + +void wxWidgetCocoaImpl::PanGestureEvent(NSPanGestureRecognizer* panGestureRecognizer) +{ + NSGestureRecognizerState gestureState; + + switch ( [panGestureRecognizer state] ) + { + case NSGestureRecognizerStateBegan: + gestureState = NSGestureRecognizerStateBegan; + break; + case NSGestureRecognizerStateChanged: + break; + case NSGestureRecognizerStateEnded: + case NSGestureRecognizerStateCancelled: + gestureState = NSGestureRecognizerStateEnded; + break; + // Do not process any other states + default: + return; + } + + wxPanGestureEvent wxevent(GetWXPeer()->GetId()); + wxevent.SetEventObject(GetWXPeer()); + + NSPoint nspoint = [panGestureRecognizer locationInView:m_osxView]; + wxPoint pt = wxFromNSPoint(m_osxView, nspoint); + + wxevent.SetPosition(pt); + + nspoint = [panGestureRecognizer translationInView:m_osxView]; + pt = wxFromNSPoint(m_osxView, nspoint); + + static wxPoint s_lastLocation; + + if ( gestureState == NSGestureRecognizerStateBegan ) + { + wxevent.SetGestureStart(); + s_lastLocation = wxPoint(0, 0); + } + + if ( gestureState == NSGestureRecognizerStateEnded ) + { + wxevent.SetGestureEnd(); + } + + // Set the offset + wxevent.SetDelta(pt - s_lastLocation); + + s_lastLocation = pt; + + GetWXPeer()->HandleWindowEvent(wxevent); +} + +void wxWidgetCocoaImpl::ZoomGestureEvent(NSMagnificationGestureRecognizer* magnificationGestureRecognizer) +{ + NSGestureRecognizerState gestureState; + + switch ( [magnificationGestureRecognizer state] ) + { + case NSGestureRecognizerStateBegan: + gestureState = NSGestureRecognizerStateBegan; + break; + case NSGestureRecognizerStateChanged: + break; + case NSGestureRecognizerStateEnded: + case NSGestureRecognizerStateCancelled: + gestureState = NSGestureRecognizerStateEnded; + break; + // Do not process any other states + default: + return; + } + + wxZoomGestureEvent wxevent(GetWXPeer()->GetId()); + wxevent.SetEventObject(GetWXPeer()); + + NSPoint nspoint = [magnificationGestureRecognizer locationInView:m_osxView]; + wxPoint pt = wxFromNSPoint(m_osxView, nspoint); + + wxevent.SetPosition(pt); + + if ( gestureState == NSGestureRecognizerStateBegan ) + { + wxevent.SetGestureStart(); + } + + if ( gestureState == NSGestureRecognizerStateEnded ) + { + wxevent.SetGestureEnd(); + } + + double magnification = [magnificationGestureRecognizer magnification]; + + // Add 1.0 get the magnification. + magnification += 1.0; + + wxevent.SetZoomFactor(magnification); + + GetWXPeer()->HandleWindowEvent(wxevent); +} + +void wxWidgetCocoaImpl::RotateGestureEvent(NSRotationGestureRecognizer* rotationGestureRecognizer) +{ + NSGestureRecognizerState gestureState; + + switch ( [rotationGestureRecognizer state] ) + { + case NSGestureRecognizerStateBegan: + gestureState = NSGestureRecognizerStateBegan; + break; + case NSGestureRecognizerStateChanged: + break; + case NSGestureRecognizerStateEnded: + case NSGestureRecognizerStateCancelled: + gestureState = NSGestureRecognizerStateEnded; + break; + // Do not process any other states + default: + return; + } + + wxRotateGestureEvent wxevent(GetWXPeer()->GetId()); + wxevent.SetEventObject(GetWXPeer()); + + NSPoint nspoint = [rotationGestureRecognizer locationInView:m_osxView]; + wxPoint pt = wxFromNSPoint(m_osxView, nspoint); + + wxevent.SetPosition(pt); + + if ( gestureState == NSGestureRecognizerStateBegan ) + { + wxevent.SetGestureStart(); + } + + if ( gestureState == NSGestureRecognizerStateEnded ) + { + wxevent.SetGestureEnd(); + } + // Multiply the returned rotation angle with -1 to obtain the angle in a clockwise sense. + double angle = -[rotationGestureRecognizer rotation]; + + // If the rotation is anti-clockwise convert the angle to its corresponding positive value in a clockwise sense. + if ( angle < 0 ) + { + angle += 2 * M_PI; + } + + wxevent.SetRotationAngle(angle); + + GetWXPeer()->HandleWindowEvent(wxevent); +} + +void wxWidgetCocoaImpl::LongPressEvent(NSPressGestureRecognizer* pressGestureRecognizer) +{ + NSGestureRecognizerState gestureState; + + switch ( [pressGestureRecognizer state] ) + { + case NSGestureRecognizerStateBegan: + gestureState = NSGestureRecognizerStateBegan; + break; + case NSGestureRecognizerStateChanged: + break; + case NSGestureRecognizerStateEnded: + case NSGestureRecognizerStateCancelled: + gestureState = NSGestureRecognizerStateEnded; + break; + // Do not process any other states + default: + return; + } + + wxLongPressEvent wxevent(GetWXPeer()->GetId()); + wxevent.SetEventObject(GetWXPeer()); + + NSPoint nspoint = [pressGestureRecognizer locationInView:m_osxView]; + wxPoint pt = wxFromNSPoint(m_osxView, nspoint); + + wxevent.SetPosition(pt); + + if ( gestureState == NSGestureRecognizerStateBegan ) + { + wxevent.SetGestureStart(); + } + + if ( gestureState == NSGestureRecognizerStateEnded ) + { + wxevent.SetGestureEnd(); + } + + GetWXPeer()->HandleWindowEvent(wxevent); +} + +enum TrackedGestures +{ + two_finger_tap = 0x0001, + press_and_tap = 0x0002 +}; + +void wxWidgetCocoaImpl::TouchesBegan(WX_NSEvent event) +{ + if ( wxCocoaGesturesImpl* gestures = wxCocoaGestures::FromObject(this) ) + gestures->TouchesBegan(event); +} + +void wxCocoaGesturesImpl::TouchesBegan(NSEvent* event) +{ + NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:m_view]; + + m_touchCount += touches.count; + m_allowedGestures &= ~two_finger_tap; + + // Check if 2 fingers are placed together + if ( m_touchCount == 2 && touches.count == 2 ) + { + // Two Finger Tap Event may occur in future + m_allowedGestures |= two_finger_tap; + + // Cancel Press and Tap Event + m_allowedGestures &= ~press_and_tap; + + return; + } + + // Time of event in milliseconds + const unsigned int eventTimeStamp = event.timestamp * 1000 + 0.5; + + if ( m_touchCount == 1 ) + { + // Save the time of event + m_lastTouchTime = eventTimeStamp; + + // Press and Tap may occur in future + m_allowedGestures |= press_and_tap; + + NSArray* array = [touches allObjects]; + + // Save the touch corresponding to "press" + m_initialTouch = [[array objectAtIndex:0] copy]; + } + + touches = [event touchesMatchingPhase:NSTouchPhaseStationary inView:m_view]; + + // Check if 2 fingers are placed within the time interval of 200 milliseconds + if ( m_touchCount == 2 && touches.count == 1 && eventTimeStamp - m_lastTouchTime <= wxTwoFingerTimeInterval ) + { + // Two Finger Tap Event may occur in future + m_allowedGestures |= two_finger_tap; + + // Cancel Press and Tap + m_allowedGestures &= ~press_and_tap; + + [m_initialTouch release]; + } +} + +void wxWidgetCocoaImpl::TouchesMoved(WX_NSEvent event) +{ + if ( wxCocoaGesturesImpl* gestures = wxCocoaGestures::FromObject(this) ) + gestures->TouchesMoved(event); +} + +void wxCocoaGesturesImpl::TouchesMoved(NSEvent* event) +{ + // Cancel Two Finger Tap Event if there is any movement + m_allowedGestures &= ~two_finger_tap; + + if ( !(m_allowedGestures & press_and_tap) ) + { + return; + } + + NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseMoved inView:m_view]; + + NSArray* array = [touches allObjects]; + + // Iterate through all moving touches to check if the touch corresponding to "press" + // in Press and Tap event is moving. + for ( int i = 0; i < [array count]; ++i ) + { + NSTouch* touch = [array objectAtIndex:i]; + + // Check if this touch and m_initialTouch are same + if ( [touch.identity isEqual:m_initialTouch.identity] ) + { + // Process Press and Tap Event if the touch corresponding to "press" is moving + // and the gesture is active. + if ( m_activeGestures & press_and_tap ) + { + wxPressAndTapEvent wxevent(m_win->GetId()); + wxevent.SetEventObject(m_win); + + // Get the mouse coordinates + wxCoord x, y; + wxSetupCoordinates(m_view, x, y, event); + wxevent.SetPosition(wxPoint (x,y)); + + m_win->HandleWindowEvent(wxevent); + } + + // Cancel Press and Tap Event if the touch corresponding to "press" is moving + // and the gesture is not active. + else + { + m_allowedGestures &= ~press_and_tap; + } + + return; + } + } +} + +void wxWidgetCocoaImpl::TouchesEnded(WX_NSEvent event) +{ + if ( wxCocoaGesturesImpl* gestures = wxCocoaGestures::FromObject(this) ) + gestures->TouchesEnded(event); +} + +void wxCocoaGesturesImpl::TouchesEnded(NSEvent* event) +{ + NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseEnded inView:m_view]; + + m_touchCount -= touches.count; + + // Time of event in milliseconds + const unsigned int eventTimeStamp = event.timestamp * 1000 + 0.5; + + // Check if 2 fingers are lifted off together or if 2 fingers are lifted off within the time interval of 200 milliseconds + if ( (!m_touchCount && (m_allowedGestures & two_finger_tap)) && + (touches.count == 2 || + (touches.count == 1 && eventTimeStamp - m_lastTouchTime <= wxTwoFingerTimeInterval)) ) + { + wxTwoFingerTapEvent wxevent(m_win->GetId()); + wxevent.SetEventObject(m_win); + wxevent.SetGestureStart(); + wxevent.SetGestureEnd(); + + // Get the mouse coordinates + wxCoord x, y; + wxSetupCoordinates(m_view, x, y, event); + wxevent.SetPosition(wxPoint (x,y)); + + m_win->HandleWindowEvent(wxevent); + } + + // If Two Finger Tap Event is possible in future then save the timestamp to use it when the other touch + // leaves the surface. + else if ( m_touchCount == 1 && (m_allowedGestures & two_finger_tap) ) + { + m_lastTouchTime = eventTimeStamp; + } + + // Check if Press and Tap event is possible. + else if ( m_allowedGestures & press_and_tap ) + { + NSArray* array = [touches allObjects]; + + // True if touch that ended is the touch corresponding to "press" + bool isPressTouch = false; + + // Iterate through all ended touches + for( int i = 0; i < [array count]; ++i ) + { + NSTouch* touch = [array objectAtIndex:i]; + + // Check if touch that ended is the touch corresponding to "press" + if ( [touch.identity isEqual:m_initialTouch.identity] ) + { + isPressTouch = true; + break; + } + } + + // Cancel Press and Tap Event if the touch corresponding to press is ended + // and Press and Tap was not active + if ( isPressTouch && !(m_activeGestures & press_and_tap) ) + { + m_allowedGestures &= ~press_and_tap; + return; + } + + wxPressAndTapEvent wxevent(m_win->GetId()); + wxevent.SetEventObject(m_win); + + // Get the mouse coordinates + wxCoord x, y; + wxSetupCoordinates(m_view, x, y, event); + wxevent.SetPosition(wxPoint (x,y)); + + if ( !(m_activeGestures & press_and_tap) ) + { + wxevent.SetGestureStart(); + m_activeGestures |= press_and_tap; + } + + // End Press and Tap Event if the touch corresponding to "press" is lifted off + else if ( isPressTouch ) + { + wxevent.SetGestureEnd(); + + m_activeGestures &= ~press_and_tap; + m_allowedGestures &= ~press_and_tap; + [m_initialTouch release]; + } + + m_win->HandleWindowEvent(wxevent); + } + + else + { + m_allowedGestures &= ~press_and_tap; + m_allowedGestures &= ~two_finger_tap; + m_activeGestures &= ~press_and_tap; + } +} +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + void wxWidgetCocoaImpl::insertText(NSString* text, WXWidget slf, void *_cmd) { bool result = false; @@ -1775,7 +2399,7 @@ void wxOSXCocoaClassAddWXMethods(Class c) wxOSX_CLASS_ADD_METHOD(c, @selector(scrollWheel:), (IMP) wxOSX_mouseEvent, "v@:@" ) wxOSX_CLASS_ADD_METHOD(c, @selector(mouseEntered:), (IMP) wxOSX_mouseEvent, "v@:@" ) wxOSX_CLASS_ADD_METHOD(c, @selector(mouseExited:), (IMP) wxOSX_mouseEvent, "v@:@" ) - + wxOSX_CLASS_ADD_METHOD(c, @selector(magnifyWithEvent:), (IMP)wxOSX_mouseEvent, "v@:@") wxOSX_CLASS_ADD_METHOD(c, @selector(cursorUpdate:), (IMP) wxOSX_cursorUpdate, "v@:@" ) @@ -1875,6 +2499,10 @@ wxWidgetCocoaImpl::~wxWidgetCocoaImpl() // gc aware handling if ( m_osxView ) CFRelease(m_osxView); + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + wxCocoaGestures::EraseForObject(this); +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 } bool wxWidgetCocoaImpl::IsVisible() const @@ -2791,7 +3419,42 @@ void wxWidgetCocoaImpl::InstallEventHandler( WXWidget control ) NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect: NSZeroRect options: options owner: m_osxView userInfo: nil]; [m_osxView addTrackingArea: area]; [area release]; - } +} + +bool wxWidgetCocoaImpl::EnableTouchEvents(int eventsMask) +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + if ( wxPlatformInfo::Get().CheckOSVersion(10, 10) ) + { + if ( IsUserPane() ) + { + if ( eventsMask == wxTOUCH_NONE ) + { + if ( wxCocoaGestures::EraseForObject(this) ) + { + [m_osxView setAcceptsTouchEvents:NO]; + } + //else: we didn't have any gesture data anyhow + } + else // We do want to have gesture events. + { + wxCocoaGestures::StoreForObject + ( + this, + new wxCocoaGesturesImpl(this, m_osxView, eventsMask) + ); + + [m_osxView setAcceptsTouchEvents:YES]; + } + + return true; + } + } +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + + wxUnusedVar(eventsMask); + return false; +} bool wxWidgetCocoaImpl::DoHandleCharEvent(NSEvent *event, NSString *text) { diff --git a/src/osx/window_osx.cpp b/src/osx/window_osx.cpp index 823c0beae8..c725ef8d57 100644 --- a/src/osx/window_osx.cpp +++ b/src/osx/window_osx.cpp @@ -1429,6 +1429,11 @@ void wxWindowMac::WarpPointer(int x_pos, int y_pos) #endif } +bool wxWindowMac::EnableTouchEvents(int eventsMask) +{ + return GetPeer() ? GetPeer()->EnableTouchEvents(eventsMask) : false; +} + int wxWindowMac::GetScrollPos(int orient) const { #if wxUSE_SCROLLBAR