// // UserInputService.h // App // // Created by Ben Tkacheff on 8/28/12. // // #pragma once #include "Util/TextureId.h" #include "V8DataModel/DataModel.h" #include "V8DataModel/InputObject.h" #define NKEYSTATES 512 namespace RBX { class Humanoid; class Camera; class Frame; class ScreenGui; extern const char* const sUserInputService; class UserInputService : public DescribedCreatable , public Service { public: typedef std::vector< shared_ptr > InputObjectVector; typedef enum { WRAP_AUTO = 0, WRAP_CENTER, WRAP_HYBRID, WRAP_NONEANDCENTER, WRAP_NONE } WrapMode; typedef enum { MOUSETYPE_DEFAULT = 0, MOUSETYPE_LOCKCENTER, MOUSETYPE_LOCKCURRENT } MouseType; typedef enum { OVERRIDE_BEHAVIOR_NONE = 0, OVERRIDE_BEHAVIOR_FORCESHOW, OVERRIDE_BEHAVIOR_FORCEHIDE } OverrideMouseIconBehavior; typedef enum { GESTURE_TAP = 0, GESTURE_LONGPRESS, GESTURE_SWIPE, GESTURE_PINCH, GESTURE_ROTATE, GESTURE_PAN, GESTURE_NONE } Gesture; enum UserCFrame { USERCFRAME_HEAD, USERCFRAME_LEFTHAND, USERCFRAME_RIGHTHAND, USERCFRAME_COUNT }; private: struct KeyboardData { RBX::KeyCode keyCode; ModCode modCode; char modifiedKey; bool isDown; }; typedef DescribedCreatable Super; typedef std::vector< std::pair, void*> > EventsVector; typedef std::vector< std::pair,shared_ptr > > > GestureEventsVector; typedef std::vector< std::pair, bool> > TextBoxEventsVector; // for touch input debugging static bool DrawTouchEvents; static RBX::Color3 DrawTouchColor; static RBX::Color3 DrawMoveColor; shared_ptr drawTouchFrame; shared_ptr currentTextBox; bool touchEnabled; bool keyboardEnabled; bool mouseEnabled; bool gamepadEnabled; bool accelerometerEnabled; bool gyroscopeEnabled; InputObject::UserInputType lastInputType; bool modalEnabled; bool localCharacterJumpEnabled; static bool isStudioEmulatingMobile; bool isJump; std::vector mouseIconStack; shared_ptr mouseEventObject; float maxWalkDelta; RBX::Vector2 walkDirection; RBX::Vector2 lastWalkDirection; boost::mutex GestureEventsMutex; boost::mutex ToolEventsMutex; boost::mutex TextboxEventsMutex; boost::mutex MotionEventsMutex; boost::mutex KeyboardEventsMutex; EventsVector changeEventsToProcess; EventsVector beginEventsToProcess; EventsVector endEventsToProcess; EventsVector tempEndEvents; GestureEventsVector gestureEventsToProcess; std::vector keyboardEventsToProcess; InputObjectVector currentTouches; InputObjectVector toolEventsVector; TextBoxEventsVector textboxFinishedVector; RBX::Vector2 cameraPanDelta; float cameraZoom; RBX::Vector2 cameraMouseTrack; RBX::Vector2 cameraMouseWrap; bool fireJumpRequestEvent; bool mouseIconEnabled; // Devs use this to hide mouse in their game MouseType mouseType; OverrideMouseIconBehavior overrideMouseIconBehavior; // Core Scripts use this to determine whether mouse shows in menus // this is used to store mouse events for touch input (for legacy compatibility with gui objects) boost::unordered_map > fakeMouseEventsMap; boost::unordered_map > supportedGamepadKeyCodes; boost::unordered_map connectedGamepadsMap; weak_ptr lastDownGuiObjectLeft; weak_ptr lastDownGuiObjectRight; bool shouldFireAccelerationEvent; bool shouldFireRotationEvent; bool shouldFireGravityEvent; shared_ptr rotationInputObject; CoordinateFrame rotationCFrame; shared_ptr accelerationInputObject; shared_ptr gravityInputObject; void processMotionEvents(); // Mouse Stuff WrapMode wrapMode; shared_ptr currentMousePosition; // Keyboard Stuff boost::unordered_map > newKeyState; std::vector > modPairs; bool capsLocked; int studioCamFlySteps; // VR Stuff bool vrEnabled; bool recenterUserHeadCFrameRequested; CoordinateFrame userHeadCFrame; CoordinateFrame userCFrame[USERCFRAME_COUNT]; void processToolEvents(); void processCameraInternal(); void processTextboxInternal(); void processKeyboardEvents(); RBX::ModCode getCurrentModKey(); void clearKeyStateInternal(); void setKeyStateInternal(RBX::KeyCode keyCode, ModCode modCode, char modifiedKey, bool isDown); void fireLegacyMouseEvent(const shared_ptr& inputObject, void* nativeInputObject, bool fireImmediately = false); void signalInputEventOnService(const shared_ptr& inputObject, const InputObject::UserInputState& inputState, bool processedEvent, bool menuIsOpen); void processInputVector(EventsVector& eventVector, const InputObject::UserInputState& inputState); bool inputVectorHasInputPair(const std::pair, void*>& eventPair, const EventsVector& vectorToCheck); bool eraseInputTypeFromInputVector(const InputObject::UserInputType inputType, EventsVector& vectorToEraseFrom); void eraseInputPairFromInputVector(const std::pair, void*>& eventPair, EventsVector& vectorToCheck); void processGestures(); void fireGestureEvent(const UserInputService::Gesture gesture, const shared_ptr& touchPositions, const shared_ptr& args, const bool wasSunk = false); void processInputObjects(); static void moveLocalCharacterInternal(weak_ptr weakDataModel, Vector2 walkDir, float walkDelta); static void setLocalHumanoidJump(weak_ptr weakDataModel, bool isJump); void doDataModelProcessInput(DataModel* dataModel, const shared_ptr& inputObject, void* nativeInputObject, const InputObject::UserInputState& inputState); void dataModelProcessInput(const shared_ptr& inputObject, void* nativeInputObject, const InputObject::UserInputState& inputState); void updateLastInputType(const shared_ptr& inputObject); void updateCurrentMousePosition(const shared_ptr& inputObject); void cleanupCurrentTouches(); public: UserInputService(); ~UserInputService() { } typedef enum { DIRECTION_RIGHT = 0, DIRECTION_LEFT, DIRECTION_UP, DIRECTION_DOWN, DIRECTION_NONE } SwipeDirection; typedef enum { PLATFORM_WINDOWS = 0, PLATFORM_OSX, PLATFORM_IOS, PLATFORM_ANDROID, PLATFORM_XBOXONE, PLATFORM_PS4, PLATFORM_PS3, PLATFORM_XBOX360, PLATFORM_WIIU, PLATFORM_NX, PLATFORM_OUYA, PLATFORM_ANDROIDTV, PLATFORM_CHROMECAST, PLATFORM_LINUX, PLATFORM_STEAMOS, PLATFORM_WEBOS, PLATFORM_DOS, PLATFORM_BEOS, PLATFORM_UWP, PLATFORM_NONE } Platform; static Reflection::EventDesc, bool), rbx::signal, bool)>, rbx::signal, bool)>* (UserInputService::*)(bool)> event_InputBegin; static Reflection::EventDesc, bool), rbx::signal, bool)>, rbx::signal, bool)>* (UserInputService::*)(bool)> event_InputUpdate; static Reflection::EventDesc, bool), rbx::signal, bool)>, rbx::signal, bool)>* (UserInputService::*)(bool)> event_InputEnd; static bool IsUsingNewKeyboardEvents(); // Signals // IF THESE ARE BEING CALLED DIRECTLY FROM OUTSIDE DATAMODEL, STOP THAT! // could cause a deadlock, please submit a task to datamodel and call the signal rbx::signal&)> processedMouseEvent; rbx::signal, bool)> processedEventSignal; // touch gesture event signals rbx::signal, bool)> tapGestureEvent; rbx::signal, float, float, InputObject::UserInputState, bool)> pinchGestureEvent; rbx::signal swipeGestureEvent; rbx::signal, InputObject::UserInputState, bool)> longPressGestureEvent; rbx::signal, float, float, InputObject::UserInputState, bool)> rotateGestureEvent; rbx::signal, RBX::Vector2, RBX::Vector2, InputObject::UserInputState, bool)> panGestureEvent; // low level touch event signals rbx::signal, bool)> touchStartedEvent; rbx::signal, bool)> coreTouchStartedEvent; rbx::signal, bool)> touchEndedEvent; rbx::signal, bool)> coreTouchEndedEvent; rbx::signal, bool)> touchMovedEvent; rbx::signal, bool)> coreTouchMovedEvent; rbx::signal, bool)>* getTouchBeganEvent(bool whatever = true); rbx::signal, bool)>* getTouchChangedEvent(bool whatever = true); rbx::signal, bool)>* getTouchEndedEvent(bool whatever = true); // low level generic event signals rbx::signal, bool)> inputBeganEvent; rbx::signal, bool)> coreInputBeganEvent; rbx::signal, bool)> inputUpdatedEvent; rbx::signal, bool)> coreInputUpdatedEvent; rbx::signal, bool)> inputEndedEvent; rbx::signal, bool)> coreInputEndedEvent; rbx::signal, bool)>* getInputBeganEvent(bool whatever = true); rbx::signal, bool)>* getInputChangedEvent(bool whatever = true); rbx::signal, bool)>* getInputEndedEvent(bool whatever = true); // Motion Events rbx::signal, CoordinateFrame)> gyroChangedSignal; rbx::signal)> accelerometerChangedSignal; rbx::signal)> gravityChangedSignal; rbx::signal, CoordinateFrame)>* getOrCreateScriptGyroEventSignal(bool create = true); rbx::signal)>* getOrCreateScriptAccelerometerEventSignal(bool create = true); rbx::signal)>* getOrCreateScriptGravityEventSignal(bool create = true); rbx::signal motionEventListeningStarted; // Text Events rbx::signal)> textBoxGainFocus; rbx::signal)> textBoxReleaseFocus; rbx::signal&)> textBoxFinishedEditing; rbx::signals::connection textboxFocusBeganConnection; rbx::signals::connection textboxFocusEndedConnection; // Gamepad Events rbx::signal gamepadDisconnectedSignal; rbx::signal gamepadConnectedSignal; rbx::signal getSupportedGamepadKeyCodesSignal; // Window Event rbx::signal windowFocused; rbx::signal windowFocusReleased; // Misc Events rbx::signal jumpRequestEvent; rbx::signal mouseIconEnabledEvent; rbx::signal updateInputSignal; rbx::signal lastInputTypeChangedSignal; // Touch Functions InputObjectVector getCurrentTouches() { return currentTouches; } // Motion Functions shared_ptr getAcceleration(); shared_ptr getRotation(); shared_ptr getGravity(); void fireAccelerationEvent(const RBX::Vector3& newAcceleration); void fireRotationEvent(const RBX::Vector3& newRotation, const RBX::Vector4& quaternion); void fireGravityEvent(const RBX::Vector3& newGravity); // take a lock on this whenever editing InputObjects stored in UserInputService static boost::mutex InputEventsMutex; void onRenderStep(); // Mouse Functions void setMouseWrapMode(WrapMode set) { wrapMode = set; } WrapMode getMouseWrapMode() const; shared_ptr getCurrentMousePosition() const { return currentMousePosition; } void setCurrentMousePosition(shared_ptr value) { currentMousePosition = value; } bool isMouseOverGui(); ContentId getDefaultMouseCursor(bool hoverOverActive, bool relativePathNoExtension = false); TextureId getCurrentMouseIcon(); void pushMouseIcon(TextureId newMouseIcon); void popMouseIcon(); void popMouseIcon(TextureId mouseIconToRemove); void setMouseIconEnabled(bool enabled); bool getMouseIconEnabled() const { return mouseIconEnabled; } void setMouseType(MouseType value); MouseType getMouseType() const { return mouseType; } void setOverrideMouseIconBehavior(OverrideMouseIconBehavior value) { overrideMouseIconBehavior = value; } OverrideMouseIconBehavior getOverrideMouseIconBehavior() const { return overrideMouseIconBehavior; } void setLastDownGuiObject(weak_ptr object, InputObject::UserInputType type); weak_ptr getLastDownGuiObject(InputObject::UserInputType type); // Keyboard Functions static char getModifiedKey(RBX::KeyCode key, RBX::ModCode mod); void setKeyState(RBX::KeyCode keyCode, ModCode m, char key, bool isDown); bool isKeyDown(RBX::KeyCode keyCode); bool isCapsLocked(); void resetKeyState(); shared_ptr getKeyboardState(); Vector2 getKeyboardGuiSelectionDirection(const shared_ptr& inputObject); bool isCtrlDown() { return isKeyDown(SDLK_LCTRL) || isKeyDown(SDLK_RCTRL); } bool isShiftDown() { return isKeyDown(SDLK_LSHIFT) || isKeyDown(SDLK_RSHIFT); } bool isAltDown() { return isKeyDown(SDLK_LALT) || isKeyDown(SDLK_RALT); } // Gamepad Functions void safeFireGamepadConnected(InputObject::UserInputType gamepadType); void safeFireGamepadDisconnected(InputObject::UserInputType gamepadType); void setConnectedGamepad(InputObject::UserInputType gamepadType, bool isConnected); bool getGamepadConnected(InputObject::UserInputType gamepadType); shared_ptr getConnectedGamepads(); void setSupportedGamepadKeyCodes(InputObject::UserInputType gamepadType, shared_ptr newSupportedGamepadKeyCodes); shared_ptr getSupportedGamepadKeyCodes(InputObject::UserInputType gamepadType); bool gamepadSupports(InputObject::UserInputType gamepadType, RBX::KeyCode keyCode); shared_ptr getGamepadState(InputObject::UserInputType gamepadType); shared_ptr getNavigationGamepads(); bool isNavigationGamepad(InputObject::UserInputType gamepadType); void setNavigationGamepad(InputObject::UserInputType gamepadType, bool enabled); // Instance /*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider); // fires input events on datamodel w/o taking lock!!!! If you use this make sure you have lock! (usually called from renderstepped externally) void dangerousFireInputEvent(const shared_ptr& inputObject, void* nativeInputObject); // Takes a lock on datamodel, and will update input on render stepped void fireInputEvent(const shared_ptr& inputObject, void* nativeInputObject, bool processed = false); void addGestureEventToProcess(const UserInputService::Gesture gesture, shared_ptr& touchPositions, shared_ptr& args); // Interaction styles bool getTouchEnabled() const; bool getKeyboardEnabled() const; bool getMouseEnabled() const; bool getGamepadEnabled() const; bool getAccelerometerEnabled() const; bool getGyroscopeEnabled() const; void setTouchEnabled(bool value); void setKeyboardEnabled(bool value); void setMouseEnabled(bool value); void setGamepadEnabled(bool value); void setAccelerometerEnabled(bool value); void setGyroscopeEnabled(bool value); static Platform getPlatform(); Platform getPlatformLua() { return getPlatform(); } static bool isTenFootInterface(); bool getModalEnabled() const; void setModalEnabled(bool value); bool getStudioEmulatingMobileEnabled(); void setStudioEmulatingMobileEnabled(bool value); InputObject::UserInputType getLastInputType() { return lastInputType; } // textBox functions void textboxDidFinishEditing(const char* text, bool shouldReturn); void textboxFocused(shared_ptr textBox); void textboxFocusReleased(shared_ptr textBox); shared_ptr getFocusedTextBox(); // Show Stats based on Input bool showStatsBasedOnInputString(const char* text); // Local character switches bool getLocalCharacterJumpEnabled() const; void setLocalCharacterJumpEnabled(bool value); // text functions std::string getPasteText(); std::vector getCommandModCodes(); // returns control for windows, command for os x, etc. // local character manipulations void jumpLocalCharacterLua(); void moveLocalCharacterLua(Vector2 walkDir, float maxWalkDelta); void moveLocalCharacter(RBX::Vector2 movementVector, float maxMovementDelta); void jumpLocalCharacter(bool jumpValue); void jumpOnceLocalCharacter(bool jumpValue); // camera manipulations void rotateCameraLua(RBX::Vector2 mouseDelta); void rotateCamera(RBX::Vector2 mouseDelta); void wrapCamera(RBX::Vector2 mouseWrap); void zoomCameraLua(float zoom); void zoomCamera(float zoom); void mouseTrackCamera(RBX::Vector2 mouseDelta); // raw input mechanisms void sendMouseEvent(const shared_ptr& event, void* nativeEventObject); void processToolEvent(const shared_ptr& event); void sendJumpRequestEvent(); static Reflection::PropDescriptor prop_ModalEnabled; // VR bool getVREnabled() const { return vrEnabled; } const CoordinateFrame& getUserHeadCFrame() const { return userHeadCFrame; } const CoordinateFrame& getUserCFrame(UserCFrame type) const { return userCFrame[type]; } CoordinateFrame getUserCFrameLua(UserCFrame type) { return userCFrame[type]; } void recenterUserHeadCFrame(); rbx::signal userCFrameChanged; // internal (to client, don't access from lua) // returns walkDirection, which is a Vector2 that is the raw input from user (not translated into any sort of actual world coordinates) Vector2 getInputWalkDirection() { return walkDirection; } // returns maxWalkDelta, which is a float that is the max value that x or y can be in walkDirection float getMaxInputWalkValue() { return maxWalkDelta; } bool canUseMouseLockCenter() const; // these are set by client to communicate VR support to game void setVREnabled(bool value); void setUserHeadCFrame(const CoordinateFrame& value); void setUserCFrame(UserCFrame type, const CoordinateFrame& value); bool checkAndClearRecenterUserHeadCFrameRequest(); }; }