Browse Source

Gesture recording/load/save support and gesture events. SDL fixes to gesture events. Closes #304.

Lasse Öörni 11 years ago
parent
commit
9906e7ed25

+ 5 - 0
Docs/Reference.dox

@@ -1348,6 +1348,9 @@ The input events include:
 - E_TOUCHBEGIN: a finger touched the screen.
 - E_TOUCHEND: a finger was lifted from the screen.
 - E_TOUCHMOVE: a finger moved on the screen.
+- E_GESTURERECORDED : recording a touch gesture is complete. Triggered with \ref Input::RecordGesture "RecordGesture()"
+- E_GESTUREINPUT : a touch gesture was recognized.
+- E_MULTIGESTURE : a multi-finger swipe/rotation touch gesture is underway.
 - E_JOYSTICKBUTTONDOWN: a joystick button was pressed.
 - E_JOYSTICKBUTTONUP: a joystick button was released.
 - E_JOYSTICKAXISMOVE: a joystick axis was moved.
@@ -1372,6 +1375,8 @@ From the input subsystem you can also query whether the application window has i
 
 On platforms that support it (such as Android) an on-screen virtual keyboard can be shown or hidden. When shown, keypresses from the virtual keyboard will be sent as char events just as if typed from an actual keyboard. Show or hide it by calling \ref Input::SetScreenKeyboardVisible "SetScreenKeyboardVisible()". The UI subsystem can also automatically show the virtual keyboard when a LineEdit element is focused, and hide it when defocused. This behavior can be controlled by calling \ref UI::SetUseScreenKeyboard "SetUseScreenKeyboard()".
 
+On Windows the user must first touch the screen once before touch input is activated. Trying to record or load touch gestures will fail before that.
+
 \page Audio %Audio
 
 The Audio subsystem implements an audio output stream. Once it has been initialized, the following operations are supported:

+ 116 - 0
Source/Engine/IO/RWOpsWrapper.h

@@ -0,0 +1,116 @@
+//
+// Copyright (c) 2008-2014 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "File.h"
+
+#include <SDL_rwops.h>
+
+namespace Urho3D
+{
+
+/// Template wrapper class for using Serializer / Deserializer or their subclasses through SDL's RWOps structure.
+template <class T> class RWOpsWrapper
+{
+public:
+    /// Construct with object reference.
+    RWOpsWrapper(T& object)
+    {
+        ops_.type = dynamic_cast<File*>(&object) ? SDL_RWOPS_STDFILE : SDL_RWOPS_MEMORY;
+        ops_.hidden.unknown.data1 = &object;
+        ops_.size = &Size;
+        ops_.seek = &Seek;
+        ops_.close = &Close;
+        ops_.read = &Read;
+        ops_.write = &Write;
+    }
+
+    /// Return the RWOps structure.
+    SDL_RWops* GetRWOps() { return &ops_; }
+    
+private:
+    /// Return data size of the object.
+    static Sint64 Size(SDL_RWops* context)
+    {
+        T* object = reinterpret_cast<T*>(context->hidden.unknown.data1);
+        Deserializer* des = dynamic_cast<Deserializer*>(object);
+        return des ? des->GetSize() : 0;
+    }
+
+    /// Seek within the object's data.
+    static Sint64 Seek(SDL_RWops* context, Sint64 offset, int whence)
+    {
+        T* object = reinterpret_cast<T*>(context->hidden.unknown.data1);
+        Deserializer* des = dynamic_cast<Deserializer*>(object);
+        if (!des)
+            return 0;
+
+        switch (whence)
+        {
+        case RW_SEEK_SET:
+            des->Seek((unsigned)offset);
+            break;
+
+        case RW_SEEK_CUR:
+            des->Seek((unsigned)(des->GetPosition() + offset));
+            break;
+            
+        case RW_SEEK_END:
+            des->Seek((unsigned)(des->GetSize() + offset));
+            break;
+        }
+
+        return (Sint64)des->GetPosition();
+    }
+
+    /// Close the object. Only meaningful for files, no-op otherwise.
+    static int Close(SDL_RWops* context)
+    {
+        T* object = reinterpret_cast<T*>(context->hidden.unknown.data1);
+        File* file = dynamic_cast<File*>(object);
+        if (file)
+            file->Close();
+        return 0;
+    }
+
+    /// Read from the object. Return number of "packets" read.
+    static size_t Read(SDL_RWops* context, void* ptr, size_t size, size_t maxNum)
+    {
+        T* object = reinterpret_cast<T*>(context->hidden.unknown.data1);
+        Deserializer* des = dynamic_cast<Deserializer*>(object);
+        return des ? des->Read(ptr, size * maxNum) / size : 0;
+    }
+
+    /// Write to the object. Return number of "packets" written.
+    static size_t Write(SDL_RWops* context, const void* ptr, size_t size, size_t maxNum)
+    {
+        T* object = reinterpret_cast<T*>(context->hidden.unknown.data1);
+        Serializer* ser = dynamic_cast<Serializer*>(object);
+        return ser ? ser->Write(ptr, size * maxNum) / size : 0;
+    }
+
+    /// SDL RWOps structure associated with the object.
+    SDL_RWops ops_;
+};
+
+}

+ 77 - 1
Source/Engine/Input/Input.cpp

@@ -33,6 +33,7 @@
 #include "ProcessUtils.h"
 #include "Profiler.h"
 #include "ResourceCache.h"
+#include "RWOpsWrapper.h"
 #include "StringUtils.h"
 #include "Text.h"
 #include "UI.h"
@@ -427,11 +428,48 @@ void Input::SetScreenKeyboardVisible(bool enable)
     }
 }
 
+bool Input::RecordGesture()
+{
+    // If have no touch devices, fail
+    if (!SDL_GetNumTouchDevices())
+    {
+        LOGERROR("Can not record gesture: no touch devices");
+        return false;
+    }
+
+    return SDL_RecordGesture(-1) ? true : false;
+}
+
+bool Input::SaveGestures(Serializer& dest)
+{
+    RWOpsWrapper<Serializer> wrapper(dest);
+    return SDL_SaveAllDollarTemplates(wrapper.GetRWOps()) ? true : false;
+}
+
+bool Input::SaveGesture(Serializer& dest, unsigned gestureID)
+{
+    RWOpsWrapper<Serializer> wrapper(dest);
+    return SDL_SaveDollarTemplate(gestureID, wrapper.GetRWOps()) ? true : false;
+}
+
+unsigned Input::LoadGestures(Deserializer& source)
+{
+    // If have no touch devices, fail
+    if (!SDL_GetNumTouchDevices())
+    {
+        LOGERROR("Can not load gestures: no touch devices");
+        return 0;
+    }
+
+    RWOpsWrapper<Deserializer> wrapper(source);
+    return SDL_LoadDollarTemplates(-1, wrapper.GetRWOps());
+}
+
 bool Input::OpenJoystick(unsigned index)
 {
     if (index >= joysticks_.Size())
     {
-        LOGERRORF("Joystick index #%d is out of bound", index);
+        LOGERRORF("Joystick index #%d is out of bounds", index);
         return false;
     }
 
@@ -1023,6 +1061,44 @@ void Input::HandleSDLEvent(void* sdlEvent)
         }
         break;
 
+    case SDL_DOLLARRECORD:
+        {
+            using namespace GestureRecorded;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_GESTUREID] = (int)evt.dgesture.gestureId;
+            SendEvent(E_GESTURERECORDED, eventData);
+        }
+        break;
+
+    case SDL_DOLLARGESTURE:
+        {
+            using namespace GestureInput;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_GESTUREID] = (int)evt.dgesture.gestureId;
+            eventData[P_CENTERX] = (int)(evt.dgesture.x * graphics_->GetWidth());
+            eventData[P_CENTERY] = (int)(evt.dgesture.y * graphics_->GetHeight());
+            eventData[P_NUMFINGERS] = (int)evt.dgesture.numFingers;
+            eventData[P_ERROR] = evt.dgesture.error;
+            SendEvent(E_GESTUREINPUT, eventData);
+        }
+        break;
+
+    case SDL_MULTIGESTURE:
+        {
+            using namespace MultiGesture;
+
+            VariantMap& eventData = GetEventDataMap();
+            eventData[P_CENTERX] = (int)(evt.mgesture.x * graphics_->GetWidth());
+            eventData[P_CENTERY] = (int)(evt.mgesture.y * graphics_->GetHeight());
+            eventData[P_NUMFINGERS] = (int)evt.mgesture.numFingers;
+            eventData[P_DTHETA] = M_RADTODEG * evt.mgesture.dTheta;
+            eventData[P_DDIST] = evt.mgesture.dDist;
+            SendEvent(E_MULTIGESTURE, eventData);
+        }
+        break;
+
     case SDL_JOYBUTTONDOWN:
         {
             using namespace JoystickButtonDown;

+ 10 - 0
Source/Engine/Input/Input.h

@@ -30,7 +30,9 @@
 namespace Urho3D
 {
 
+class Deserializer;
 class Graphics;
+class Serializer;
 class UIElement;
 class XMLFile;
 
@@ -150,6 +152,14 @@ public:
     bool RemoveScreenJoystick(unsigned index);
     /// Show or hide on-screen keyboard on platforms that support it. When shown, keypresses from it are delivered as key events.
     void SetScreenKeyboardVisible(bool enable);
+    /// Begin recording a touch gesture. Return true if successful. The E_GESTURERECORDED event (which contains the ID for the new gesture) will be sent when recording finishes.
+    bool RecordGesture();
+    /// Save all in-memory touch gestures. Return true if successful.
+    bool SaveGestures(Serializer& dest);
+    /// Save a specific in-memory touch gesture to a file. Return true if successful.
+    bool SaveGesture(Serializer& dest, unsigned gestureID);
+    /// Load touch gestures from a file. Return number of loaded gestures, or 0 on failure.
+    unsigned LoadGestures(Deserializer& source);
 
     /// Return keycode from key name.
     int GetKeyFromName(const String& name) const;

+ 26 - 0
Source/Engine/Input/InputEvents.h

@@ -199,6 +199,32 @@ EVENT(E_EXITREQUESTED, ExitRequested)
 {
 }
 
+/// A touch gesture finished recording.
+EVENT(E_GESTURERECORDED, GestureRecorded)
+{
+    PARAM(P_GESTUREID, GestureID);          // unsigned
+}
+
+/// A recognized touch gesture was input by the user.
+EVENT(E_GESTUREINPUT, GestureInput)
+{
+    PARAM(P_GESTUREID, GestureID);          // unsigned
+    PARAM(P_CENTERX, CenterX);              // int
+    PARAM(P_CENTERY, CenterY);              // int
+    PARAM(P_NUMFINGERS, NumFingers);        // int
+    PARAM(P_ERROR, Error);                  // float
+}
+
+/// Pinch/rotate multi-finger touch gesture motion update.
+EVENT(E_MULTIGESTURE, MultiGesture)
+{
+    PARAM(P_CENTERX, CenterX);              // int
+    PARAM(P_CENTERY, CenterY);              // int
+    PARAM(P_NUMFINGERS, NumFingers);        // int
+    PARAM(P_DTHETA, DTheta);                // float (degrees)
+    PARAM(P_DDIST, DDist);                  // float
+}
+
 static const int MOUSEB_LEFT = 1;
 static const int MOUSEB_MIDDLE = 2;
 static const int MOUSEB_RIGHT = 4;

+ 41 - 0
Source/Engine/LuaScript/pkgs/Input/Input.pkg

@@ -1,3 +1,4 @@
+$#include "File.h"
 $#include "Input.h"
 $#include "UIElement.h"
 
@@ -37,6 +38,13 @@ class Input : public Object
     unsigned AddScreenJoystick(XMLFile* layoutFile = 0, XMLFile* styleFile = 0);
     bool RemoveScreenJoystick(unsigned index);
     void SetScreenKeyboardVisible(bool enable);
+    bool RecordGesture();
+    tolua_outside bool InputSaveGestures @ SaveGestures(File* dest);
+    tolua_outside bool InputSaveGesture @ SaveGesture(File* dest, unsigned gestureID);
+    tolua_outside unsigned InputLoadGestures @ LoadGestures(File* source);
+    tolua_outside bool InputSaveGestures @ SaveGestures(const String fileName);
+    tolua_outside bool InputSaveGesture @ SaveGesture(const String fileName, unsigned gestureID);
+    tolua_outside unsigned InputLoadGestures @ LoadGestures(const String fileName);
 
     int GetKeyFromName(const String name) const;
     int GetKeyFromScancode(int scancode) const;
@@ -90,6 +98,39 @@ Input* GetInput();
 tolua_readonly tolua_property__get_set Input* input;
 
 ${
+static bool InputSaveGestures(Input* input, File* file)
+{
+    return file ? input->SaveGestures(*file) : false;
+}
+
+static bool InputSaveGesture(Input* input, File* file, unsigned gestureID)
+{
+    return file ? input->SaveGesture(*file, gestureID) : false;
+}
+
+static unsigned InputLoadGestures(Input* input, File* file)
+{
+    return file ? input->LoadGestures(*file) : 0;
+}
+
+static bool InputSaveGestures(Input* input, const String& fileName)
+{
+    File file(input->GetContext(), fileName, FILE_WRITE);
+    return file.IsOpen() ? input->SaveGestures(file) : false;
+}
+
+static bool InputSaveGesture(Input* input, const String& fileName, unsigned gestureID)
+{
+    File file(input->GetContext(), fileName, FILE_WRITE);
+    return file.IsOpen() ? input->SaveGesture(file, gestureID) : false;
+}
+
+static unsigned InputLoadGestures(Input* input, const String& fileName)
+{
+    File file(input->GetContext(), fileName, FILE_READ);
+    return file.IsOpen() ? input->LoadGestures(file) : 0;
+}
+
 #define TOLUA_DISABLE_tolua_InputLuaAPI_GetInput00
 static int tolua_InputLuaAPI_GetInput00(lua_State* tolua_S)
 {

+ 19 - 0
Source/Engine/Script/InputAPI.cpp

@@ -428,6 +428,21 @@ static Input* GetInput()
     return GetScriptContext()->GetSubsystem<Input>();
 }
 
+static bool InputSaveGestures(File* file, Input* ptr)
+{
+    return file ? ptr->SaveGestures(*file) : false;
+}
+
+static bool InputSaveGesture(File* file, unsigned gestureID, Input* ptr)
+{
+    return file ? ptr->SaveGesture(*file, gestureID) : false;
+}
+
+static unsigned InputLoadGestures(File* file, Input* ptr)
+{
+    return file ? ptr->LoadGestures(*file) : 0;
+}
+
 static void RegisterInput(asIScriptEngine* engine)
 {
     engine->RegisterObjectType("TouchState", 0, asOBJ_REF);
@@ -458,6 +473,10 @@ static void RegisterInput(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Input", "bool DetectJoysticks()", asMETHOD(Input, DetectJoysticks), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "uint AddScreenJoystick(XMLFile@+ layoutFile = null, XMLFile@+ styleFile = null)", asMETHOD(Input, AddScreenJoystick), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "bool RemoveScreenJoystick(uint)", asMETHOD(Input, RemoveScreenJoystick), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "bool RecordGesture()", asMETHOD(Input, RecordGesture), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Input", "bool SaveGestures(File@+)", asFUNCTION(InputSaveGestures), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Input", "bool SaveGesture(File@+, uint)", asFUNCTION(InputSaveGesture), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Input", "uint LoadGestures(File@+)", asFUNCTION(InputLoadGestures), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Input", "int GetKeyFromName(const String&in) const", asMETHOD(Input, GetKeyFromName), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "int GetKeyFromScancode(int) const", asMETHOD(Input, GetKeyFromScancode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Input", "String GetKeyName(int) const", asMETHOD(Input, GetKeyName), asCALL_THISCALL);

+ 10 - 5
Source/ThirdParty/SDL/src/events/SDL_gesture.c

@@ -19,6 +19,8 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
+// Modified by Lasse Oorni for Urho3D
+
 #include "../SDL_internal.h"
 
 /* General mouse handling code for SDL */
@@ -137,7 +139,8 @@ int SDL_SaveAllDollarTemplates(SDL_RWops *dst)
     for (i = 0; i < SDL_numGestureTouches; i++) {
         SDL_GestureTouch* touch = &SDL_gestureTouch[i];
         for (j = 0; j < touch->numDollarTemplates; j++) {
-            rtrn += SaveTemplate(&touch->dollarTemplate[i], dst);
+            // Urho3D: fix index variable (i -> j)
+            rtrn += SaveTemplate(&touch->dollarTemplate[j], dst);
         }
     }
     return rtrn;
@@ -149,8 +152,9 @@ int SDL_SaveDollarTemplate(SDL_GestureID gestureId, SDL_RWops *dst)
     for (i = 0; i < SDL_numGestureTouches; i++) {
         SDL_GestureTouch* touch = &SDL_gestureTouch[i];
         for (j = 0; j < touch->numDollarTemplates; j++) {
-            if (touch->dollarTemplate[i].hash == gestureId) {
-                return SaveTemplate(&touch->dollarTemplate[i], dst);
+            // Urho3D: gesture IDs are stored as 32bit, so check the low bits only. Fix index variable (i -> j)
+            if ((touch->dollarTemplate[j].hash & 0xffffffff) == (gestureId & 0xffffffff)) {
+                return SaveTemplate(&touch->dollarTemplate[j], dst);
             }
         }
     }
@@ -454,8 +458,9 @@ static int SDL_SendGestureDollar(SDL_GestureTouch* touch,
     SDL_Event event;
     event.dgesture.type = SDL_DOLLARGESTURE;
     event.dgesture.touchId = touch->id;
-    event.mgesture.x = touch->centroid.x;
-    event.mgesture.y = touch->centroid.y;
+    // Urho3D: fixed to store x,y into event.dgesture instead of event.mgesture
+    event.dgesture.x = touch->centroid.x;
+    event.dgesture.y = touch->centroid.y;
     event.dgesture.gestureId = gestureId;
     event.dgesture.error = error;
     /* A finger came up to trigger this event. */