From 36f19f491eef9a7c29026ce172d90bcf1865e9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Tue, 21 May 2024 21:25:35 +0200 Subject: [PATCH] Support touch input in engine, UI and console Add support for touch input to the engine, UI and console. Ingame touch controls require more discussion and will be delivered separately based on this engine implementation. Engine ------ The state of all currently pressed touch fingers is aggregated based on the SDL touch events and can be retrieved with the `IInput::TouchFingerStates` function. This design is less complex than an event-based system where the touch events are delivered to the individual client components, as each system would then have to keep track of the finger states individually. However, this means that only one component can handle touch fingers at any given time, which seems like a reasonable assumption for our use cases. Obsolete code for relative mouse handling on Android is removed. Connecting a mouse to an Android device should now also work as expected, as more recent SDL/Android versions support relative mouse input natively. User Interface -------------- Support absolute mouse positioning and clicking in the user interfaces (menus, editor, demo player) with touch presses. Support right clicking by pressing and holding one finger at roughly the same position for 0.5 seconds. Support scrolling scroll regions up and down with a two finger swiping gesture. Fast scrolling via a two finger flinging gesture is not yet supported and would be a useful future extension. The menus and demo player are fully usable with touch inputs. The editor is only fully usable with an external keyboard and/or mouse, as panning the map is not currently possible with only touch inputs, which is also left as a possible future extension. Console ------- The touch input logic for the user interface is reused for the console. Thereby, text selection in the console with touch input works, although the text can only be copied by pressing Ctrl+C with an external keyboard at the moment. In the future, we could add buttons to the console to activate the search and copy functionalities with touch inputs. Support scrolling the console history up and down with a two finger swiping gesture. The local/remote consoles can currently only be opened with an external keyboard. The ingame touch controls will also include buttons to open the consoles. --- src/engine/client/client.cpp | 4 + src/engine/client/input.cpp | 71 ++++++++++--- src/engine/client/input.h | 15 ++- src/engine/input.h | 56 +++++++++- src/game/client/components/console.cpp | 46 ++++++-- src/game/client/components/console.h | 2 + src/game/client/ui.cpp | 140 +++++++++++++++++++++++-- src/game/client/ui.h | 29 ++++- src/game/client/ui_scrollregion.cpp | 5 + src/game/client/ui_scrollregion.h | 1 + 10 files changed, 326 insertions(+), 43 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index d343c0436..a10a6f157 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -4558,6 +4558,10 @@ int main(int argc, const char **argv) pClient->ShellRegister(); #endif + // Do not automatically translate touch events to mouse events and vice versa. + SDL_SetHint("SDL_TOUCH_MOUSE_EVENTS", "0"); + SDL_SetHint("SDL_MOUSE_TOUCH_EVENTS", "0"); + #if defined(CONF_PLATFORM_MACOS) // Hints will not be set if there is an existing override hint or environment variable that takes precedence. // So this respects cli environment overrides. diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index 528fc4e82..dd5fe2ba6 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -263,14 +263,7 @@ bool CInput::MouseRelative(float *pX, float *pY) return false; ivec2 Relative; -#if defined(CONF_PLATFORM_ANDROID) // No relative mouse on Android - ivec2 CurrentPos; - SDL_GetMouseState(&CurrentPos.x, &CurrentPos.y); - Relative = CurrentPos - m_LastMousePos; - m_LastMousePos = CurrentPos; -#else SDL_GetRelativeMouseState(&Relative.x, &Relative.y); -#endif *pX = Relative.x; *pY = Relative.y; @@ -287,25 +280,30 @@ void CInput::MouseModeAbsolute() void CInput::MouseModeRelative() { m_InputGrabbed = true; -#if !defined(CONF_PLATFORM_ANDROID) // No relative mouse on Android SDL_SetRelativeMouseMode(SDL_TRUE); -#endif Graphics()->SetWindowGrab(true); // Clear pending relative mouse motion SDL_GetRelativeMouseState(nullptr, nullptr); } -void CInput::NativeMousePos(int *pX, int *pY) const +vec2 CInput::NativeMousePos() const { - SDL_GetMouseState(pX, pY); + ivec2 Position; + SDL_GetMouseState(&Position.x, &Position.y); + return vec2(Position.x, Position.y); } -bool CInput::NativeMousePressed(int Index) +bool CInput::NativeMousePressed(int Index) const { int i = SDL_GetMouseState(nullptr, nullptr); return (i & SDL_BUTTON(Index)) != 0; } +const std::vector &CInput::TouchFingerStates() const +{ + return m_vTouchFingerStates; +} + const char *CInput::GetClipboardText() { SDL_free(m_pClipboardText); @@ -353,6 +351,10 @@ void CInput::Clear() mem_zero(m_aInputState, sizeof(m_aInputState)); mem_zero(m_aInputCount, sizeof(m_aInputCount)); m_vInputEvents.clear(); + for(CTouchFingerState &TouchFingerState : m_vTouchFingerStates) + { + TouchFingerState.m_Delta = vec2(0.0f, 0.0f); + } } float CInput::GetUpdateTime() const @@ -539,6 +541,39 @@ void CInput::HandleJoystickRemovedEvent(const SDL_JoyDeviceEvent &Event) } } +void CInput::HandleTouchDownEvent(const SDL_TouchFingerEvent &Event) +{ + CTouchFingerState TouchFingerState; + TouchFingerState.m_Finger.m_DeviceId = Event.touchId; + TouchFingerState.m_Finger.m_FingerId = Event.fingerId; + TouchFingerState.m_Position = vec2(Event.x, Event.y); + TouchFingerState.m_Delta = vec2(Event.dx, Event.dy); + m_vTouchFingerStates.emplace_back(TouchFingerState); +} + +void CInput::HandleTouchUpEvent(const SDL_TouchFingerEvent &Event) +{ + auto FoundState = std::find_if(m_vTouchFingerStates.begin(), m_vTouchFingerStates.end(), [Event](const CTouchFingerState &State) { + return State.m_Finger.m_DeviceId == Event.touchId && State.m_Finger.m_FingerId == Event.fingerId; + }); + if(FoundState != m_vTouchFingerStates.end()) + { + m_vTouchFingerStates.erase(FoundState); + } +} + +void CInput::HandleTouchMotionEvent(const SDL_TouchFingerEvent &Event) +{ + auto FoundState = std::find_if(m_vTouchFingerStates.begin(), m_vTouchFingerStates.end(), [Event](const CTouchFingerState &State) { + return State.m_Finger.m_DeviceId == Event.touchId && State.m_Finger.m_FingerId == Event.fingerId; + }); + if(FoundState != m_vTouchFingerStates.end()) + { + FoundState->m_Position = vec2(Event.x, Event.y); + FoundState->m_Delta += vec2(Event.dx, Event.dy); + } +} + void CInput::SetCompositionWindowPosition(float X, float Y, float H) { SDL_Rect Rect; @@ -745,6 +780,18 @@ int CInput::Update() Action |= IInput::FLAG_RELEASE; break; + case SDL_FINGERDOWN: + HandleTouchDownEvent(Event.tfinger); + break; + + case SDL_FINGERUP: + HandleTouchUpEvent(Event.tfinger); + break; + + case SDL_FINGERMOTION: + HandleTouchMotionEvent(Event.tfinger); + break; + case SDL_WINDOWEVENT: // Ignore keys following a focus gain as they may be part of global // shortcuts diff --git a/src/engine/client/input.h b/src/engine/client/input.h index 5ff04b06c..e53ec3b4c 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -60,8 +60,8 @@ private: IEngineGraphics *m_pGraphics; IConsole *m_pConsole; - IEngineGraphics *Graphics() { return m_pGraphics; } - IConsole *Console() { return m_pConsole; } + IEngineGraphics *Graphics() const { return m_pGraphics; } + IConsole *Console() const { return m_pConsole; } // joystick std::vector m_vJoysticks; @@ -78,7 +78,6 @@ private: bool m_MouseFocus; #if defined(CONF_PLATFORM_ANDROID) - ivec2 m_LastMousePos = ivec2(0, 0); // No relative mouse on Android int m_NumBackPresses = 0; bool m_BackButtonReleased = true; int64_t m_LastBackPress = -1; @@ -102,6 +101,7 @@ private: uint32_t m_aInputCount[g_MaxKeys]; unsigned char m_aInputState[g_MaxKeys]; uint32_t m_InputCounter; + std::vector m_vTouchFingerStates; void UpdateMouseState(); void UpdateJoystickState(); @@ -110,6 +110,9 @@ private: void HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event); void HandleJoystickAddedEvent(const SDL_JoyDeviceEvent &Event); void HandleJoystickRemovedEvent(const SDL_JoyDeviceEvent &Event); + void HandleTouchDownEvent(const SDL_TouchFingerEvent &Event); + void HandleTouchUpEvent(const SDL_TouchFingerEvent &Event); + void HandleTouchMotionEvent(const SDL_TouchFingerEvent &Event); char m_aDropFile[IO_MAX_PATH_LENGTH]; @@ -142,8 +145,10 @@ public: bool MouseRelative(float *pX, float *pY) override; void MouseModeAbsolute() override; void MouseModeRelative() override; - void NativeMousePos(int *pX, int *pY) const override; - bool NativeMousePressed(int Index) override; + vec2 NativeMousePos() const override; + bool NativeMousePressed(int Index) const override; + + const std::vector &TouchFingerStates() const override; const char *GetClipboardText() override; void SetClipboardText(const char *pText) override; diff --git a/src/engine/input.h b/src/engine/input.h index fec0b71c2..efaec84f2 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -6,9 +6,11 @@ #include "kernel.h" #include +#include #include #include +#include const int g_MaxKeys = 512; extern const char g_aaKeyStrings[g_MaxKeys][20]; @@ -89,12 +91,62 @@ public: virtual void SetActiveJoystick(size_t Index) = 0; // mouse - virtual void NativeMousePos(int *pX, int *pY) const = 0; - virtual bool NativeMousePressed(int Index) = 0; + virtual vec2 NativeMousePos() const = 0; + virtual bool NativeMousePressed(int Index) const = 0; virtual void MouseModeRelative() = 0; virtual void MouseModeAbsolute() = 0; virtual bool MouseRelative(float *pX, float *pY) = 0; + // touch + /** + * Represents a unique finger for a current touch event. If there are multiple touch input devices, they + * are handled transparently like different fingers. The concrete values of the member variables of this + * class are arbitrary based on the touch device driver and should only be used to uniquely identify touch + * fingers. Note that once a finger has been released, the same finger value may also be reused again. + */ + class CTouchFinger + { + friend class CInput; + + int64_t m_DeviceId; + int64_t m_FingerId; + + public: + bool operator==(const CTouchFinger &Other) const { return m_DeviceId == Other.m_DeviceId && m_FingerId == Other.m_FingerId; } + bool operator!=(const CTouchFinger &Other) const { return !(*this == Other); } + }; + /** + * Represents the state of a particular touch finger currently being pressed down on a touch device. + */ + class CTouchFingerState + { + public: + /** + * The unique finger which this state is associated with. + */ + CTouchFinger m_Finger; + /** + * The current position of the finger. The x- and y-components of the position are normalized to the + * range `0.0f`-`1.0f` representing the absolute position of the finger on the current touch device. + */ + vec2 m_Position; + /** + * The current delta of the finger. The x- and y-components of the delta are normalized to the + * range `0.0f`-`1.0f` representing the absolute delta of the finger on the current touch device. + * + * @remark This is reset to zero at the end of each frame. + */ + vec2 m_Delta; + }; + /** + * Returns a vector of the states of all touch fingers currently being pressed down on touch devices. + * Note that this only contains fingers which are pressed down, i.e. released fingers are never stored. + * The order of the fingers in this vector is based on the order in which the fingers where pressed. + * + * @return vector of all touch finger states + */ + virtual const std::vector &TouchFingerStates() const = 0; + // clipboard virtual const char *GetClipboardText() = 0; virtual void SetClipboardText(const char *pText) = 0; diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index b962b8a3c..d0a3097bf 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -1072,24 +1072,48 @@ void CGameConsole::OnRender() TextRender()->TextEx(&Cursor, aPrompt); // check if mouse is pressed - if(!pConsole->m_MouseIsPress && Input()->NativeMousePressed(1)) + const vec2 WindowSize = vec2(Graphics()->WindowWidth(), Graphics()->WindowHeight()); + const vec2 ScreenSize = vec2(Screen.w, Screen.h); + Ui()->UpdateTouchState(m_TouchState); + const auto &&GetMousePosition = [&]() -> vec2 { + if(m_TouchState.m_PrimaryPressed) + { + return m_TouchState.m_PrimaryPosition * ScreenSize; + } + else + { + return Input()->NativeMousePos() / WindowSize * ScreenSize; + } + }; + if(!pConsole->m_MouseIsPress && (m_TouchState.m_PrimaryPressed || Input()->NativeMousePressed(1))) { pConsole->m_MouseIsPress = true; - ivec2 MousePress; - Input()->NativeMousePos(&MousePress.x, &MousePress.y); - pConsole->m_MousePress.x = (MousePress.x / (float)Graphics()->WindowWidth()) * Screen.w; - pConsole->m_MousePress.y = (MousePress.y / (float)Graphics()->WindowHeight()) * Screen.h; + pConsole->m_MousePress = GetMousePosition(); + } + if(pConsole->m_MouseIsPress && !m_TouchState.m_PrimaryPressed && !Input()->NativeMousePressed(1)) + { + pConsole->m_MouseIsPress = false; } if(pConsole->m_MouseIsPress) { - ivec2 MouseRelease; - Input()->NativeMousePos(&MouseRelease.x, &MouseRelease.y); - pConsole->m_MouseRelease.x = (MouseRelease.x / (float)Graphics()->WindowWidth()) * Screen.w; - pConsole->m_MouseRelease.y = (MouseRelease.y / (float)Graphics()->WindowHeight()) * Screen.h; + pConsole->m_MouseRelease = GetMousePosition(); } - if(pConsole->m_MouseIsPress && !Input()->NativeMousePressed(1)) + const float ScaledRowHeight = RowHeight / ScreenSize.y; + if(absolute(m_TouchState.m_ScrollAmount.y) >= ScaledRowHeight) { - pConsole->m_MouseIsPress = false; + if(m_TouchState.m_ScrollAmount.y > 0.0f) + { + pConsole->m_BacklogCurLine += pConsole->GetLinesToScroll(-1, 1); + m_TouchState.m_ScrollAmount.y -= ScaledRowHeight; + } + else + { + --pConsole->m_BacklogCurLine; + if(pConsole->m_BacklogCurLine < 0) + pConsole->m_BacklogCurLine = 0; + m_TouchState.m_ScrollAmount.y += ScaledRowHeight; + } + pConsole->m_HasSelection = false; } x = Cursor.m_X; diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 3e92ad195..bdfbf4a62 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -10,6 +10,7 @@ #include #include +#include enum { @@ -153,6 +154,7 @@ class CGameConsole : public CComponent float m_StateChangeDuration; bool m_WantsSelectionCopy = false; + CUi::CTouchState m_TouchState; static const ColorRGBA ms_SearchHighlightColor; static const ColorRGBA ms_SearchSelectedColor; diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index e29646fa3..16830838c 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -176,24 +176,63 @@ void CUi::OnCursorMove(float X, float Y) void CUi::Update(vec2 MouseWorldPos) { - unsigned MouseButtons = 0; + const vec2 WindowSize = vec2(Graphics()->WindowWidth(), Graphics()->WindowHeight()); + const CUIRect *pScreen = Screen(); + + unsigned UpdatedMouseButtonsNext = 0; if(Enabled()) { - if(Input()->KeyIsPressed(KEY_MOUSE_1)) - MouseButtons |= 1; - if(Input()->KeyIsPressed(KEY_MOUSE_2)) - MouseButtons |= 2; - if(Input()->KeyIsPressed(KEY_MOUSE_3)) - MouseButtons |= 4; + // Update mouse buttons based on mouse keys + for(int MouseKey = KEY_MOUSE_1; MouseKey <= KEY_MOUSE_3; ++MouseKey) + { + if(Input()->KeyIsPressed(MouseKey)) + { + m_UpdatedMouseButtons |= 1 << (MouseKey - KEY_MOUSE_1); + } + } + + // Update mouse position and buttons based on touch finger state + UpdateTouchState(m_TouchState); + if(m_TouchState.m_AnyPressed) + { + if(!CheckMouseLock()) + { + m_UpdatedMousePos = m_TouchState.m_PrimaryPosition * WindowSize; + m_UpdatedMousePos.x = clamp(m_UpdatedMousePos.x, 0.0f, WindowSize.x - 1.0f); + m_UpdatedMousePos.y = clamp(m_UpdatedMousePos.y, 0.0f, WindowSize.y - 1.0f); + } + m_UpdatedMouseDelta += m_TouchState.m_PrimaryDelta * WindowSize; + + // Scroll currently hovered scroll region with touch scroll gesture. + if(m_TouchState.m_ScrollAmount != vec2(0.0f, 0.0f)) + { + if(m_pHotScrollRegion != nullptr) + { + m_pHotScrollRegion->ScrollRelativeDirect(-m_TouchState.m_ScrollAmount.y * pScreen->h); + } + m_TouchState.m_ScrollAmount = vec2(0.0f, 0.0f); + } + + // We need to delay the click until the next update or it's not possible to use UI + // elements because click and hover would happen at the same time for touch events. + if(m_TouchState.m_PrimaryPressed) + { + UpdatedMouseButtonsNext |= 1; + } + if(m_TouchState.m_SecondaryPressed) + { + UpdatedMouseButtonsNext |= 2; + } + } } - const CUIRect *pScreen = Screen(); - m_MousePos = m_UpdatedMousePos * vec2(pScreen->w / Graphics()->WindowWidth(), pScreen->h / Graphics()->WindowHeight()); + m_MousePos = m_UpdatedMousePos * vec2(pScreen->w, pScreen->h) / WindowSize; m_MouseDelta = m_UpdatedMouseDelta; m_UpdatedMouseDelta = vec2(0.0f, 0.0f); m_MouseWorldPos = MouseWorldPos; m_LastMouseButtons = m_MouseButtons; - m_MouseButtons = MouseButtons; + m_MouseButtons = m_UpdatedMouseButtons; + m_UpdatedMouseButtons = UpdatedMouseButtonsNext; m_pHotItem = m_pBecomingHotItem; if(m_pActiveItem) @@ -257,6 +296,87 @@ void CUi::ConvertMouseMove(float *pX, float *pY, IInput::ECursorType CursorType) *pY *= Factor; } +void CUi::UpdateTouchState(CTouchState &State) const +{ + const std::vector &vTouchFingerStates = Input()->TouchFingerStates(); + + // Updated touch position as long as any finger is beinged pressed. + const bool WasAnyPressed = State.m_AnyPressed; + State.m_AnyPressed = !vTouchFingerStates.empty(); + if(State.m_AnyPressed) + { + // We always use the position of first finger being pressed down. Multi-touch UI is + // not possible and always choosing the last finger would cause the cursor to briefly + // warp without having any effect if multiple fingers are used. + const IInput::CTouchFingerState &PrimaryTouchFingerState = vTouchFingerStates.front(); + State.m_PrimaryPosition = PrimaryTouchFingerState.m_Position; + State.m_PrimaryDelta = PrimaryTouchFingerState.m_Delta; + } + + // Update primary (left click) and secondary (right click) action. + if(State.m_SecondaryPressedNext) + { + // The secondary action is delayed by one frame until the primary has been released, + // otherwise most UI elements cannot be activated by the secondary action because they + // never become the hot-item unless all mouse buttons are released for one frame. + State.m_SecondaryPressedNext = false; + State.m_SecondaryPressed = true; + } + else if(vTouchFingerStates.size() != 1) + { + // Consider primary and secondary to be pressed only when exactly one finger is pressed, + // to avoid UI elements and console text selection being activated while scrolling. + State.m_PrimaryPressed = false; + State.m_SecondaryPressed = false; + } + else if(!WasAnyPressed) + { + State.m_PrimaryPressed = true; + State.m_SecondaryActivationTime = Client()->GlobalTime(); + State.m_SecondaryActivationDelta = vec2(0.0f, 0.0f); + } + else if(State.m_PrimaryPressed) + { + // Activate secondary by pressing and holding roughly on the same position for some time. + const float SecondaryActivationDelay = 0.5f; + const float SecondaryActivationMaxDistance = 0.001f; + State.m_SecondaryActivationDelta += State.m_PrimaryDelta; + if(Client()->GlobalTime() - State.m_SecondaryActivationTime >= SecondaryActivationDelay && + length(State.m_SecondaryActivationDelta) <= SecondaryActivationMaxDistance) + { + State.m_PrimaryPressed = false; + State.m_SecondaryPressedNext = true; + } + } + + // Handle two fingers being moved roughly in same direction as a scrolling gesture. + if(vTouchFingerStates.size() == 2) + { + const vec2 Delta0 = vTouchFingerStates[0].m_Delta; + const vec2 Delta1 = vTouchFingerStates[1].m_Delta; + const float Similarity = dot(normalize(Delta0), normalize(Delta1)); + const float SimilarityThreshold = 0.8f; // How parallel the deltas have to be (1.0f being completely parallel) + if(Similarity > SimilarityThreshold) + { + const float DirectionThreshold = 3.0f; // How much longer the delta of one axis has to be compared to other axis + + // Vertical scrolling (y-delta must be larger than x-delta) + if(absolute(Delta0.y) > DirectionThreshold * absolute(Delta0.x) && + absolute(Delta1.y) > DirectionThreshold * absolute(Delta1.x) && + Delta0.y * Delta1.y > 0.0f) // Same y direction required + { + // Accumulate average delta of the two fingers + State.m_ScrollAmount.y += (Delta0.y + Delta1.y) / 2.0f; + } + } + } + else + { + // Scrolling gesture should start from zero again if released. + State.m_ScrollAmount = vec2(0.0f, 0.0f); + } +} + bool CUi::ConsumeHotkey(EHotkey Hotkey) { const bool Pressed = m_HotkeysPressed & Hotkey; diff --git a/src/game/client/ui.h b/src/game/client/ui.h index 7ee062aea..6cc7129f2 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -320,6 +320,26 @@ public: */ typedef std::function FPopupMenuClosedCallback; + /** + * Represents the aggregated state of current touch events to control a user interface. + */ + class CTouchState + { + friend class CUi; + + bool m_SecondaryPressedNext = false; + float m_SecondaryActivationTime = 0.0f; + vec2 m_SecondaryActivationDelta = vec2(0.0f, 0.0f); + + public: + bool m_AnyPressed = false; + bool m_PrimaryPressed = false; + bool m_SecondaryPressed = false; + vec2 m_PrimaryPosition = vec2(-1.0f, -1.0f); + vec2 m_PrimaryDelta = vec2(0.0f, 0.0f); + vec2 m_ScrollAmount = vec2(0.0f, 0.0f); + }; + private: bool m_Enabled; @@ -327,8 +347,8 @@ private: const void *m_pActiveItem = nullptr; const void *m_pLastActiveItem = nullptr; // only used internally to track active CLineInput const void *m_pBecomingHotItem = nullptr; - const CScrollRegion *m_pHotScrollRegion = nullptr; - const CScrollRegion *m_pBecomingHotScrollRegion = nullptr; + CScrollRegion *m_pHotScrollRegion = nullptr; + CScrollRegion *m_pBecomingHotScrollRegion = nullptr; bool m_ActiveItemValid = false; int m_ActiveButtonLogicButton = -1; @@ -360,8 +380,10 @@ private: vec2 m_MousePos = vec2(0.0f, 0.0f); // in gui space vec2 m_MouseDelta = vec2(0.0f, 0.0f); // in gui space vec2 m_MouseWorldPos = vec2(-1.0f, -1.0f); // in world space + unsigned m_UpdatedMouseButtons = 0; unsigned m_MouseButtons = 0; unsigned m_LastMouseButtons = 0; + CTouchState m_TouchState; bool m_MouseSlow = false; bool m_MouseLock = false; const void *m_pMouseLockId = nullptr; @@ -491,7 +513,7 @@ public: } return false; } - void SetHotScrollRegion(const CScrollRegion *pId) { m_pBecomingHotScrollRegion = pId; } + void SetHotScrollRegion(CScrollRegion *pId) { m_pBecomingHotScrollRegion = pId; } const void *HotItem() const { return m_pHotItem; } const void *NextHotItem() const { return m_pBecomingHotItem; } const void *ActiveItem() const { return m_pActiveItem; } @@ -512,6 +534,7 @@ public: bool MouseInsideClip() const { return !IsClipped() || MouseInside(ClipArea()); } bool MouseHovered(const CUIRect *pRect) const { return MouseInside(pRect) && MouseInsideClip(); } void ConvertMouseMove(float *pX, float *pY, IInput::ECursorType CursorType) const; + void UpdateTouchState(CTouchState &State) const; void ResetMouseSlow() { m_MouseSlow = false; } bool ConsumeHotkey(EHotkey Hotkey); diff --git a/src/game/client/ui_scrollregion.cpp b/src/game/client/ui_scrollregion.cpp index a4d8abe6f..8c0cdba6c 100644 --- a/src/game/client/ui_scrollregion.cpp +++ b/src/game/client/ui_scrollregion.cpp @@ -234,6 +234,11 @@ void CScrollRegion::ScrollRelative(EScrollRelative Direction, float SpeedMultipl m_ScrollSpeedMultiplier = SpeedMultiplier; } +void CScrollRegion::ScrollRelativeDirect(float ScrollAmount) +{ + m_RequestScrollY = clamp(m_ScrollY + ScrollAmount, 0.0f, m_ContentH - m_ClipRect.h); +} + void CScrollRegion::DoEdgeScrolling() { if(!ScrollbarShown()) diff --git a/src/game/client/ui_scrollregion.h b/src/game/client/ui_scrollregion.h index 203356c4c..0094b66e2 100644 --- a/src/game/client/ui_scrollregion.h +++ b/src/game/client/ui_scrollregion.h @@ -133,6 +133,7 @@ public: bool AddRect(const CUIRect &Rect, bool ShouldScrollHere = false); // returns true if the added rect is visible (not clipped) void ScrollHere(EScrollOption Option = SCROLLHERE_KEEP_IN_VIEW); void ScrollRelative(EScrollRelative Direction, float SpeedMultiplier = 1.0f); + void ScrollRelativeDirect(float ScrollAmount); const CUIRect *ClipRect() const { return &m_ClipRect; } void DoEdgeScrolling(); bool RectClipped(const CUIRect &Rect) const;