diff --git a/CMakeLists.txt b/CMakeLists.txt index e64dadcc8..189c09035 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -730,7 +730,7 @@ endif() if(TARGET_OS STREQUAL "windows") set(PLATFORM_CLIENT) - set(PLATFORM_CLIENT_LIBS opengl32 winmm) + set(PLATFORM_CLIENT_LIBS opengl32 winmm imm32) set(PLATFORM_LIBS) list(APPEND PLATFORM_LIBS shlwapi) # PathIsRelativeW list(APPEND PLATFORM_LIBS version ws2_32) # Windows sockets diff --git a/src/base/system.cpp b/src/base/system.cpp index f8891967f..a45d49b88 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -3750,7 +3750,7 @@ int str_utf8_check(const char *str) return 1; } -void str_utf8_stats(const char *str, int max_size, int max_count, int *size, int *count) +void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count) { const char *cursor = str; *size = 0; @@ -3761,7 +3761,7 @@ void str_utf8_stats(const char *str, int max_size, int max_count, int *size, int { break; } - if(cursor - str >= max_size) + if((size_t)(cursor - str) >= max_size) { break; } diff --git a/src/base/system.h b/src/base/system.h index 360b7c51c..a0e2246fb 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -2386,7 +2386,7 @@ int str_utf8_check(const char *str); - The string is treated as zero-terminated utf8 string. - It's the user's responsibility to make sure the bounds are aligned. */ -void str_utf8_stats(const char *str, int max_size, int max_count, int *size, int *count); +void str_utf8_stats(const char *str, size_t max_size, size_t max_count, size_t *size, size_t *count); /* Function: str_next_token diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index e7df04416..395f18f73 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -3231,7 +3231,6 @@ void CClient::Run() if(!m_EditorActive) { Input()->MouseModeRelative(); - Input()->SetIMEState(true); GameClient()->OnActivateEditor(); m_pEditor->ResetMentions(); m_EditorActive = true; @@ -3239,7 +3238,6 @@ void CClient::Run() } else if(m_EditorActive) { - Input()->SetIMEState(false); m_EditorActive = false; } diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index 0edecb466..af57890b8 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -24,6 +24,16 @@ #define SDL_JOYSTICK_AXIS_MAX 32767 #endif +#if defined(CONF_FAMILY_WINDOWS) +#define WIN32_LEAN_AND_MEAN +#include +// windows.h must be included before imm.h, but clang-format requires includes to be sorted alphabetically, hence this comment. +#include +#endif + +// for platform specific features that aren't available or are broken in SDL +#include + void CInput::AddEvent(char *pText, int Key, int Flags) { if(m_NumEvents != INPUT_BUFFER_SIZE) @@ -57,20 +67,20 @@ CInput::CInput() m_pClipboardText = nullptr; - m_NumTextInputInstances = 0; - m_EditingTextLen = -1; - m_aEditingText[0] = '\0'; + m_CompositionLength = COMP_LENGTH_INACTIVE; + m_CompositionCursor = 0; + m_CandidateSelectedIndex = -1; m_aDropFile[0] = '\0'; } void CInput::Init() { + StopTextInput(); + m_pGraphics = Kernel()->RequestInterface(); m_pConsole = Kernel()->RequestInterface(); - // increase ime instance counter for menu - SetIMEState(true); MouseModeRelative(); InitJoysticks(); @@ -324,6 +334,24 @@ void CInput::SetClipboardText(const char *pText) SDL_SetClipboardText(pText); } +void CInput::StartTextInput() +{ + // enable system messages for IME + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_StartTextInput(); +} + +void CInput::StopTextInput() +{ + SDL_StopTextInput(); + // disable system messages for performance + SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE); + m_CompositionLength = COMP_LENGTH_INACTIVE; + m_CompositionCursor = 0; + m_aComposition[0] = '\0'; + m_vCandidates.clear(); +} + void CInput::Clear() { mem_zero(m_aInputState, sizeof(m_aInputState)); @@ -510,58 +538,14 @@ void CInput::HandleJoystickRemovedEvent(const SDL_JoyDeviceEvent &Event) } } -bool CInput::GetIMEState() +void CInput::SetCompositionWindowPosition(float X, float Y, float H) { - return m_NumTextInputInstances > 0; -} - -void CInput::SetIMEState(bool Activate) -{ - if(Activate) - { - if(m_NumTextInputInstances == 0) - SDL_StartTextInput(); - m_NumTextInputInstances++; - } - else - { - if(m_NumTextInputInstances == 0) - return; - m_NumTextInputInstances--; - if(m_NumTextInputInstances == 0) - SDL_StopTextInput(); - } -} - -const char *CInput::GetIMEEditingText() -{ - if(m_EditingTextLen > 0) - return m_aEditingText; - else - return ""; -} - -int CInput::GetEditingCursor() -{ - return m_EditingCursor; -} - -void CInput::SetEditingPosition(float X, float Y) -{ - float ScreenX0, ScreenY0, ScreenX1, ScreenY1; - int ScreenWidth = Graphics()->ScreenWidth(); - int ScreenHeight = Graphics()->ScreenHeight(); - Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); - - vec2 ScreenScale = vec2(ScreenWidth / (ScreenX1 - ScreenX0), ScreenHeight / (ScreenY1 - ScreenY0)); - - SDL_Rect ImeWindowRect; - ImeWindowRect.x = X * ScreenScale.x; - ImeWindowRect.y = Y * ScreenScale.y; - ImeWindowRect.h = 60; - ImeWindowRect.w = 1000; - - SDL_SetTextInputRect(&ImeWindowRect); + SDL_Rect Rect; + Rect.x = X / m_pGraphics->ScreenHiDPIScale(); + Rect.y = Y / m_pGraphics->ScreenHiDPIScale(); + Rect.h = H / m_pGraphics->ScreenHiDPIScale(); + Rect.w = 0; + SDL_SetTextInputRect(&Rect); } int CInput::Update() @@ -583,8 +567,6 @@ int CInput::Update() NumKeyStates = KEY_MOUSE_1; mem_copy(m_aInputState, pState, NumKeyStates); mem_zero(m_aInputState + NumKeyStates, KEY_LAST - NumKeyStates); - if(m_EditingTextLen == 0) - m_EditingTextLen = -1; // these states must always be updated manually because they are not in the SDL_GetKeyboardState from SDL UpdateMouseState(); @@ -598,26 +580,38 @@ int CInput::Update() int Action = IInput::FLAG_PRESS; switch(Event.type) { + case SDL_SYSWMEVENT: + ProcessSystemMessage(Event.syswm.msg); + break; + case SDL_TEXTEDITING: { - m_EditingTextLen = str_length(Event.edit.text); - if(m_EditingTextLen) + m_CompositionLength = str_length(Event.edit.text); + if(m_CompositionLength) { - str_copy(m_aEditingText, Event.edit.text); - m_EditingCursor = 0; + str_copy(m_aComposition, Event.edit.text); + m_CompositionCursor = 0; for(int i = 0; i < Event.edit.start; i++) - m_EditingCursor = str_utf8_forward(m_aEditingText, m_EditingCursor); + m_CompositionCursor = str_utf8_forward(m_aComposition, m_CompositionCursor); + // Event.edit.length is currently unused on Windows and will always be 0, so we don't support selecting composition text + AddEvent(nullptr, KEY_UNKNOWN, IInput::FLAG_TEXT); } else { - m_aEditingText[0] = '\0'; + m_aComposition[0] = '\0'; + m_CompositionLength = 0; + m_CompositionCursor = 0; } break; } + case SDL_TEXTINPUT: - m_EditingTextLen = -1; + m_aComposition[0] = '\0'; + m_CompositionLength = COMP_LENGTH_INACTIVE; + m_CompositionCursor = 0; AddEvent(Event.text.text, KEY_UNKNOWN, IInput::FLAG_TEXT); break; + // handle keys case SDL_KEYDOWN: // See SDL_Keymod for possible modifiers: @@ -771,7 +765,7 @@ int CInput::Update() break; } - if(Scancode > KEY_FIRST && Scancode < g_MaxKeys && !IgnoreKeys && (!SDL_IsTextInputActive() || m_EditingTextLen == -1)) + if(Scancode > KEY_FIRST && Scancode < g_MaxKeys && !IgnoreKeys && !HasComposition()) { if(Action & IInput::FLAG_PRESS) { @@ -782,9 +776,63 @@ int CInput::Update() } } + if(m_CompositionLength == 0) + m_CompositionLength = COMP_LENGTH_INACTIVE; + return 0; } +void CInput::ProcessSystemMessage(SDL_SysWMmsg *pMsg) +{ +#if defined(CONF_FAMILY_WINDOWS) + // Todo SDL: remove this after SDL2 supports IME candidates + if(pMsg->subsystem == SDL_SYSWM_WINDOWS && pMsg->msg.win.msg == WM_IME_NOTIFY) + { + switch(pMsg->msg.win.wParam) + { + case IMN_OPENCANDIDATE: + case IMN_CHANGECANDIDATE: + { + HWND WindowHandle = pMsg->msg.win.hwnd; + HIMC ImeContext = ImmGetContext(WindowHandle); + DWORD Size = ImmGetCandidateListW(ImeContext, 0, nullptr, 0); + LPCANDIDATELIST pCandidateList = nullptr; + if(Size > 0) + { + pCandidateList = (LPCANDIDATELIST)malloc(Size); + Size = ImmGetCandidateListW(ImeContext, 0, pCandidateList, Size); + } + m_vCandidates.clear(); + if(pCandidateList && Size > 0) + { + for(DWORD i = pCandidateList->dwPageStart; i < pCandidateList->dwCount && (int)m_vCandidates.size() < (int)pCandidateList->dwPageSize; i++) + { + LPCWSTR pCandidate = (LPCWSTR)((DWORD_PTR)pCandidateList + pCandidateList->dwOffset[i]); + const int Len = WideCharToMultiByte(CP_UTF8, 0, pCandidate, -1, nullptr, 0, nullptr, nullptr); + dbg_assert(Len > 0, "WideCharToMultiByte failure"); + m_vCandidates.emplace_back(); + m_vCandidates.back().resize(Len, '\0'); + dbg_assert(WideCharToMultiByte(CP_UTF8, 0, pCandidate, -1, &m_vCandidates.back()[0], Len, nullptr, nullptr) == Len, "WideCharToMultiByte failure"); + } + m_CandidateSelectedIndex = pCandidateList->dwSelection - pCandidateList->dwPageStart; + } + else + { + m_CandidateSelectedIndex = -1; + } + free(pCandidateList); + ImmReleaseContext(WindowHandle, ImeContext); + break; + } + case IMN_CLOSECANDIDATE: + m_vCandidates.clear(); + m_CandidateSelectedIndex = -1; + break; + } + } +#endif +} + bool CInput::GetDropFile(char *aBuf, int Len) { if(m_aDropFile[0] != '\0') diff --git a/src/engine/client/input.h b/src/engine/client/input.h index 3ee21de12..d3aa18ace 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -9,6 +9,9 @@ #include #include +#include +#include + class IEngineGraphics; class CInput : public IEngineInput @@ -75,6 +78,13 @@ private: bool m_MouseFocus; bool m_MouseDoubleClick; + // IME support + char m_aComposition[MAX_COMPOSITION_ARRAY_SIZE]; + int m_CompositionCursor; + int m_CompositionLength; + std::vector m_vCandidates; + int m_CandidateSelectedIndex; + void AddEvent(char *pText, int Key, int Flags); void Clear() override; bool IsEventValid(const CEvent &Event) const override { return Event.m_InputCount == m_InputCounter; } @@ -94,18 +104,15 @@ private: char m_aDropFile[IO_MAX_PATH_LENGTH]; - // IME support - int m_NumTextInputInstances; - char m_aEditingText[INPUT_TEXT_SIZE]; - int m_EditingTextLen; - int m_EditingCursor; - bool KeyState(int Key) const; + void ProcessSystemMessage(SDL_SysWMmsg *pMsg); + public: CInput(); void Init() override; + int Update() override; void Shutdown() override; bool ModifierIsPressed() const override { return KeyState(KEY_LCTRL) || KeyState(KEY_RCTRL) || KeyState(KEY_LGUI) || KeyState(KEY_RGUI); } @@ -128,14 +135,16 @@ public: const char *GetClipboardText() override; void SetClipboardText(const char *pText) override; - int Update() override; - - bool GetIMEState() override; - void SetIMEState(bool Activate) override; - int GetIMEEditingTextLength() const override { return m_EditingTextLen; } - const char *GetIMEEditingText() override; - int GetEditingCursor() override; - void SetEditingPosition(float X, float Y) override; + void StartTextInput() override; + void StopTextInput() override; + const char *GetComposition() const override { return m_aComposition; } + bool HasComposition() const override { return m_CompositionLength != COMP_LENGTH_INACTIVE; } + int GetCompositionCursor() const override { return m_CompositionCursor; } + int GetCompositionLength() const override { return m_CompositionLength; } + const char *GetCandidate(int Index) const override { return m_vCandidates[Index].c_str(); } + int GetCandidateCount() const override { return m_vCandidates.size(); } + int GetCandidateSelectedIndex() const override { return m_CandidateSelectedIndex; } + void SetCompositionWindowPosition(float X, float Y, float H) override; bool GetDropFile(char *aBuf, int Len) override; }; diff --git a/src/engine/client/text.cpp b/src/engine/client/text.cpp index 5af16acd7..772859037 100644 --- a/src/engine/client/text.cpp +++ b/src/engine/client/text.cpp @@ -803,16 +803,15 @@ public: pCursor->m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE; pCursor->m_SelectionHeightFactor = 1.0f; - pCursor->m_PressMouseX = 0; - pCursor->m_PressMouseY = 0; - pCursor->m_ReleaseMouseX = 0; - pCursor->m_ReleaseMouseY = 0; + pCursor->m_PressMouse = vec2(0.0f, 0.0f); + pCursor->m_ReleaseMouse = vec2(0.0f, 0.0f); pCursor->m_SelectionStart = 0; pCursor->m_SelectionEnd = 0; pCursor->m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE; pCursor->m_ForceCursorRendering = false; pCursor->m_CursorCharacter = -1; + pCursor->m_CursorRenderedPosition = vec2(-1.0f, -1.0f); } void MoveCursor(CTextCursor *pCursor, float x, float y) const override @@ -859,15 +858,6 @@ public: return Cursor.BoundingBox(); } - vec2 CaretPosition(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) override - { - CTextCursor Cursor; - SetCursor(&Cursor, 0, 0, Size, Flags); - Cursor.m_LineWidth = LineWidth; - TextEx(&Cursor, pText, StrLength); - return vec2(Cursor.m_X, Cursor.m_Y); - } - void TextColor(float r, float g, float b, float a) override { m_Color.r = r; @@ -1102,18 +1092,18 @@ public: int SelectionStartChar = -1; int SelectionEndChar = -1; - auto &&CheckInsideChar = [&](bool CheckOuter, int CursorX_, int CursorY_, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { - return (LastCharX - LastCharWidth / 2 <= CursorX_ && - CharX + CharWidth / 2 > CursorX_ && - CharY - Size <= CursorY_ && - CharY > CursorY_) || + auto &&CheckInsideChar = [&](bool CheckOuter, vec2 CursorPos, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) -> bool { + return (LastCharX - LastCharWidth / 2 <= CursorPos.x && + CharX + CharWidth / 2 > CursorPos.x && + CharY - Size <= CursorPos.y && + CharY > CursorPos.y) || (CheckOuter && - CharY - Size > CursorY_); + CharY - Size > CursorPos.y); }; - auto &&CheckSelectionStart = [&](bool CheckOuter, int CursorX_, int CursorY_, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { + auto &&CheckSelectionStart = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float LastCharX, float LastCharWidth, float CharX, float CharWidth, float CharY) { if(!SelectionStarted && !SelectionUsedCase) { - if(CheckInsideChar(CheckOuter, CursorX_, CursorY_, LastCharX, LastCharWidth, CharX, CharWidth, CharY)) + if(CheckInsideChar(CheckOuter, CursorPos, LastCharX, LastCharWidth, CharX, CharWidth, CharY)) { SelectionChar = pCursor->m_GlyphCount; SelectionStarted = !SelectionStarted; @@ -1121,17 +1111,17 @@ public: } } }; - auto &&CheckOutsideChar = [&](bool CheckOuter, int CursorX_, int CursorY_, float CharX, float CharWidth, float CharY) -> bool { - return (CharX + CharWidth / 2 > CursorX_ && - CharY - Size <= CursorY_ && - CharY > CursorY_) || + auto &&CheckOutsideChar = [&](bool CheckOuter, vec2 CursorPos, float CharX, float CharWidth, float CharY) -> bool { + return (CharX + CharWidth / 2 > CursorPos.x && + CharY - Size <= CursorPos.y && + CharY > CursorPos.y) || (CheckOuter && - CharY <= CursorY_); + CharY <= CursorPos.y); }; - auto &&CheckSelectionEnd = [&](bool CheckOuter, int CursorX_, int CursorY_, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { + auto &&CheckSelectionEnd = [&](bool CheckOuter, vec2 CursorPos, int &SelectionChar, bool &SelectionUsedCase, float CharX, float CharWidth, float CharY) { if(SelectionStarted && !SelectionUsedCase) { - if(CheckOutsideChar(CheckOuter, CursorX_, CursorY_, CharX, CharWidth, CharY)) + if(CheckOutsideChar(CheckOuter, CursorPos, CharX, CharWidth, CharY)) { SelectionChar = pCursor->m_GlyphCount; SelectionStarted = !SelectionStarted; @@ -1356,7 +1346,7 @@ public: if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) { - if(pCursor->m_CursorCharacter == -1 && CheckInsideChar(pCursor->m_GlyphCount == 0, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, pCursor->m_GlyphCount == 0 ? std::numeric_limits::lowest() : LastCharX, LastCharWidth, CharX, CharWidth, TmpY)) + if(pCursor->m_CursorCharacter == -1 && CheckInsideChar(pCursor->m_GlyphCount == 0, pCursor->m_ReleaseMouse, pCursor->m_GlyphCount == 0 ? std::numeric_limits::lowest() : LastCharX, LastCharWidth, CharX, CharWidth, TmpY)) { pCursor->m_CursorCharacter = pCursor->m_GlyphCount; } @@ -1366,15 +1356,15 @@ public: { if(pCursor->m_GlyphCount == 0) { - CheckSelectionStart(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, std::numeric_limits::lowest(), 0, CharX, CharWidth, TmpY); - CheckSelectionStart(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::lowest(), 0, CharX, CharWidth, TmpY); + CheckSelectionStart(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits::lowest(), 0, CharX, CharWidth, TmpY); + CheckSelectionStart(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::lowest(), 0, CharX, CharWidth, TmpY); } // if selection didn't start and the mouse pos is at least on 50% of the right side of the character start - CheckSelectionStart(false, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); - CheckSelectionStart(false, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); - CheckSelectionEnd(false, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, CharX, CharWidth, TmpY); - CheckSelectionEnd(false, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, CharX, CharWidth, TmpY); + CheckSelectionStart(false, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); + CheckSelectionStart(false, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, LastCharX, LastCharWidth, CharX, CharWidth, TmpY); + CheckSelectionEnd(false, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, CharX, CharWidth, TmpY); + CheckSelectionEnd(false, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, CharX, CharWidth, TmpY); } if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET) { @@ -1399,6 +1389,7 @@ public: HasCursor = true; aCursorQuads[0] = IGraphics::CQuadItem(SelX - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size); aCursorQuads[1] = IGraphics::CQuadItem(SelX, DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2); + pCursor->m_CursorRenderedPosition = vec2(SelX, DrawY); } } @@ -1459,8 +1450,8 @@ public: if(SelectionStarted) { - CheckSelectionEnd(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::max(), 0, DrawY + Size); - CheckSelectionEnd(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, std::numeric_limits::max(), 0, DrawY + Size); + CheckSelectionEnd(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits::max(), 0, DrawY + Size); + CheckSelectionEnd(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits::max(), 0, DrawY + Size); } } else if(pCursor->m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_SET) @@ -1481,7 +1472,7 @@ public: if(pCursor->m_CursorMode != TEXT_CURSOR_CURSOR_MODE_NONE) { - if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && pCursor->m_CursorCharacter == -1 && CheckOutsideChar(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, std::numeric_limits::max(), 0, DrawY + Size)) + if(pCursor->m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE && pCursor->m_CursorCharacter == -1 && CheckOutsideChar(true, pCursor->m_ReleaseMouse, std::numeric_limits::max(), 0, DrawY + Size)) { pCursor->m_CursorCharacter = pCursor->m_GlyphCount; } @@ -1491,6 +1482,7 @@ public: HasCursor = true; aCursorQuads[0] = IGraphics::CQuadItem((LastSelX + LastSelWidth) - CursorOuterInnerDiff, DrawY, CursorOuterWidth, Size); aCursorQuads[1] = IGraphics::CQuadItem((LastSelX + LastSelWidth), DrawY + CursorOuterInnerDiff, CursorInnerWidth, Size - CursorOuterInnerDiff * 2); + pCursor->m_CursorRenderedPosition = vec2(LastSelX + LastSelWidth, DrawY); } } diff --git a/src/engine/input.h b/src/engine/input.h index 19ab452f3..95760c558 100644 --- a/src/engine/input.h +++ b/src/engine/input.h @@ -14,7 +14,7 @@ class IInput : public IInterface public: enum { - INPUT_TEXT_SIZE = 128 + INPUT_TEXT_SIZE = 32 * UTF8_BYTE_LENGTH + 1, }; class CEvent @@ -34,7 +34,7 @@ protected: // quick access to events size_t m_NumEvents; - IInput::CEvent m_aInputEvents[INPUT_BUFFER_SIZE]; + CEvent m_aInputEvents[INPUT_BUFFER_SIZE]; int64_t m_LastUpdate; float m_UpdateTime; @@ -51,6 +51,12 @@ public: CURSOR_MOUSE, CURSOR_JOYSTICK, }; + enum + { + MAX_COMPOSITION_ARRAY_SIZE = 32, // SDL2 limitation + + COMP_LENGTH_INACTIVE = -1, + }; // events size_t NumEvents() const { return m_NumEvents; } @@ -60,8 +66,6 @@ public: dbg_assert(Index < m_NumEvents, "Index invalid"); return m_aInputEvents[Index]; } - CEvent *GetEventsRaw() { return m_aInputEvents; } - size_t *GetEventCountRaw() { return &m_NumEvents; } /** * @return Rolling average of the time in seconds between @@ -110,12 +114,16 @@ public: virtual void SetClipboardText(const char *pText) = 0; // text editing - virtual bool GetIMEState() = 0; - virtual void SetIMEState(bool Activate) = 0; - virtual int GetIMEEditingTextLength() const = 0; - virtual const char *GetIMEEditingText() = 0; - virtual int GetEditingCursor() = 0; - virtual void SetEditingPosition(float X, float Y) = 0; + virtual void StartTextInput() = 0; + virtual void StopTextInput() = 0; + virtual const char *GetComposition() const = 0; + virtual bool HasComposition() const = 0; + virtual int GetCompositionCursor() const = 0; + virtual int GetCompositionLength() const = 0; + virtual const char *GetCandidate(int Index) const = 0; + virtual int GetCandidateCount() const = 0; + virtual int GetCandidateSelectedIndex() const = 0; + virtual void SetCompositionWindowPosition(float X, float Y, float H) = 0; virtual bool GetDropFile(char *aBuf, int Len) = 0; diff --git a/src/engine/textrender.h b/src/engine/textrender.h index 5ad45ac21..bb9fdd725 100644 --- a/src/engine/textrender.h +++ b/src/engine/textrender.h @@ -185,12 +185,10 @@ public: ETextCursorSelectionMode m_CalculateSelectionMode; float m_SelectionHeightFactor; - // these coordinates are repsected if selection mode is set to calculate @see ETextCursorSelectionMode - int m_PressMouseX; - int m_PressMouseY; - // these coordinates are repsected if selection/cursor mode is set to calculate @see ETextCursorSelectionMode / @see ETextCursorCursorMode - int m_ReleaseMouseX; - int m_ReleaseMouseY; + // these coordinates are respected if selection mode is set to calculate @see ETextCursorSelectionMode + vec2 m_PressMouse; + // these coordinates are respected if selection/cursor mode is set to calculate @see ETextCursorSelectionMode / @see ETextCursorCursorMode + vec2 m_ReleaseMouse; // note m_SelectionStart can be bigger than m_SelectionEnd, depending on how the mouse cursor was dragged // also note, that these are the character offsets decoded @@ -201,6 +199,7 @@ public: bool m_ForceCursorRendering; // note this is the decoded character offset int m_CursorCharacter; + vec2 m_CursorRenderedPosition; float Height() const { @@ -273,7 +272,6 @@ public: virtual void Text(float x, float y, float Size, const char *pText, float LineWidth = -1.0f) = 0; virtual float TextWidth(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0, float *pHeight = nullptr, float *pAlignedFontSize = nullptr, float *pMaxCharacterHeightInLine = nullptr) = 0; virtual STextBoundingBox TextBoundingBox(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) = 0; - virtual vec2 CaretPosition(float Size, const char *pText, int StrLength = -1, float LineWidth = -1.0f, int Flags = 0) = 0; virtual ColorRGBA GetTextColor() const = 0; virtual ColorRGBA GetTextOutlineColor() const = 0; diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 8c9e922b0..0de480a88 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -36,6 +36,8 @@ CChat::CChat() std::sort(m_vCommands.begin(), m_vCommands.end()); m_Mode = MODE_NONE; + + m_Input.SetClipboardLineCallback([this](const char *pStr) { SayChat(pStr); }); } void CChat::RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp) @@ -77,8 +79,6 @@ void CChat::Reset() m_PrevShowChat = false; m_Show = false; - m_InputUpdate = false; - m_ChatStringOffset = 0; m_CompletionUsed = false; m_CompletionChosen = -1; m_aCompletionBuffer[0] = 0; @@ -170,99 +170,6 @@ bool CChat::OnInput(const IInput::CEvent &Event) if(m_Mode == MODE_NONE) return false; - if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_V)) - { - const char *pText = Input()->GetClipboardText(); - if(pText) - { - // if the text has more than one line, we send all lines except the last one - // the last one is set as in the text field - char aLine[256]; - int i, Begin = 0; - for(i = 0; i < str_length(pText); i++) - { - if(pText[i] == '\n') - { - int max = minimum(i - Begin + 1, (int)sizeof(aLine)); - str_copy(aLine, pText + Begin, max); - Begin = i + 1; - SayChat(aLine); - while(pText[i] == '\n') - i++; - } - } - int max = minimum(i - Begin + 1, (int)sizeof(aLine)); - str_copy(aLine, pText + Begin, max); - m_Input.Append(aLine); - } - } - - if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_C)) - { - Input()->SetClipboardText(m_Input.GetString()); - } - - if(Input()->ModifierIsPressed()) // jump to spaces and special ASCII characters - { - int SearchDirection = 0; - if(Input()->KeyPress(KEY_LEFT) || Input()->KeyPress(KEY_BACKSPACE)) - SearchDirection = -1; - else if(Input()->KeyPress(KEY_RIGHT) || Input()->KeyPress(KEY_DELETE)) - SearchDirection = 1; - - if(SearchDirection != 0) - { - int OldOffset = m_Input.GetCursorOffset(); - - int FoundAt = SearchDirection > 0 ? m_Input.GetLength() - 1 : 0; - for(int i = m_Input.GetCursorOffset() + SearchDirection; SearchDirection > 0 ? i < m_Input.GetLength() - 1 : i > 0; i += SearchDirection) - { - int Next = i + SearchDirection; - if((m_Input.GetString()[Next] == ' ') || - (m_Input.GetString()[Next] >= 32 && m_Input.GetString()[Next] <= 47) || - (m_Input.GetString()[Next] >= 58 && m_Input.GetString()[Next] <= 64) || - (m_Input.GetString()[Next] >= 91 && m_Input.GetString()[Next] <= 96)) - { - FoundAt = i; - if(SearchDirection < 0) - FoundAt++; - break; - } - } - - if(Input()->KeyPress(KEY_BACKSPACE)) - { - if(m_Input.GetCursorOffset() != 0) - { - char aText[512]; - str_copy(aText, m_Input.GetString(), FoundAt + 1); - - if(m_Input.GetCursorOffset() != str_length(m_Input.GetString())) - str_append(aText, m_Input.GetString() + m_Input.GetCursorOffset(), str_length(m_Input.GetString())); - - m_Input.Set(aText); - } - } - else if(Input()->KeyPress(KEY_DELETE)) - { - if(m_Input.GetCursorOffset() != m_Input.GetLength()) - { - char aText[512]; - aText[0] = '\0'; - - str_copy(aText, m_Input.GetString(), m_Input.GetCursorOffset() + 1); - - if(FoundAt != m_Input.GetLength()) - str_append(aText, m_Input.GetString() + FoundAt, sizeof(aText)); - - m_Input.Set(aText); - FoundAt = OldOffset; - } - } - m_Input.SetCursorOffset(FoundAt); - } - } - if(Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) { DisableMode(); @@ -307,7 +214,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) if(!m_CompletionUsed) { const char *pCursor = m_Input.GetString() + m_Input.GetCursorOffset(); - for(int Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor - 1) != ' '; --pCursor, ++Count) + for(size_t Count = 0; Count < m_Input.GetCursorOffset() && *(pCursor - 1) != ' '; --pCursor, ++Count) ; m_PlaceholderOffset = pCursor - m_Input.GetString(); @@ -405,10 +312,8 @@ bool CChat::OnInput(const IInput::CEvent &Event) str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength, sizeof(aBuf)); m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_pName) + 1; - m_OldChatStringLength = m_Input.GetLength(); - m_Input.Set(aBuf); // TODO: Use Add instead + m_Input.Set(aBuf); m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength); - m_InputUpdate = true; } } else @@ -470,10 +375,8 @@ bool CChat::OnInput(const IInput::CEvent &Event) str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength, sizeof(aBuf)); m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionString); - m_OldChatStringLength = m_Input.GetLength(); - m_Input.Set(aBuf); // TODO: Use Add instead + m_Input.Set(aBuf); m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength); - m_InputUpdate = true; } } } @@ -486,9 +389,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) m_CompletionUsed = false; } - m_OldChatStringLength = m_Input.GetLength(); m_Input.ProcessInput(Event); - m_InputUpdate = true; } if(Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_UP) @@ -532,10 +433,10 @@ void CChat::EnableMode(int Team) else m_Mode = MODE_ALL; - Input()->SetIMEState(true); Input()->Clear(); m_CompletionChosen = -1; m_CompletionUsed = false; + m_Input.Activate(EInputPriority::CHAT); } } @@ -543,8 +444,8 @@ void CChat::DisableMode() { if(m_Mode != MODE_NONE) { - Input()->SetIMEState(false); m_Mode = MODE_NONE; + m_Input.Deactivate(); } } @@ -1156,8 +1057,10 @@ void CChat::OnRender() --m_PendingChatCounter; } - float Width = 300.0f * Graphics()->ScreenAspect(); - Graphics()->MapScreen(0.0f, 0.0f, Width, 300.0f); + const float Height = 300.0f; + const float Width = Height * Graphics()->ScreenAspect(); + Graphics()->MapScreen(0.0f, 0.0f, Width, Height); + float x = 5.0f; float y = 300.0f - 20.0f; if(m_Mode != MODE_NONE) @@ -1166,7 +1069,6 @@ void CChat::OnRender() CTextCursor Cursor; TextRender()->SetCursor(&Cursor, x, y, 8.0f, TEXTFLAG_RENDER); Cursor.m_LineWidth = Width - 190.0f; - Cursor.m_MaxLines = 2; if(m_Mode == MODE_ALL) TextRender()->TextEx(&Cursor, Localize("All"), -1); @@ -1177,52 +1079,32 @@ void CChat::OnRender() TextRender()->TextEx(&Cursor, ": ", -1); - // IME candidate editing - bool Editing = false; - int EditingCursor = Input()->GetEditingCursor(); - if(Input()->GetIMEState()) - { - if(str_length(Input()->GetIMEEditingText())) - { - m_Input.Editing(Input()->GetIMEEditingText(), EditingCursor); - Editing = true; - } - } + const float MessageMaxWidth = Cursor.m_LineWidth - (Cursor.m_X - Cursor.m_StartX); + const CUIRect ClippingRect = {Cursor.m_X, Cursor.m_Y, MessageMaxWidth, 2.25f * Cursor.m_FontSize}; + const float XScale = Graphics()->ScreenWidth() / Width; + const float YScale = Graphics()->ScreenHeight() / Height; + Graphics()->ClipEnable((int)(ClippingRect.x * XScale), (int)(ClippingRect.y * YScale), (int)(ClippingRect.w * XScale), (int)(ClippingRect.h * YScale)); - // check if the visible text has to be moved - if(m_InputUpdate) - { - if(m_ChatStringOffset > 0 && m_Input.GetLength(Editing) < m_OldChatStringLength) - m_ChatStringOffset = maximum(0, m_ChatStringOffset - (m_OldChatStringLength - m_Input.GetLength(Editing))); + float ScrollOffset = m_Input.GetScrollOffset(); + float ScrollOffsetChange = m_Input.GetScrollOffsetChange(); - if(m_ChatStringOffset > m_Input.GetCursorOffset(Editing)) - m_ChatStringOffset -= m_ChatStringOffset - m_Input.GetCursorOffset(Editing); - else - { - CTextCursor Temp = Cursor; - Temp.m_Flags = 0; - TextRender()->TextEx(&Temp, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset); - TextRender()->TextEx(&Temp, "|", -1); - while(Temp.m_LineCount > 2) - { - ++m_ChatStringOffset; - Temp = Cursor; - Temp.m_Flags = 0; - TextRender()->TextEx(&Temp, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset); - TextRender()->TextEx(&Temp, "|", -1); - } - } - m_InputUpdate = false; - } + m_Input.Activate(EInputPriority::CHAT); // Ensure that the input is active + const CUIRect InputCursorRect = {Cursor.m_X, Cursor.m_Y - ScrollOffset, 0.0f, 0.0f}; + const STextBoundingBox BoundingBox = m_Input.Render(&InputCursorRect, Cursor.m_FontSize, TEXTALIGN_TL, m_Input.WasChanged(), MessageMaxWidth); - TextRender()->TextEx(&Cursor, m_Input.GetString(Editing) + m_ChatStringOffset, m_Input.GetCursorOffset(Editing) - m_ChatStringOffset); - static float MarkerOffset = TextRender()->TextWidth(8.0f, "|", -1, -1.0f) / 3; - CTextCursor Marker = Cursor; - Marker.m_X -= MarkerOffset; - TextRender()->TextEx(&Marker, "|", -1); - TextRender()->TextEx(&Cursor, m_Input.GetString(Editing) + m_Input.GetCursorOffset(Editing), -1); - if(m_pClient->m_GameConsole.IsClosed()) - Input()->SetEditingPosition(Marker.m_X, Marker.m_Y + Marker.m_FontSize); + Graphics()->ClipDisable(); + + // Scroll up or down to keep the caret inside the clipping rect + const float CaretPositionY = m_Input.GetCaretPosition().y - ScrollOffsetChange; + if(CaretPositionY < ClippingRect.y) + ScrollOffsetChange -= ClippingRect.y - CaretPositionY; + else if(CaretPositionY + Cursor.m_FontSize > ClippingRect.y + ClippingRect.h) + ScrollOffsetChange += CaretPositionY + Cursor.m_FontSize - (ClippingRect.y + ClippingRect.h); + + UI()->DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, ClippingRect.h, BoundingBox.m_H); + + m_Input.SetScrollOffset(ScrollOffset); + m_Input.SetScrollOffsetChange(ScrollOffsetChange); } #if defined(CONF_VIDEORECORDER) diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h index 84d3b27fb..6e949679a 100644 --- a/src/game/client/components/chat.h +++ b/src/game/client/components/chat.h @@ -15,7 +15,7 @@ class CChat : public CComponent { - CLineInput m_Input; + CLineInputBuffered<512> m_Input; static constexpr float CHAT_WIDTH = 200.0f; static constexpr float CHAT_HEIGHT_FULL = 200.0f; @@ -80,9 +80,6 @@ class CChat : public CComponent int m_Mode; bool m_Show; - bool m_InputUpdate; - int m_ChatStringOffset; - int m_OldChatStringLength; bool m_CompletionUsed; int m_CompletionChosen; char m_aCompletionBuffer[256]; diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 0e5da06f7..7c452a259 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -2,15 +2,11 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include - -#include -#include -#include +#include +#include #include -#include - #include #include #include @@ -19,17 +15,12 @@ #include #include -#include - #include #include -#include -#include - #include - -#include +#include +#include #include "console.h" @@ -111,6 +102,8 @@ CGameConsole::CInstance::CInstance(int Type) m_UsernameReq = false; m_IsCommand = false; + + m_Input.SetClipboardLineCallback([this](const char *pStr) { ExecuteLine(pStr); }); } void CGameConsole::CInstance::Init(CGameConsole *pGameConsole) @@ -203,124 +196,15 @@ void CGameConsole::CInstance::PossibleArgumentsCompleteCallback(int Index, const } } -void CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) +bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) { bool Handled = false; - if(m_pGameConsole->Input()->ModifierIsPressed()) // jump to spaces and special ASCII characters - { - int SearchDirection = 0; - if(m_pGameConsole->Input()->KeyPress(KEY_LEFT) || m_pGameConsole->Input()->KeyPress(KEY_BACKSPACE)) - SearchDirection = -1; - else if(m_pGameConsole->Input()->KeyPress(KEY_RIGHT) || m_pGameConsole->Input()->KeyPress(KEY_DELETE)) - SearchDirection = 1; - - if(SearchDirection != 0) - { - int OldOffset = m_Input.GetCursorOffset(); - - int FoundAt = SearchDirection > 0 ? m_Input.GetLength() - 1 : 0; - for(int i = m_Input.GetCursorOffset() + SearchDirection; SearchDirection > 0 ? i < m_Input.GetLength() - 1 : i > 0; i += SearchDirection) - { - int Next = i + SearchDirection; - if((m_Input.GetString()[Next] == ' ') || - (m_Input.GetString()[Next] >= 32 && m_Input.GetString()[Next] <= 47) || - (m_Input.GetString()[Next] >= 58 && m_Input.GetString()[Next] <= 64) || - (m_Input.GetString()[Next] >= 91 && m_Input.GetString()[Next] <= 96)) - { - FoundAt = i; - if(SearchDirection < 0) - FoundAt++; - break; - } - } - - if(m_pGameConsole->Input()->KeyPress(KEY_BACKSPACE)) - { - if(m_Input.GetCursorOffset() != 0) - { - char aText[512]; - str_copy(aText, m_Input.GetString(), FoundAt + 1); - - if(m_Input.GetCursorOffset() != str_length(m_Input.GetString())) - str_append(aText, m_Input.GetString() + m_Input.GetCursorOffset(), str_length(m_Input.GetString())); - - m_Input.Set(aText); - } - } - - if(m_pGameConsole->Input()->KeyPress(KEY_DELETE)) - { - if(m_Input.GetCursorOffset() != m_Input.GetLength()) - { - char aText[512]; - aText[0] = '\0'; - - str_copy(aText, m_Input.GetString(), m_Input.GetCursorOffset() + 1); - - if(FoundAt != m_Input.GetLength()) - str_append(aText, m_Input.GetString() + FoundAt, sizeof(aText)); - - m_Input.Set(aText); - FoundAt = OldOffset; - } - } - m_Input.SetCursorOffset(FoundAt); - } - } - if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_V)) - { - const char *pText = m_pGameConsole->Input()->GetClipboardText(); - if(pText) - { - char aLine[256]; - int i, Begin = 0; - for(i = 0; i < str_length(pText); i++) - { - if(pText[i] == '\n') - { - if(i == Begin) - { - Begin++; - continue; - } - int max = minimum(i - Begin + 1, (int)sizeof(aLine)); - str_copy(aLine, pText + Begin, max); - Begin = i + 1; - ExecuteLine(aLine); - } - } - int max = minimum(i - Begin + 1, (int)sizeof(aLine)); - str_copy(aLine, pText + Begin, max); - m_Input.Append(aLine); - } - } - else if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_C)) - { - m_pGameConsole->Input()->SetClipboardText(m_Input.GetString()); - } - else if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_A)) - { - m_Input.SetCursorOffset(0); - } - else if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_E)) - { - m_Input.SetCursorOffset(m_Input.GetLength()); - } - else if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_U)) - { - m_Input.SetRange("", 0, m_Input.GetCursorOffset()); - } - else if(m_pGameConsole->Input()->ModifierIsPressed() && m_pGameConsole->Input()->KeyPress(KEY_K)) - { - m_Input.SetRange("", m_Input.GetCursorOffset(), m_Input.GetLength()); - } - if(Event.m_Flags & IInput::FLAG_PRESS) { if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER) { - if(m_Input.GetString()[0] || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot)) + if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot)) { if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed()) { @@ -424,12 +308,12 @@ void CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } // in order not to conflict with CLineInput's handling of Home/End only // react to it when the input is empty - else if(Event.m_Key == KEY_HOME && m_Input.GetString()[0] == '\0') + else if(Event.m_Key == KEY_HOME && m_Input.IsEmpty()) { m_BacklogCurPage = INT_MAX; m_pGameConsole->m_HasSelection = false; } - else if(Event.m_Key == KEY_END && m_Input.GetString()[0] == '\0') + else if(Event.m_Key == KEY_END && m_Input.IsEmpty()) { m_BacklogCurPage = 0; m_pGameConsole->m_HasSelection = false; @@ -437,7 +321,7 @@ void CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) } if(!Handled) - m_Input.ProcessInput(Event); + Handled = m_Input.ProcessInput(Event); if(Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_TEXT)) { @@ -475,6 +359,8 @@ void CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) m_IsCommand = false; } } + + return Handled; } void CGameConsole::CInstance::PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) @@ -591,6 +477,8 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v void CGameConsole::OnRender() { CUIRect Screen = *UI()->Screen(); + CInstance *pConsole = CurrentConsole(); + float ConsoleMaxHeight = Screen.h * 3 / 5.0f; float ConsoleHeight; @@ -599,9 +487,15 @@ void CGameConsole::OnRender() if(Progress >= 1.0f) { if(m_ConsoleState == CONSOLE_CLOSING) + { m_ConsoleState = CONSOLE_CLOSED; + pConsole->m_Input.Deactivate(); + } else if(m_ConsoleState == CONSOLE_OPENING) + { m_ConsoleState = CONSOLE_OPEN; + pConsole->m_Input.Activate(EInputPriority::CONSOLE); + } Progress = 1.0f; } @@ -675,8 +569,6 @@ void CGameConsole::OnRender() ConsoleHeight -= 22.0f; - CInstance *pConsole = CurrentConsole(); - { float FontSize = 10.0f; float RowHeight = FontSize * 1.25f; @@ -714,57 +606,67 @@ void CGameConsole::OnRender() } TextRender()->TextEx(&Cursor, pPrompt, -1); + // check if mouse is pressed + if(!m_MouseIsPress && Input()->NativeMousePressed(1)) + { + m_MouseIsPress = true; + ivec2 MousePress; + Input()->NativeMousePos(&MousePress.x, &MousePress.y); + m_MousePress.x = (MousePress.x / (float)Graphics()->WindowWidth()) * Screen.w; + m_MousePress.y = (MousePress.y / (float)Graphics()->WindowHeight()) * Screen.h; + } + if(m_MouseIsPress) + { + ivec2 MouseRelease; + Input()->NativeMousePos(&MouseRelease.x, &MouseRelease.y); + m_MouseRelease.x = (MouseRelease.x / (float)Graphics()->WindowWidth()) * Screen.w; + m_MouseRelease.y = (MouseRelease.y / (float)Graphics()->WindowHeight()) * Screen.h; + } + if(m_MouseIsPress && !Input()->NativeMousePressed(1)) + { + m_MouseIsPress = false; + } + x = Cursor.m_X; - //console text editing - bool Editing = false; - int EditingCursor = Input()->GetEditingCursor(); - if(Input()->GetIMEState()) + static STextBoundingBox s_BoundingBox = {0.0f, 0.0f, 0.0f, 0.0f}; + + if(m_ConsoleState == CONSOLE_OPEN) { - if(str_length(Input()->GetIMEEditingText())) + if(m_MousePress.y >= s_BoundingBox.m_Y && m_MousePress.y < s_BoundingBox.m_Y + s_BoundingBox.m_H) { - pConsole->m_Input.Editing(Input()->GetIMEEditingText(), EditingCursor); - Editing = true; + CLineInput::SMouseSelection *pMouseSelection = pConsole->m_Input.GetMouseSelection(); + pMouseSelection->m_Selecting = m_MouseIsPress; + pMouseSelection->m_PressMouse = m_MousePress; + pMouseSelection->m_ReleaseMouse = m_MouseRelease; + } + else if(m_MouseIsPress) + { + pConsole->m_Input.SelectNothing(); } } - //hide rcon password - char aInputString[512]; - str_copy(aInputString, pConsole->m_Input.GetString(Editing)); - if(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)) - { - for(int i = 0; i < pConsole->m_Input.GetLength(Editing); ++i) - aInputString[i] = '*'; - } - // render console input (wrap line) - TextRender()->SetCursor(&Cursor, x, y, FontSize, 0); - Cursor.m_LineWidth = Screen.w - 10.0f - x; - TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset(Editing)); - TextRender()->TextEx(&Cursor, aInputString + pConsole->m_Input.GetCursorOffset(Editing), -1); - int Lines = Cursor.m_LineCount; + pConsole->m_Input.SetHidden(m_ConsoleType == CONSOLETYPE_REMOTE && Client()->State() == IClient::STATE_ONLINE && !Client()->RconAuthed() && (pConsole->m_UserGot || !pConsole->m_UsernameReq)); + pConsole->m_Input.Activate(EInputPriority::CONSOLE); // Ensure that the input is active + const CUIRect InputCursorRect = {x, y + FontSize, 0.0f, 0.0f}; + s_BoundingBox = pConsole->m_Input.Render(&InputCursorRect, FontSize, TEXTALIGN_BL, pConsole->m_Input.WasChanged(), Screen.w - 10.0f - x); + if(pConsole->m_Input.HasSelection()) + m_HasSelection = false; // Clear console selection if we have a line input selection - int InputExtraLineCount = Lines - 1; - y -= InputExtraLineCount * FontSize; + y -= s_BoundingBox.m_H - FontSize; TextRender()->SetCursor(&Cursor, x, y, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = Screen.w - 10.0f - x; - if(m_LastInputLineCount != InputExtraLineCount) + if(m_LastInputHeight != s_BoundingBox.m_H) { m_HasSelection = false; m_MouseIsPress = false; - m_LastInputLineCount = InputExtraLineCount; + m_LastInputHeight = s_BoundingBox.m_H; } - TextRender()->TextEx(&Cursor, aInputString, pConsole->m_Input.GetCursorOffset(Editing)); - CTextCursor Marker = Cursor; - Marker.m_LineWidth = -1; - TextRender()->TextEx(&Marker, "|", -1); - TextRender()->TextEx(&Cursor, aInputString + pConsole->m_Input.GetCursorOffset(Editing), -1); - Input()->SetEditingPosition(Marker.m_X, Marker.m_Y + Marker.m_FontSize); - // render possible commands - if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && pConsole->m_Input.GetString()[0]) + if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty()) { CCompletionOptionRenderInfo Info; Info.m_pSelf = this; @@ -813,30 +715,8 @@ void CGameConsole::OnRender() float OffsetY = 0.0f; float LineOffset = 1.0f; - bool WantsSelectionCopy = false; - if(Input()->ModifierIsPressed() && Input()->KeyPress(KEY_C)) - WantsSelectionCopy = true; std::string SelectionString; - // check if mouse is pressed - if(!m_MouseIsPress && Input()->NativeMousePressed(1)) - { - m_MouseIsPress = true; - Input()->NativeMousePos(&m_MousePressX, &m_MousePressY); - m_MousePressX = (m_MousePressX / (float)Graphics()->WindowWidth()) * Screen.w; - m_MousePressY = (m_MousePressY / (float)Graphics()->WindowHeight()) * Screen.h; - } - if(m_MouseIsPress) - { - Input()->NativeMousePos(&m_MouseCurX, &m_MouseCurY); - m_MouseCurX = (m_MouseCurX / (float)Graphics()->WindowWidth()) * Screen.w; - m_MouseCurY = (m_MouseCurY / (float)Graphics()->WindowHeight()) * Screen.h; - } - if(m_MouseIsPress && !Input()->NativeMousePressed(1)) - { - m_MouseIsPress = false; - } - static int s_LastActivePage = pConsole->m_BacklogCurPage; int TotalPages = 1; for(int Page = 0; Page <= maximum(s_LastActivePage, pConsole->m_BacklogCurPage); ++Page, OffsetY = 0.0f) @@ -858,9 +738,9 @@ void CGameConsole::OnRender() if((m_HasSelection || m_MouseIsPress) && m_NewLineCounter > 0) { float MouseExtraOff = pEntry->m_YOffset; - m_MousePressY -= MouseExtraOff; + m_MousePress.y -= MouseExtraOff; if(!m_MouseIsPress) - m_MouseCurY -= MouseExtraOff; + m_MouseRelease.y -= MouseExtraOff; } // next page when lines reach the top @@ -872,11 +752,9 @@ void CGameConsole::OnRender() { TextRender()->SetCursor(&Cursor, 0.0f, y - OffsetY, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = Screen.w - 10.0f; - Cursor.m_CalculateSelectionMode = (m_MouseIsPress || (m_CurSelStart != m_CurSelEnd) || m_HasSelection) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; - Cursor.m_PressMouseX = m_MousePressX; - Cursor.m_PressMouseY = m_MousePressY; - Cursor.m_ReleaseMouseX = m_MouseCurX; - Cursor.m_ReleaseMouseY = m_MouseCurY; + Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && m_MousePress.y < s_BoundingBox.m_Y && (m_MouseIsPress || (m_CurSelStart != m_CurSelEnd) || m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; + Cursor.m_PressMouse = m_MousePress; + Cursor.m_ReleaseMouse = m_MouseRelease; TextRender()->TextEx(&Cursor, pEntry->m_aText, -1); if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) { @@ -885,11 +763,9 @@ void CGameConsole::OnRender() } if(m_CurSelStart != m_CurSelEnd) { - if(WantsSelectionCopy) + if(m_WantsSelectionCopy) { - bool HasNewLine = false; - if(!SelectionString.empty()) - HasNewLine = true; + const bool HasNewLine = !SelectionString.empty(); int OffUTF8Start = 0; int OffUTF8End = 0; if(TextRender()->SelectionToUTF8OffSets(pEntry->m_aText, m_CurSelStart, m_CurSelEnd, OffUTF8Start, OffUTF8End)) @@ -908,11 +784,6 @@ void CGameConsole::OnRender() --m_NewLineCounter; } - if(WantsSelectionCopy && !SelectionString.empty()) - { - Input()->SetClipboardText(SelectionString.c_str()); - } - if(!pEntry) break; TotalPages++; @@ -922,6 +793,15 @@ void CGameConsole::OnRender() pConsole->m_BacklogLock.unlock(); + if(m_WantsSelectionCopy && !SelectionString.empty()) + { + m_HasSelection = false; + m_CurSelStart = -1; + m_CurSelEnd = -1; + Input()->SetClipboardText(SelectionString.c_str()); + m_WantsSelectionCopy = false; + } + // render page char aBuf[128]; TextRender()->TextColor(1, 1, 1, 1); @@ -949,8 +829,11 @@ bool CGameConsole::OnInput(const IInput::CEvent &Event) if(Event.m_Key == KEY_ESCAPE && (Event.m_Flags & IInput::FLAG_PRESS)) Toggle(m_ConsoleType); - else - CurrentConsole()->OnInput(Event); + else if(!CurrentConsole()->OnInput(Event)) + { + if(m_pClient->Input()->ModifierIsPressed() && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_C) + m_WantsSelectionCopy = true; + } return true; } @@ -979,8 +862,6 @@ void CGameConsole::Toggle(int Type) { UI()->SetEnabled(false); m_ConsoleState = CONSOLE_OPENING; - - Input()->SetIMEState(true); } else { @@ -988,8 +869,6 @@ void CGameConsole::Toggle(int Type) UI()->SetEnabled(true); m_pClient->OnRelease(); m_ConsoleState = CONSOLE_CLOSING; - - Input()->SetIMEState(false); } } if(m_ConsoleType != Type) diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index aba19a24e..f741d7a56 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -37,7 +37,7 @@ class CGameConsole : public CComponent CStaticRingBuffer m_History; char *m_pHistoryEntry; - CLineInput m_Input; + CLineInputBuffered<512> m_Input; const char *m_pName; int m_Type; int m_BacklogCurPage; @@ -71,7 +71,7 @@ class CGameConsole : public CComponent void ExecuteLine(const char *pLine); - void OnInput(const IInput::CEvent &Event); + bool OnInput(const IInput::CEvent &Event); void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor); const char *GetString() const { return m_Input.GetString(); } @@ -94,16 +94,15 @@ class CGameConsole : public CComponent float m_StateChangeDuration; bool m_MouseIsPress = false; - int m_MousePressX = 0; - int m_MousePressY = 0; - int m_MouseCurX = 0; - int m_MouseCurY = 0; + vec2 m_MousePress = vec2(0.0f, 0.0f); + vec2 m_MouseRelease = vec2(0.0f, 0.0f); int m_CurSelStart = 0; int m_CurSelEnd = 0; bool m_HasSelection = false; int m_NewLineCounter = 0; + bool m_WantsSelectionCopy = false; - int m_LastInputLineCount = 0; + float m_LastInputHeight = 0.0f; void Toggle(int Type); void Dump(int Type); diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index c848d5929..e1bddb2ff 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -57,9 +57,6 @@ bool CMenus::ms_ValueSelectorTextMode; float CMenus::ms_ButtonHeight = 25.0f; float CMenus::ms_ListheaderHeight = 17.0f; -IInput::CEvent CMenus::m_aInputEvents[MAX_INPUTEVENTS]; -size_t CMenus::m_NumInputEvents; - CMenus::CMenus() { m_Popup = POPUP_NONE; @@ -75,10 +72,7 @@ CMenus::CMenus() m_MenuActive = true; m_ShowStart = true; - m_NumInputEvents = 0; - str_copy(m_aCurrentDemoFolder, "demos"); - m_aCallvoteReason[0] = 0; m_FriendlistSelectedIndex = -1; @@ -447,21 +441,20 @@ int CMenus::DoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, bool { // logic static float s_Value; - static char s_aNumStr[64]; + static CLineInputNumber s_NumberInput; static void *s_pLastTextpID = pID; const bool Inside = UI()->MouseInside(pRect); if(Inside) UI()->SetHotItem(pID); + const int Base = IsHex ? 16 : 10; + if(UI()->MouseButtonReleased(1) && UI()->HotItem() == pID) { s_pLastTextpID = pID; ms_ValueSelectorTextMode = true; - if(IsHex) - str_format(s_aNumStr, sizeof(s_aNumStr), "%06X", Current); - else - str_format(s_aNumStr, sizeof(s_aNumStr), "%d", Current); + s_NumberInput.SetInteger(Current, Base); } if(UI()->CheckActiveItem(pID)) @@ -476,18 +469,13 @@ int CMenus::DoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, bool if(ms_ValueSelectorTextMode && s_pLastTextpID == pID) { - static float s_NumberBoxID = 0; - UI()->DoEditBox(&s_NumberBoxID, pRect, s_aNumStr, sizeof(s_aNumStr), 10.0f, &s_NumberBoxID, false, IGraphics::CORNER_ALL); - - UI()->SetActiveItem(&s_NumberBoxID); + UI()->DoEditBox(&s_NumberInput, pRect, 10.0f); + UI()->SetActiveItem(&s_NumberInput); if(Input()->KeyIsPressed(KEY_RETURN) || Input()->KeyIsPressed(KEY_KP_ENTER) || ((UI()->MouseButtonClicked(1) || UI()->MouseButtonClicked(0)) && !Inside)) { - if(IsHex) - Current = clamp(str_toint_base(s_aNumStr, 16), Min, Max); - else - Current = clamp(str_toint(s_aNumStr), Min, Max); + Current = clamp(s_NumberInput.GetInteger(Base), Min, Max); //m_LockMouse = false; UI()->SetActiveItem(nullptr); ms_ValueSelectorTextMode = false; @@ -561,6 +549,9 @@ int CMenus::DoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, bool UI()->DoLabel(pRect, aBuf, 10.0f, TEXTALIGN_MC); } + if(!ms_ValueSelectorTextMode) + s_NumberInput.Clear(); + return Current; } @@ -969,8 +960,6 @@ void CMenus::OnInit() if(g_Config.m_ClSkipStartMenu) m_ShowStart = false; - UI()->InitInputs(m_aInputEvents, &m_NumInputEvents); - m_RefreshButton.Init(UI(), -1); m_ConnectButton.Init(UI(), -1); @@ -1733,8 +1722,9 @@ int CMenus::Render() TextBox.VSplitLeft(20.0f, 0, &TextBox); TextBox.VSplitRight(60.0f, &TextBox, 0); UI()->DoLabel(&Label, Localize("Password"), 18.0f, TEXTALIGN_ML); - static float s_Offset = 0.0f; - UI()->DoEditBox(&g_Config.m_Password, &TextBox, g_Config.m_Password, sizeof(g_Config.m_Password), 12.0f, &s_Offset, true); + static CLineInput s_PasswordInput(g_Config.m_Password, sizeof(g_Config.m_Password)); + s_PasswordInput.SetHidden(true); + UI()->DoClearableEditBox(&s_PasswordInput, &TextBox, 12.0f); } else if(m_Popup == POPUP_CONNECTING) { @@ -1910,7 +1900,7 @@ int CMenus::Render() char aBufOld[IO_MAX_PATH_LENGTH]; str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); char aBufNew[IO_MAX_PATH_LENGTH]; - str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_aCurrentDemoFile); + str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString()); if(!str_endswith(aBufNew, ".demo")) str_append(aBufNew, ".demo", sizeof(aBufNew)); if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) @@ -1931,8 +1921,7 @@ int CMenus::Render() TextBox.VSplitLeft(20.0f, 0, &TextBox); TextBox.VSplitRight(60.0f, &TextBox, 0); UI()->DoLabel(&Label, Localize("New name:"), 18.0f, TEXTALIGN_ML); - static float s_Offset = 0.0f; - UI()->DoEditBox(&s_Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &s_Offset); + UI()->DoEditBox(&m_DemoRenameInput, &TextBox, 12.0f); } #if defined(CONF_VIDEORECORDER) else if(m_Popup == POPUP_RENDER_DEMO) @@ -1963,14 +1952,14 @@ int CMenus::Render() // name video if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) { - if(!str_endswith(m_aCurrentDemoFile, ".mp4")) - str_append(m_aCurrentDemoFile, ".mp4", sizeof(m_aCurrentDemoFile)); + if(!str_endswith(m_DemoRenderInput.GetString(), ".mp4")) + m_DemoRenderInput.Append(".mp4"); char aWholePath[IO_MAX_PATH_LENGTH]; // store new video filename to origin buffer - if(Storage()->FindFile(m_aCurrentDemoFile, "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath))) + if(Storage()->FindFile(m_DemoRenderInput.GetString(), "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath))) { char aMessage[128 + IO_MAX_PATH_LENGTH]; - str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_aCurrentDemoFile); + str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString()); PopupConfirm(Localize("Replace video"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDemoReplaceVideo, POPUP_NONE, &CMenus::DefaultButtonCallback, POPUP_RENDER_DEMO); } else @@ -2043,8 +2032,7 @@ int CMenus::Render() TextBox.VSplitLeft(20.0f, 0, &TextBox); TextBox.VSplitRight(60.0f, &TextBox, 0); UI()->DoLabel(&Label, Localize("Video name:"), 18.0f, TEXTALIGN_ML); - static float s_Offset = 0.0f; - UI()->DoEditBox(&s_Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &s_Offset); + UI()->DoEditBox(&m_DemoRenderInput, &TextBox, 12.0f); } #endif else if(m_Popup == POPUP_FIRST_LAUNCH) @@ -2093,10 +2081,9 @@ int CMenus::Render() TextBox.VSplitLeft(20.0f, 0, &TextBox); TextBox.VSplitRight(60.0f, &TextBox, 0); UI()->DoLabel(&Label, Localize("Nickname"), 16.0f, TEXTALIGN_ML); - static float s_Offset = 0.0f; - SUIExEditBoxProperties EditProps; - EditProps.m_pEmptyText = Client()->PlayerName(); - UI()->DoEditBox(&g_Config.m_PlayerName, &TextBox, g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName), 12.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditProps); + static CLineInput s_PlayerNameInput(g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName)); + s_PlayerNameInput.SetEmptyText(Client()->PlayerName()); + UI()->DoEditBox(&s_PlayerNameInput, &TextBox, 12.0f); } else if(m_Popup == POPUP_POINTS) { @@ -2161,7 +2148,7 @@ void CMenus::PopupConfirmDemoReplaceVideo() { char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); - const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, m_aCurrentDemoFile, m_Speed); + const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, m_DemoRenderInput.GetString(), m_Speed); m_Speed = 4; if(pError) PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok")); @@ -2245,7 +2232,6 @@ void CMenus::SetActive(bool Active) if(Active != m_MenuActive) { ms_ColorPicker.m_Active = false; - Input()->SetIMEState(Active); UI()->SetHotItem(nullptr); UI()->SetActiveItem(nullptr); } @@ -2308,8 +2294,6 @@ bool CMenus::OnInput(const IInput::CEvent &Event) } if(IsActive()) { - if(m_NumInputEvents < MAX_INPUTEVENTS) - m_aInputEvents[m_NumInputEvents++] = Event; UI()->OnInput(Event); return true; } @@ -2391,7 +2375,6 @@ void CMenus::OnRender() { UI()->FinishCheck(); UI()->ClearHotkeys(); - m_NumInputEvents = 0; return; } @@ -2443,7 +2426,6 @@ void CMenus::OnRender() UI()->FinishCheck(); UI()->ClearHotkeys(); - m_NumInputEvents = 0; } void CMenus::RenderBackground() diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 3a5a81433..cfa4c1497 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -334,14 +334,6 @@ protected: FPopupButtonCallback pfnConfirmButtonCallback = &CMenus::DefaultButtonCallback, int ConfirmNextPopup = POPUP_NONE, FPopupButtonCallback pfnCancelButtonCallback = &CMenus::DefaultButtonCallback, int CancelNextPopup = POPUP_NONE); - // TODO: this is a bit ugly but.. well.. yeah - enum - { - MAX_INPUTEVENTS = 32 - }; - static IInput::CEvent m_aInputEvents[MAX_INPUTEVENTS]; - static size_t m_NumInputEvents; - // some settings static float ms_ButtonHeight; static float ms_ListheaderHeight; @@ -366,8 +358,8 @@ protected: // for call vote int m_CallvoteSelectedOption; int m_CallvoteSelectedPlayer; - char m_aCallvoteReason[VOTE_REASON_LENGTH]; - char m_aFilterString[25]; + CLineInputBuffered m_CallvoteReasonInput; + CLineInputBuffered<64> m_FilterInput; bool m_ControlPageOpening; // demo @@ -443,7 +435,9 @@ protected: }; char m_aCurrentDemoFolder[IO_MAX_PATH_LENGTH]; - char m_aCurrentDemoFile[IO_MAX_PATH_LENGTH]; + CLineInputBuffered m_DemoRenameInput; + CLineInputBuffered m_DemoSliceInput; + CLineInputBuffered m_DemoRenderInput; int m_DemolistSelectedIndex; bool m_DemolistSelectedIsDir; int m_DemolistStorageType; diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 70fcae710..4a89988e3 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -503,16 +503,13 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) QuickSearch.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); - SUIExEditBoxProperties EditProps; + static CLineInput s_FilterInput(g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString)); if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) { UI()->SetActiveItem(&g_Config.m_BrFilterString); - - EditProps.m_SelectText = true; + s_FilterInput.SelectAll(); } - static int s_ClearButton = 0; - static float s_Offset = 0.0f; - if(UI()->DoClearableEditBox(&g_Config.m_BrFilterString, &s_ClearButton, &QuickSearch, g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString), 12.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditProps)) + if(UI()->DoClearableEditBox(&s_FilterInput, &QuickSearch, 12.0f)) Client()->ServerBrowserUpdate(); } @@ -533,11 +530,10 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) QuickExclude.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickExclude); QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude); - static int s_ClearButton = 0; - static float s_Offset = 0.0f; + static CLineInput s_ExcludeInput(g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString)); if(Input()->KeyPress(KEY_X) && Input()->ShiftIsPressed() && Input()->ModifierIsPressed()) - UI()->SetActiveItem(&g_Config.m_BrExcludeString); - if(UI()->DoClearableEditBox(&g_Config.m_BrExcludeString, &s_ClearButton, &QuickExclude, g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString), 12.0f, &s_Offset, false, IGraphics::CORNER_ALL)) + UI()->SetActiveItem(&s_ExcludeInput); + if(UI()->DoClearableEditBox(&s_ExcludeInput, &QuickExclude, 12.0f)) Client()->ServerBrowserUpdate(); } @@ -569,9 +565,8 @@ void CMenus::RenderServerbrowserServerList(CUIRect View) // address info UI()->DoLabel(&ServerAddr, Localize("Server address:"), 14.0f, TEXTALIGN_ML); ServerAddr.VSplitLeft(SearchExcludeAddrStrMax + 5.0f + ExcludeSearchIconMax + 5.0f, NULL, &ServerAddr); - static int s_ClearButton = 0; - static float s_Offset = 0.0f; - UI()->DoClearableEditBox(&g_Config.m_UiServerAddress, &s_ClearButton, &ServerAddr, g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress), 12.0f, &s_Offset); + static CLineInput s_ServerAddressInput(g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress)); + UI()->DoClearableEditBox(&s_ServerAddressInput, &ServerAddr, 12.0f); // button area ButtonArea = ConnectButtons; @@ -670,8 +665,8 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) UI()->DoLabel(&Button, Localize("Game types:"), FontSize, TEXTALIGN_ML); Button.VSplitRight(60.0f, 0, &Button); ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); - static float s_OffsetGametype = 0.0f; - if(UI()->DoEditBox(&g_Config.m_BrFilterGametype, &Button, g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype), FontSize, &s_OffsetGametype)) + static CLineInput s_GametypeInput(g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype)); + if(UI()->DoEditBox(&s_GametypeInput, &Button, FontSize)) Client()->ServerBrowserUpdate(); // server address @@ -679,8 +674,8 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); UI()->DoLabel(&Button, Localize("Server address:"), FontSize, TEXTALIGN_ML); Button.VSplitRight(60.0f, 0, &Button); - static float s_OffsetAddr = 0.0f; - if(UI()->DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &s_OffsetAddr)) + static CLineInput s_FilterServerAddressInput(g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress)); + if(UI()->DoEditBox(&s_FilterServerAddressInput, &Button, FontSize)) Client()->ServerBrowserUpdate(); // player country @@ -1375,25 +1370,25 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_ML); Button.VSplitLeft(80.0f, 0, &Button); - static char s_aName[MAX_NAME_LENGTH] = {0}; - static float s_OffsetName = 0.0f; - UI()->DoEditBox(&s_aName, &Button, s_aName, sizeof(s_aName), FontSize + 2, &s_OffsetName); + static CLineInputBuffered s_NameInput; + UI()->DoEditBox(&s_NameInput, &Button, FontSize + 2.0f); ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends); str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_ML); Button.VSplitLeft(80.0f, 0, &Button); - static char s_aClan[MAX_CLAN_LENGTH] = {0}; - static float s_OffsetClan = 0.0f; - UI()->DoEditBox(&s_aClan, &Button, s_aClan, sizeof(s_aClan), FontSize + 2, &s_OffsetClan); + static CLineInputBuffered s_ClanInput; + UI()->DoEditBox(&s_ClanInput, &Button, FontSize + 2.0f); ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); static CButtonContainer s_AddButton; if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button)) { - m_pClient->Friends()->AddFriend(s_aName, s_aClan); + m_pClient->Friends()->AddFriend(s_NameInput.GetString(), s_ClanInput.GetString()); + s_NameInput.Clear(); + s_ClanInput.Clear(); FriendlistOnUpdate(); Client()->ServerBrowserUpdate(); } diff --git a/src/game/client/components/menus_demo.cpp b/src/game/client/components/menus_demo.cpp index 533028e7c..c866f22a4 100644 --- a/src/game/client/components/menus_demo.cpp +++ b/src/game/client/components/menus_demo.cpp @@ -163,15 +163,15 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); str_append(aDemoName, ".demo", sizeof(aDemoName)); - if(!str_endswith(m_aCurrentDemoFile, ".demo")) - str_append(m_aCurrentDemoFile, ".demo", sizeof(m_aCurrentDemoFile)); + if(!str_endswith(m_DemoSliceInput.GetString(), ".demo")) + m_DemoSliceInput.Append(".demo"); - if(str_comp(aDemoName, m_aCurrentDemoFile) == 0) + if(str_comp(aDemoName, m_DemoSliceInput.GetString()) == 0) str_copy(m_aDemoPlayerPopupHint, Localize("Please use a different name")); else { char aPath[IO_MAX_PATH_LENGTH]; - str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_aCurrentDemoFile); + str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); IOHANDLE DemoFile = Storage()->OpenFile(aPath, IOFLAG_READ, IStorage::TYPE_SAVE); const char *pStr = Localize("File already exists, do you want to overwrite it?"); @@ -207,8 +207,7 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) TextBox.VSplitLeft(20.0f, 0, &TextBox); TextBox.VSplitRight(60.0f, &TextBox, 0); UI()->DoLabel(&Label, Localize("New name:"), 18.0f, TEXTALIGN_ML); - static float s_Offset = 0.0f; - if(UI()->DoEditBox(&s_Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &s_Offset)) + if(UI()->DoEditBox(&m_DemoSliceInput, &TextBox, 12.0f)) { m_aDemoPlayerPopupHint[0] = '\0'; } @@ -561,8 +560,11 @@ void CMenus::RenderDemoPlayer(CUIRect MainView) static CButtonContainer s_SliceSaveButton; if(DoButton_FontIcon(&s_SliceSaveButton, FONT_ICON_ARROW_UP_RIGHT_FROM_SQUARE, 0, &Button, IGraphics::CORNER_ALL)) { - DemoPlayer()->GetDemoName(m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile)); - str_append(m_aCurrentDemoFile, ".demo", sizeof(m_aCurrentDemoFile)); + char aDemoName[IO_MAX_PATH_LENGTH]; + DemoPlayer()->GetDemoName(aDemoName, sizeof(aDemoName)); + m_DemoSliceInput.Set(aDemoName); + m_DemoSliceInput.Append(".demo"); + UI()->SetActiveItem(&m_DemoSliceInput); m_aDemoPlayerPopupHint[0] = '\0'; m_DemoPlayerState = DEMOPLAYER_SLICE_SAVE; } @@ -1142,9 +1144,9 @@ void CMenus::RenderDemoList(CUIRect MainView) { if(m_DemolistSelectedIndex >= 0) { - UI()->SetActiveItem(nullptr); m_Popup = POPUP_RENAME_DEMO; - str_copy(m_aCurrentDemoFile, m_vDemos[m_DemolistSelectedIndex].m_aFilename); + m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenameInput); return; } } @@ -1155,9 +1157,9 @@ void CMenus::RenderDemoList(CUIRect MainView) { if(m_DemolistSelectedIndex >= 0) { - UI()->SetActiveItem(nullptr); m_Popup = POPUP_RENDER_DEMO; - str_copy(m_aCurrentDemoFile, m_vDemos[m_DemolistSelectedIndex].m_aFilename); + m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); + UI()->SetActiveItem(&m_DemoRenderInput); return; } } diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 8e81afaf8..bbec9b1a7 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -535,7 +535,7 @@ bool CMenus::RenderServerControlServer(CUIRect MainView) for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext) { - if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString)) + if(!m_FilterInput.IsEmpty() && !str_utf8_find_nocase(pOption->m_aDescription, m_FilterInput.GetString())) continue; TotalShown++; } @@ -547,7 +547,7 @@ bool CMenus::RenderServerControlServer(CUIRect MainView) for(CVoteOptionClient *pOption = m_pClient->m_Voting.m_pFirst; pOption; pOption = pOption->m_pNext) { i++; - if(m_aFilterString[0] != '\0' && !str_utf8_find_nocase(pOption->m_aDescription, m_aFilterString)) + if(!m_FilterInput.IsEmpty() && !str_utf8_find_nocase(pOption->m_aDescription, m_FilterInput.GetString())) continue; if(NumVoteOptions < Total) @@ -581,7 +581,7 @@ bool CMenus::RenderServerControlKick(CUIRect MainView, bool FilterSpectators) if(Index == m_pClient->m_Snap.m_LocalClientID || (FilterSpectators && pInfoByName->m_Team == TEAM_SPECTATORS)) continue; - if(!str_utf8_find_nocase(m_pClient->m_aClients[Index].m_aName, m_aFilterString)) + if(!str_utf8_find_nocase(m_pClient->m_aClients[Index].m_aName, m_FilterInput.GetString())) continue; if(m_CallvoteSelectedPlayer == Index) @@ -680,18 +680,15 @@ void CMenus::RenderServerControl(CUIRect MainView) TextRender()->SetCurFont(NULL); QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); - static int s_ClearButton = 0; - static float s_Offset = 0.0f; - SUIExEditBoxProperties EditProps; if(m_ControlPageOpening || (Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())) { - UI()->SetActiveItem(&m_aFilterString); + UI()->SetActiveItem(&m_FilterInput); m_ControlPageOpening = false; - EditProps.m_SelectText = true; + m_FilterInput.SelectAll(); } - EditProps.m_pEmptyText = Localize("Search"); - UI()->DoClearableEditBox(&m_aFilterString, &s_ClearButton, &QuickSearch, m_aFilterString, sizeof(m_aFilterString), 14.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditProps); + m_FilterInput.SetEmptyText(Localize("Search")); + UI()->DoClearableEditBox(&m_FilterInput, &QuickSearch, 14.0f); } Bottom.VSplitRight(120.0f, &Bottom, &Button); @@ -701,7 +698,7 @@ void CMenus::RenderServerControl(CUIRect MainView) { if(s_ControlPage == 0) { - m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason); + m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString()); if(g_Config.m_UiCloseWindowAfterChangingSetting) SetActive(false); } @@ -710,7 +707,7 @@ void CMenus::RenderServerControl(CUIRect MainView) if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason); + m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); SetActive(false); } } @@ -719,11 +716,11 @@ void CMenus::RenderServerControl(CUIRect MainView) if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason); + m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString()); SetActive(false); } } - m_aCallvoteReason[0] = 0; + m_CallvoteReasonInput.Clear(); } // render kick reason @@ -735,10 +732,12 @@ void CMenus::RenderServerControl(CUIRect MainView) UI()->DoLabel(&Reason, pLabel, 14.0f, TEXTALIGN_ML); float w = TextRender()->TextWidth(14.0f, pLabel, -1, -1.0f); Reason.VSplitLeft(w + 10.0f, 0, &Reason); - static float s_Offset = 0.0f; if(Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()) - UI()->SetActiveItem(&m_aCallvoteReason); - UI()->DoEditBox(&m_aCallvoteReason, &Reason, m_aCallvoteReason, sizeof(m_aCallvoteReason), 14.0f, &s_Offset, false, IGraphics::CORNER_ALL); + { + UI()->SetActiveItem(&m_CallvoteReasonInput); + m_CallvoteReasonInput.SelectAll(); + } + UI()->DoEditBox(&m_CallvoteReasonInput, &Reason, 14.0f); // extended features (only available when authed in rcon) if(Client()->RconAuthed()) @@ -756,13 +755,13 @@ void CMenus::RenderServerControl(CUIRect MainView) if(DoButton_Menu(&s_ForceVoteButton, Localize("Force vote"), 0, &Button)) { if(s_ControlPage == 0) - m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_aCallvoteReason, true); + m_pClient->m_Voting.CallvoteOption(m_CallvoteSelectedOption, m_CallvoteReasonInput.GetString(), true); else if(s_ControlPage == 1) { if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_aCallvoteReason, true); + m_pClient->m_Voting.CallvoteKick(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); SetActive(false); } } @@ -771,11 +770,11 @@ void CMenus::RenderServerControl(CUIRect MainView) if(m_CallvoteSelectedPlayer >= 0 && m_CallvoteSelectedPlayer < MAX_CLIENTS && m_pClient->m_Snap.m_apPlayerInfos[m_CallvoteSelectedPlayer]) { - m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_aCallvoteReason, true); + m_pClient->m_Voting.CallvoteSpectate(m_CallvoteSelectedPlayer, m_CallvoteReasonInput.GetString(), true); SetActive(false); } } - m_aCallvoteReason[0] = 0; + m_CallvoteReasonInput.Clear(); } if(s_ControlPage == 0) @@ -796,24 +795,22 @@ void CMenus::RenderServerControl(CUIRect MainView) Bottom.VSplitLeft(20.0f, 0, &Button); UI()->DoLabel(&Button, Localize("Vote command:"), 14.0f, TEXTALIGN_ML); - static char s_aVoteDescription[64] = {0}; - static char s_aVoteCommand[512] = {0}; + static CLineInputBuffered<64> s_VoteDescriptionInput; + static CLineInputBuffered<512> s_VoteCommandInput; RconExtension.HSplitTop(20.0f, &Bottom, &RconExtension); Bottom.VSplitRight(10.0f, &Bottom, 0); Bottom.VSplitRight(120.0f, &Bottom, &Button); static CButtonContainer s_AddVoteButton; if(DoButton_Menu(&s_AddVoteButton, Localize("Add"), 0, &Button)) - if(s_aVoteDescription[0] != 0 && s_aVoteCommand[0] != 0) - m_pClient->m_Voting.AddvoteOption(s_aVoteDescription, s_aVoteCommand); + if(!s_VoteDescriptionInput.IsEmpty() && !s_VoteCommandInput.IsEmpty()) + m_pClient->m_Voting.AddvoteOption(s_VoteDescriptionInput.GetString(), s_VoteCommandInput.GetString()); Bottom.VSplitLeft(5.0f, 0, &Bottom); Bottom.VSplitLeft(250.0f, &Button, &Bottom); - static float s_OffsetDesc = 0.0f; - UI()->DoEditBox(&s_aVoteDescription, &Button, s_aVoteDescription, sizeof(s_aVoteDescription), 14.0f, &s_OffsetDesc, false, IGraphics::CORNER_ALL); + UI()->DoEditBox(&s_VoteDescriptionInput, &Button, 14.0f); Bottom.VMargin(20.0f, &Button); - static float s_OffsetCmd = 0.0f; - UI()->DoEditBox(&s_aVoteCommand, &Button, s_aVoteCommand, sizeof(s_aVoteCommand), 14.0f, &s_OffsetCmd, false, IGraphics::CORNER_ALL); + UI()->DoEditBox(&s_VoteCommandInput, &Button, 14.0f); } } } diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index b42891e26..c2a783db1 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -335,10 +335,10 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView) char aBuf[128]; str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML); - static float s_OffsetName = 0.0f; - SUIExEditBoxProperties EditProps; - EditProps.m_pEmptyText = pNameFallback; - if(UI()->DoEditBox(pName, &Button, pName, sizeof(g_Config.m_PlayerName), 14.0f, &s_OffsetName, false, IGraphics::CORNER_ALL, EditProps)) + static CLineInput s_NameInput; + s_NameInput.SetBuffer(pName, sizeof(g_Config.m_PlayerName)); + s_NameInput.SetEmptyText(pNameFallback); + if(UI()->DoEditBox(&s_NameInput, &Button, 14.0f)) { SetNeedSendInfo(); } @@ -351,8 +351,9 @@ void CMenus::RenderSettingsPlayer(CUIRect MainView) Button.VSplitLeft(150.0f, &Button, 0); str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); UI()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML); - static float s_OffsetClan = 0.0f; - if(UI()->DoEditBox(pClan, &Button, pClan, sizeof(g_Config.m_PlayerClan), 14.0f, &s_OffsetClan)) + static CLineInput s_ClanInput; + s_ClanInput.SetBuffer(pClan, sizeof(g_Config.m_PlayerClan)); + if(UI()->DoEditBox(&s_ClanInput, &Button, 14.0f)) { SetNeedSendInfo(); } @@ -585,11 +586,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView) UI()->DoLabel(&SkinPrefixLabel, Localize("Skin prefix"), 14.0f, TEXTALIGN_ML); SkinPrefix.HSplitTop(20.0f, &SkinPrefixLabel, &SkinPrefix); - { - static int s_ClearButton = 0; - static float s_Offset = 0.0f; - UI()->DoClearableEditBox(g_Config.m_ClSkinPrefix, &s_ClearButton, &SkinPrefixLabel, g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix), 14.0f, &s_Offset); - } + static CLineInput s_SkinPrefixInput(g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix)); + UI()->DoClearableEditBox(&s_SkinPrefixInput, &SkinPrefixLabel, 14.0f); SkinPrefix.HSplitTop(2.0f, 0, &SkinPrefix); { @@ -692,11 +690,10 @@ void CMenus::RenderSettingsTee(CUIRect MainView) Label.VSplitRight(34.0f, &Label, &Button); - static float s_OffsetSkin = 0.0f; - static int s_ClearButton = 0; - SUIExEditBoxProperties EditProps; - EditProps.m_pEmptyText = "default"; - if(UI()->DoClearableEditBox(pSkinName, &s_ClearButton, &Label, pSkinName, sizeof(g_Config.m_ClPlayerSkin), 14.0f, &s_OffsetSkin, false, IGraphics::CORNER_ALL, EditProps)) + static CLineInput s_SkinInput; + s_SkinInput.SetBuffer(pSkinName, sizeof(g_Config.m_ClPlayerSkin)); + s_SkinInput.SetEmptyText("default"); + if(UI()->DoClearableEditBox(&s_SkinInput, &Label, 14.0f)) { SetNeedSendInfo(); } @@ -929,17 +926,14 @@ void CMenus::RenderSettingsTee(CUIRect MainView) QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); QuickSearch.VSplitLeft(QuickSearch.w - 15.0f, &QuickSearch, &QuickSearchClearButton); - static int s_ClearButtonSearch = 0; - static float s_Offset = 0.0f; - SUIExEditBoxProperties EditPropsSearch; + static CLineInput s_SkinFilterInput(g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString)); if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) { UI()->SetActiveItem(&g_Config.m_ClSkinFilterString); - - EditPropsSearch.m_SelectText = true; + s_SkinFilterInput.SelectAll(); } - EditPropsSearch.m_pEmptyText = Localize("Search"); - if(UI()->DoClearableEditBox(&g_Config.m_ClSkinFilterString, &s_ClearButtonSearch, &QuickSearch, g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString), 14.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditPropsSearch)) + s_SkinFilterInput.SetEmptyText(Localize("Search")); + if(UI()->DoClearableEditBox(&s_SkinFilterInput, &QuickSearch, 14.0f)) s_InitSkinlist = true; } @@ -2008,15 +2002,16 @@ void CMenus::RenderSettingsSound(CUIRect MainView) // sample rate box { - char aBuf[64]; - str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_SndRate); MainView.HSplitTop(20.0f, &Button, &MainView); UI()->DoLabel(&Button, Localize("Sample rate"), 14.0f, TEXTALIGN_ML); Button.VSplitLeft(190.0f, 0, &Button); - static float s_Offset = 0.0f; - UI()->DoEditBox(&g_Config.m_SndRate, &Button, aBuf, sizeof(aBuf), 14.0f, &s_Offset); - g_Config.m_SndRate = maximum(1, str_toint(aBuf)); - m_NeedRestartSound = !s_SndEnable || s_SndRate != g_Config.m_SndRate; + static CLineInputNumber s_SndRateInput(g_Config.m_SndRate); + if(UI()->DoEditBox(&s_SndRateInput, &Button, 14.0f)) + { + g_Config.m_SndRate = maximum(1, s_SndRateInput.GetInteger()); + m_NeedRestartSound = !s_SndEnable || s_SndRate != g_Config.m_SndRate; + } + s_SndRateInput.SetInteger(g_Config.m_SndRate); } // volume slider @@ -3365,8 +3360,8 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) Background.HSplitTop(20.0f, &Background, 0); Background.VSplitLeft(100.0f, &Label, &TempLabel); UI()->DoLabel(&Label, Localize("Map"), 14.0f, TEXTALIGN_ML); - static float s_Map = 0.0f; - UI()->DoEditBox(g_Config.m_ClBackgroundEntities, &TempLabel, g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities), 14.0f, &s_Map); + static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities)); + UI()->DoEditBox(&s_BackgroundEntitiesInput, &TempLabel, 14.0f); Left.HSplitTop(20.0f, &Button, &Left); bool UseCurrentMap = str_comp(g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0; @@ -3395,16 +3390,15 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) Client()->GenerateTimeoutSeed(); } - static float s_RunOnJoin = 0.0f; Right.HSplitTop(5.0f, 0, &Right); Right.HSplitTop(20.0f, &Label, &Right); Label.VSplitLeft(5.0f, 0, &Label); UI()->DoLabel(&Label, Localize("Run on join"), 14.0f, TEXTALIGN_ML); Right.HSplitTop(20.0f, &Button, &Right); Button.VSplitLeft(5.0f, 0, &Button); - SUIExEditBoxProperties EditProps; - EditProps.m_pEmptyText = Localize("Chat command (e.g. showall 1)"); - UI()->DoEditBox(g_Config.m_ClRunOnJoin, &Button, g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin), 14.0f, &s_RunOnJoin, false, IGraphics::CORNER_ALL, EditProps); + static CLineInput s_RunOnJoinInput(g_Config.m_ClRunOnJoin, sizeof(g_Config.m_ClRunOnJoin)); + s_RunOnJoinInput.SetEmptyText(Localize("Chat command (e.g. showall 1)")); + UI()->DoEditBox(&s_RunOnJoinInput, &Button, 14.0f); #if defined(CONF_FAMILY_WINDOWS) static CButtonContainer s_ButtonUnregisterShell; diff --git a/src/game/client/components/menus_settings_assets.cpp b/src/game/client/components/menus_settings_assets.cpp index 0c265d5de..b1a0e355b 100644 --- a/src/game/client/components/menus_settings_assets.cpp +++ b/src/game/client/components/menus_settings_assets.cpp @@ -259,7 +259,7 @@ static size_t gs_aCustomListSize[NUMBER_OF_ASSETS_TABS] = { 0, }; -static char s_aFilterString[NUMBER_OF_ASSETS_TABS][50]; +static CLineInputBuffered<64> s_aFilterInputs[NUMBER_OF_ASSETS_TABS]; static int s_CurCustomTab = ASSETS_TAB_ENTITIES; @@ -377,7 +377,7 @@ int InitSearchList(std::vector &vpSearchList, std::vector const TName *pAsset = &vAssetList[i]; // filter quick search - if(s_aFilterString[s_CurCustomTab][0] != '\0' && !str_utf8_find_nocase(pAsset->m_aName, s_aFilterString[s_CurCustomTab])) + if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(pAsset->m_aName, s_aFilterInputs[s_CurCustomTab].GetString())) continue; vpSearchList.push_back(pAsset); @@ -472,7 +472,7 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) const SCustomEntities *pEntity = &m_vEntitiesList[i]; // filter quick search - if(s_aFilterString[s_CurCustomTab][0] != '\0' && !str_utf8_find_nocase(pEntity->m_aName, s_aFilterString[s_CurCustomTab])) + if(!s_aFilterInputs[s_CurCustomTab].IsEmpty() && !str_utf8_find_nocase(pEntity->m_aName, s_aFilterInputs[s_CurCustomTab].GetString())) continue; gs_vpSearchEntitiesList.push_back(pEntity); @@ -650,16 +650,13 @@ void CMenus::RenderSettingsCustom(CUIRect MainView) QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); QuickSearch.VSplitLeft(QuickSearch.w - 15.0f, &QuickSearch, &QuickSearchClearButton); - static int s_ClearButton = 0; - static float s_Offset = 0.0f; - SUIExEditBoxProperties EditProps; if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed()) { - UI()->SetActiveItem(&s_aFilterString[s_CurCustomTab]); - EditProps.m_SelectText = true; + UI()->SetActiveItem(&s_aFilterInputs[s_CurCustomTab]); + s_aFilterInputs[s_CurCustomTab].SelectAll(); } - EditProps.m_pEmptyText = Localize("Search"); - if(UI()->DoClearableEditBox(&s_aFilterString[s_CurCustomTab], &s_ClearButton, &QuickSearch, s_aFilterString[s_CurCustomTab], sizeof(s_aFilterString[0]), 14.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditProps)) + s_aFilterInputs[s_CurCustomTab].SetEmptyText(Localize("Search")); + if(UI()->DoClearableEditBox(&s_aFilterInputs[s_CurCustomTab], &QuickSearch, 14.0f)) gs_aInitCustomList[s_CurCustomTab] = true; } diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index ed75dedc1..1e71a0434 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -28,6 +28,7 @@ #include #include "gameclient.h" +#include "lineinput.h" #include "race.h" #include "render.h" @@ -647,6 +648,8 @@ void CGameClient::OnRender() // clear all events/input for this frame Input()->Clear(); + CLineInput::RenderCandidates(); + // clear new tick flags m_NewTick = false; m_NewPredictedTick = false; diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp index 9a502ce7f..c4a46b661 100644 --- a/src/game/client/lineinput.cpp +++ b/src/game/client/lineinput.cpp @@ -1,80 +1,94 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include -#include - -#include "lineinput.h" #include -CLineInput::CLineInput() +#include "lineinput.h" +#include "ui.h" + +IInput *CLineInput::ms_pInput = nullptr; +ITextRender *CLineInput::ms_pTextRender = nullptr; +IGraphics *CLineInput::ms_pGraphics = nullptr; +IClient *CLineInput::ms_pClient = nullptr; + +CLineInput *CLineInput::ms_pActiveInput = nullptr; +EInputPriority CLineInput::ms_ActiveInputPriority = EInputPriority::NONE; + +vec2 CLineInput::ms_CompositionWindowPosition = vec2(0.0f, 0.0f); +float CLineInput::ms_CompositionLineHeight = 0.0f; + +char CLineInput::ms_aStars[128] = {'\0'}; + +void CLineInput::SetBuffer(char *pStr, size_t MaxSize, size_t MaxChars) { - Clear(); + if(m_pStr && m_pStr == pStr) + return; + const char *pLastStr = m_pStr; + m_pStr = pStr; + m_MaxSize = MaxSize; + m_MaxChars = MaxChars; + m_WasChanged = m_pStr && pLastStr && m_WasChanged; + if(!pLastStr) + { + m_ScrollOffset = m_ScrollOffsetChange = 0.0f; + m_CaretPosition = vec2(0.0f, 0.0f); + m_Hidden = false; + m_pEmptyText = nullptr; + m_MouseSelection.m_Selecting = false; + } + if(m_pStr && m_pStr != pLastStr) + UpdateStrData(); } void CLineInput::Clear() { - Set(""); + mem_zero(m_pStr, m_MaxSize); + UpdateStrData(); } void CLineInput::Set(const char *pString) { - str_copy(m_aStr, pString); - str_utf8_stats(m_aStr, MAX_SIZE, MAX_CHARS, &m_Len, &m_NumChars); - m_CursorPos = m_Len; + str_copy(m_pStr, pString, m_MaxSize); + UpdateStrData(); + SetCursorOffset(m_Len); } -void CLineInput::SetRange(const char *pString, int Begin, int End) +void CLineInput::SetRange(const char *pString, size_t Begin, size_t End) { if(Begin > End) std::swap(Begin, End); - Begin = clamp(Begin, 0, m_Len); - End = clamp(End, 0, m_Len); + Begin = clamp(Begin, 0, m_Len); + End = clamp(End, 0, m_Len); - int RemovedCharSize, RemovedCharCount; - str_utf8_stats(m_aStr + Begin, End - Begin + 1, MAX_CHARS, &RemovedCharSize, &RemovedCharCount); + size_t RemovedCharSize, RemovedCharCount; + str_utf8_stats(m_pStr + Begin, End - Begin + 1, m_MaxChars, &RemovedCharSize, &RemovedCharCount); - int AddedCharSize, AddedCharCount; - str_utf8_stats(pString, MAX_SIZE - m_Len + RemovedCharSize, MAX_CHARS - m_NumChars + RemovedCharCount, &AddedCharSize, &AddedCharCount); + size_t AddedCharSize, AddedCharCount; + str_utf8_stats(pString, m_MaxSize - m_Len + RemovedCharSize, m_MaxChars - m_NumChars + RemovedCharCount, &AddedCharSize, &AddedCharCount); if(RemovedCharSize || AddedCharSize) { if(AddedCharSize < RemovedCharSize) { if(AddedCharSize) - mem_copy(m_aStr + Begin, pString, AddedCharSize); - mem_move(m_aStr + Begin + AddedCharSize, m_aStr + Begin + RemovedCharSize, m_Len - Begin - AddedCharSize); + mem_copy(m_pStr + Begin, pString, AddedCharSize); + mem_move(m_pStr + Begin + AddedCharSize, m_pStr + Begin + RemovedCharSize, m_Len - Begin - AddedCharSize); } else if(AddedCharSize > RemovedCharSize) - mem_move(m_aStr + End + AddedCharSize - RemovedCharSize, m_aStr + End, m_Len - End); + mem_move(m_pStr + End + AddedCharSize - RemovedCharSize, m_pStr + End, m_Len - End); if(AddedCharSize >= RemovedCharSize) - mem_copy(m_aStr + Begin, pString, AddedCharSize); + mem_copy(m_pStr + Begin, pString, AddedCharSize); m_CursorPos = End - RemovedCharSize + AddedCharSize; m_Len += AddedCharSize - RemovedCharSize; m_NumChars += AddedCharCount - RemovedCharCount; - m_aStr[m_Len] = '\0'; + m_WasChanged = true; + m_pStr[m_Len] = '\0'; + m_SelectionStart = m_SelectionEnd = m_CursorPos; } } -void CLineInput::Editing(const char *pString, int Cursor) -{ - str_copy(m_aDisplayStr, m_aStr); - char aEditingText[IInput::INPUT_TEXT_SIZE + 2]; - str_format(aEditingText, sizeof(aEditingText), "[%s]", pString); - int NewTextLen = str_length(aEditingText); - int CharsLeft = (int)sizeof(m_aDisplayStr) - str_length(m_aDisplayStr) - 1; - int FillCharLen = NewTextLen < CharsLeft ? NewTextLen : CharsLeft; - for(int i = str_length(m_aDisplayStr) - 1; i >= m_CursorPos; i--) - m_aDisplayStr[i + FillCharLen] = m_aDisplayStr[i]; - for(int i = 0; i < FillCharLen; i++) - m_aDisplayStr[m_CursorPos + i] = aEditingText[i]; - m_aDisplayStr[m_CursorPos + FillCharLen] = '\0'; - m_FakeLen = str_length(m_aDisplayStr); - m_FakeCursorPos = m_CursorPos + Cursor + 1; -} - -void CLineInput::Insert(const char *pString, int Begin) +void CLineInput::Insert(const char *pString, size_t Begin) { SetRange(pString, Begin, Begin); } @@ -84,141 +98,583 @@ void CLineInput::Append(const char *pString) Insert(pString, m_Len); } -static bool IsNotAWordChar(signed char c) +void CLineInput::UpdateStrData() { - return (c > 0 && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z'); // all non chars in ascii -- random + str_utf8_stats(m_pStr, m_MaxSize, m_MaxChars, &m_Len, &m_NumChars); + if(!in_range(m_CursorPos, 0, m_Len)) + SetCursorOffset(m_CursorPos); + if(!in_range(m_SelectionStart, 0, m_Len) || !in_range(m_SelectionEnd, 0, m_Len)) + SetSelection(m_SelectionStart, m_SelectionEnd); } -int32_t CLineInput::Manipulate(IInput::CEvent Event, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr, int32_t ModifyFlags, int ModifierKey) +const char *CLineInput::GetDisplayedString() { - int NumChars = *pNumCharsPtr; - int CursorPos = *pCursorPosPtr; - int Len = *pStrLenPtr; - int32_t Changes = 0; + if(!IsHidden()) + return m_pStr; - if(CursorPos > Len) - CursorPos = Len; + const size_t NumStars = minimum(GetNumChars(), sizeof(ms_aStars) - 1); + for(size_t i = 0; i < NumStars; ++i) + ms_aStars[i] = '*'; + ms_aStars[NumStars] = '\0'; + return ms_aStars; +} - if(Event.m_Flags & IInput::FLAG_TEXT) +void CLineInput::MoveCursor(EMoveDirection Direction, bool MoveWord, const char *pStr, size_t MaxSize, size_t *pCursorPos) +{ + // Check whether cursor position is initially on space or non-space character. + // When forwarding, check character to the right of the cursor position. + // When rewinding, check character to the left of the cursor position (rewind first). + size_t PeekCursorPos = Direction == FORWARD ? *pCursorPos : str_utf8_rewind(pStr, *pCursorPos); + const char *pTemp = pStr + PeekCursorPos; + bool AnySpace = str_utf8_isspace(str_utf8_decode(&pTemp)); + bool AnyWord = !AnySpace; + while(true) { - // gather string stats - int CharCount = 0; - int CharSize = 0; - str_utf8_stats(Event.m_aText, MAX_SIZE, MAX_CHARS, &CharSize, &CharCount); + if(Direction == FORWARD) + *pCursorPos = str_utf8_forward(pStr, *pCursorPos); + else + *pCursorPos = str_utf8_rewind(pStr, *pCursorPos); + if(!MoveWord || *pCursorPos <= 0 || *pCursorPos >= MaxSize) + break; + PeekCursorPos = Direction == FORWARD ? *pCursorPos : str_utf8_rewind(pStr, *pCursorPos); + pTemp = pStr + PeekCursorPos; + const bool CurrentSpace = str_utf8_isspace(str_utf8_decode(&pTemp)); + const bool CurrentWord = !CurrentSpace; + if(Direction == FORWARD && AnySpace && !CurrentSpace) + break; // Forward: Stop when next (right) character is non-space after seeing at least one space character. + else if(Direction == REWIND && AnyWord && !CurrentWord) + break; // Rewind: Stop when next (left) character is space after seeing at least one non-space character. + AnySpace |= CurrentSpace; + AnyWord |= CurrentWord; + } +} - // add new string - if(CharCount) - { - if(Len + CharSize < StrMaxSize && CursorPos + CharSize < StrMaxSize && NumChars + CharCount < StrMaxChars) - { - mem_move(pStr + CursorPos + CharSize, pStr + CursorPos, Len - CursorPos + 1); // +1 == null term - for(int i = 0; i < CharSize; i++) - pStr[CursorPos + i] = Event.m_aText[i]; - CursorPos += CharSize; - Len += CharSize; - NumChars += CharCount; - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_STRING; - } - } +void CLineInput::SetCursorOffset(size_t Offset) +{ + m_SelectionStart = m_SelectionEnd = m_LastCompositionCursorPos = m_CursorPos = clamp(Offset, 0, m_Len); + m_WasChanged = true; +} + +void CLineInput::SetSelection(size_t Start, size_t End) +{ + if(Start > End) + std::swap(Start, End); + m_SelectionStart = clamp(Start, 0, m_Len); + m_SelectionEnd = clamp(End, 0, m_Len); + m_WasChanged = true; +} + +size_t CLineInput::OffsetFromActualToDisplay(size_t ActualOffset) const +{ + if(!IsHidden()) + return ActualOffset; + size_t DisplayOffset = 0; + size_t CurrentOffset = 0; + while(CurrentOffset < ActualOffset) + { + const size_t PrevOffset = CurrentOffset; + CurrentOffset = str_utf8_forward(m_pStr, CurrentOffset); + if(CurrentOffset == PrevOffset) + break; + DisplayOffset++; + } + return DisplayOffset; +} + +size_t CLineInput::OffsetFromDisplayToActual(size_t DisplayOffset) const +{ + if(!IsHidden()) + return DisplayOffset; + size_t ActualOffset = 0; + for(size_t i = 0; i < DisplayOffset; i++) + { + const size_t PrevOffset = ActualOffset; + ActualOffset = str_utf8_forward(m_pStr, ActualOffset); + if(ActualOffset == PrevOffset) + break; + } + return ActualOffset; +} + +bool CLineInput::ProcessInput(const IInput::CEvent &Event) +{ + // update derived attributes to handle external changes to the buffer + UpdateStrData(); + + const size_t OldCursorPos = m_CursorPos; + const bool Selecting = Input()->ShiftIsPressed(); + const size_t SelectionLength = GetSelectionLength(); + bool KeyHandled = false; + + if((Event.m_Flags & IInput::FLAG_TEXT) && !(KEY_LCTRL <= Event.m_Key && Event.m_Key <= KEY_RGUI)) + { + SetRange(Event.m_aText, m_SelectionStart, m_SelectionEnd); } if(Event.m_Flags & IInput::FLAG_PRESS) { - int Key = Event.m_Key; - if(Key == KEY_BACKSPACE) + const bool ModPressed = Input()->ModifierIsPressed(); + const bool AltPressed = Input()->AltIsPressed(); + +#ifdef CONF_PLATFORM_MACOSX + const bool MoveWord = AltPressed && !ModPressed; +#else + const bool MoveWord = ModPressed && !AltPressed; +#endif + + if(Event.m_Key == KEY_BACKSPACE) { - if((ModifyFlags & LINE_INPUT_MODIFY_DONT_DELETE) == 0 && CursorPos > 0) + if(SelectionLength && !MoveWord) { - int NewCursorPos = str_utf8_rewind(pStr, CursorPos); - int CharSize = CursorPos - NewCursorPos; - mem_move(pStr + NewCursorPos, pStr + CursorPos, Len - NewCursorPos - CharSize + 1); // +1 == null term - CursorPos = NewCursorPos; - Len -= CharSize; - if(CharSize > 0) - --NumChars; - } - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CHARACTERS_DELETE; - } - else if(Key == KEY_DELETE) - { - if((ModifyFlags & LINE_INPUT_MODIFY_DONT_DELETE) == 0 && CursorPos < Len) - { - int p = str_utf8_forward(pStr, CursorPos); - int CharSize = p - CursorPos; - mem_move(pStr + CursorPos, pStr + CursorPos + CharSize, Len - CursorPos - CharSize + 1); // +1 == null term - Len -= CharSize; - if(CharSize > 0) - --NumChars; - } - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CHARACTERS_DELETE; - } - else if(Key == KEY_LEFT) - { - if(ModifierKey == KEY_LCTRL || ModifierKey == KEY_RCTRL || ModifierKey == KEY_LGUI || ModifierKey == KEY_RGUI) - { - bool MovedCursor = false; - int OldCursorPos = CursorPos; - CursorPos = str_utf8_rewind(pStr, CursorPos); - if(OldCursorPos != CursorPos) - MovedCursor = true; - bool WasNonWordChar = IsNotAWordChar(pStr[CursorPos]); - while((!WasNonWordChar && !IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && IsNotAWordChar(pStr[CursorPos]))) - { - CursorPos = str_utf8_rewind(pStr, CursorPos); - if(CursorPos == 0) - break; - } - if(MovedCursor && ((!WasNonWordChar && IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && !IsNotAWordChar(pStr[CursorPos])))) - CursorPos = str_utf8_forward(pStr, CursorPos); - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR; + SetRange("", m_SelectionStart, m_SelectionEnd); } else { - if(CursorPos > 0) - CursorPos = str_utf8_rewind(pStr, CursorPos); - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CURSOR; + // If in MoveWord-mode, backspace will delete the word before the selection + if(SelectionLength) + m_SelectionEnd = m_CursorPos = m_SelectionStart; + if(m_CursorPos > 0) + { + size_t NewCursorPos = m_CursorPos; + MoveCursor(REWIND, MoveWord, m_pStr, m_Len, &NewCursorPos); + SetRange("", NewCursorPos, m_CursorPos); + } + m_SelectionStart = m_SelectionEnd = m_CursorPos; } } - else if(Key == KEY_RIGHT) + else if(Event.m_Key == KEY_DELETE) { - if(ModifierKey == KEY_LCTRL || ModifierKey == KEY_RCTRL || ModifierKey == KEY_LGUI || ModifierKey == KEY_RGUI) + if(SelectionLength && !MoveWord) { - bool WasNonWordChar = IsNotAWordChar(pStr[CursorPos]); - while((!WasNonWordChar && !IsNotAWordChar(pStr[CursorPos])) || (WasNonWordChar && IsNotAWordChar(pStr[CursorPos]))) - { - CursorPos = str_utf8_forward(pStr, CursorPos); - if(CursorPos >= Len) - break; - } - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR; + SetRange("", m_SelectionStart, m_SelectionEnd); } else { - if(CursorPos < Len) - CursorPos = str_utf8_forward(pStr, CursorPos); - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_CURSOR; + // If in MoveWord-mode, delete will delete the word after the selection + if(SelectionLength) + m_SelectionStart = m_CursorPos = m_SelectionEnd; + if(m_CursorPos < m_Len) + { + size_t EndCursorPos = m_CursorPos; + MoveCursor(FORWARD, MoveWord, m_pStr, m_Len, &EndCursorPos); + SetRange("", m_CursorPos, EndCursorPos); + } + m_SelectionStart = m_SelectionEnd = m_CursorPos; } } - else if(Key == KEY_HOME) + else if(Event.m_Key == KEY_LEFT) { - CursorPos = 0; - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR; + if(SelectionLength && !Selecting) + { + m_CursorPos = m_SelectionStart; + } + else if(m_CursorPos > 0) + { + MoveCursor(REWIND, MoveWord, m_pStr, m_Len, &m_CursorPos); + if(Selecting) + { + if(m_SelectionStart == OldCursorPos) // expand start first + m_SelectionStart = m_CursorPos; + else if(m_SelectionEnd == OldCursorPos) + m_SelectionEnd = m_CursorPos; + } + } + + if(!Selecting) + m_SelectionStart = m_SelectionEnd = m_CursorPos; } - else if(Key == KEY_END) + else if(Event.m_Key == KEY_RIGHT) { - CursorPos = Len; - Changes |= ELineInputChanges::LINE_INPUT_CHANGE_WARP_CURSOR; + if(SelectionLength && !Selecting) + { + m_CursorPos = m_SelectionEnd; + } + else if(m_CursorPos < m_Len) + { + MoveCursor(FORWARD, MoveWord, m_pStr, m_Len, &m_CursorPos); + if(Selecting) + { + if(m_SelectionEnd == OldCursorPos) // expand end first + m_SelectionEnd = m_CursorPos; + else if(m_SelectionStart == OldCursorPos) + m_SelectionStart = m_CursorPos; + } + } + + if(!Selecting) + m_SelectionStart = m_SelectionEnd = m_CursorPos; + } + else if(Event.m_Key == KEY_HOME) + { + if(Selecting) + { + if(SelectionLength && m_CursorPos == m_SelectionEnd) + m_SelectionEnd = m_SelectionStart; + } + else + m_SelectionEnd = 0; + m_CursorPos = 0; + m_SelectionStart = 0; + } + else if(Event.m_Key == KEY_END) + { + if(Selecting) + { + if(SelectionLength && m_CursorPos == m_SelectionStart) + m_SelectionStart = m_SelectionEnd; + } + else + m_SelectionStart = m_Len; + m_CursorPos = m_Len; + m_SelectionEnd = m_Len; + } + else if(ModPressed && !AltPressed && Event.m_Key == KEY_V) + { + const char *pClipboardText = Input()->GetClipboardText(); + if(pClipboardText) + { + std::string ClipboardText = Input()->GetClipboardText(); + if(m_pfnClipboardLineCallback) + { + // Split clipboard text into multiple lines. Send all complete lines to callback. + // The lineinput is set to the last clipboard line. + bool FirstLine = true; + size_t i, Begin = 0; + for(i = 0; i < ClipboardText.length(); i++) + { + if(ClipboardText[i] == '\n') + { + if(i == Begin) + { + Begin++; + continue; + } + std::string Line = ClipboardText.substr(Begin, i - Begin + 1); + if(FirstLine) + { + str_sanitize_cc(Line.data()); + SetRange(Line.c_str(), m_SelectionStart, m_SelectionEnd); + FirstLine = false; + Line = GetString(); + } + Begin = i + 1; + m_pfnClipboardLineCallback(Line.c_str()); + } + } + std::string Line = ClipboardText.substr(Begin, i - Begin + 1); + str_sanitize_cc(Line.data()); + if(FirstLine) + SetRange(Line.c_str(), m_SelectionStart, m_SelectionEnd); + else + Set(Line.c_str()); + } + else + { + str_sanitize_cc(ClipboardText.data()); + SetRange(ClipboardText.c_str(), m_SelectionStart, m_SelectionEnd); + } + } + KeyHandled = true; + } + else if(ModPressed && !AltPressed && (Event.m_Key == KEY_C || Event.m_Key == KEY_X) && SelectionLength) + { + char *pSelection = m_pStr + m_SelectionStart; + const char TempChar = pSelection[SelectionLength]; + pSelection[SelectionLength] = '\0'; + Input()->SetClipboardText(pSelection); + pSelection[SelectionLength] = TempChar; + if(Event.m_Key == KEY_X) + SetRange("", m_SelectionStart, m_SelectionEnd); + KeyHandled = true; + } + else if(ModPressed && !AltPressed && Event.m_Key == KEY_A) + { + m_SelectionStart = 0; + m_SelectionEnd = m_CursorPos = m_Len; } } - *pNumCharsPtr = NumChars; - *pCursorPosPtr = CursorPos; - *pStrLenPtr = Len; - - return Changes; + m_WasChanged |= OldCursorPos != m_CursorPos; + m_WasChanged |= SelectionLength != GetSelectionLength(); + return m_WasChanged || KeyHandled; } -void CLineInput::ProcessInput(IInput::CEvent e) +STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth) { - Manipulate(e, m_aStr, MAX_SIZE, MAX_CHARS, &m_Len, &m_CursorPos, &m_NumChars, 0, 0); + const char *pDisplayStr = GetDisplayedString(); + const bool HasComposition = Input()->HasComposition(); + + if(pDisplayStr[0] == '\0' && !HasComposition && m_pEmptyText != nullptr) + { + pDisplayStr = m_pEmptyText; + m_MouseSelection.m_Selecting = false; + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.75f); + } + + CTextCursor Cursor; + if(IsActive()) + { + const size_t CursorOffset = GetCursorOffset(); + const size_t DisplayCursorOffset = OffsetFromActualToDisplay(CursorOffset); + const size_t CompositionStart = CursorOffset + Input()->GetCompositionCursor(); + const size_t DisplayCompositionStart = OffsetFromActualToDisplay(CompositionStart); + const size_t CaretOffset = HasComposition ? DisplayCompositionStart : DisplayCursorOffset; + + std::string DisplayStrBuffer; + if(HasComposition) + { + const std::string DisplayStr = std::string(pDisplayStr); + DisplayStrBuffer = DisplayStr.substr(0, DisplayCursorOffset) + Input()->GetComposition() + DisplayStr.substr(DisplayCursorOffset); + pDisplayStr = DisplayStrBuffer.c_str(); + } + + const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); + const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); + + TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); + Cursor.m_LineWidth = LineWidth; + Cursor.m_ForceCursorRendering = Changed; + Cursor.m_PressMouse.x = m_MouseSelection.m_PressMouse.x; + Cursor.m_ReleaseMouse.x = m_MouseSelection.m_ReleaseMouse.x; + if(LineWidth < 0.0f) + { + // Using a Y position that's always inside the line input makes it so the selection does not reset when + // the mouse is moved outside the line input while selecting, which would otherwise be very inconvenient. + // This is a single line cursor, so we don't need the Y position to support selection over multiple lines. + Cursor.m_PressMouse.y = CursorPos.y + BoundingBox.m_H / 2.0f; + Cursor.m_ReleaseMouse.y = CursorPos.y + BoundingBox.m_H / 2.0f; + } + else + { + Cursor.m_PressMouse.y = m_MouseSelection.m_PressMouse.y; + Cursor.m_ReleaseMouse.y = m_MouseSelection.m_ReleaseMouse.y; + } + + if(HasComposition) + { + // We need to track the last composition cursor position separately, because the composition + // cursor movement does not cause an input event that would set the Changed variable. + Cursor.m_ForceCursorRendering |= m_LastCompositionCursorPos != CaretOffset; + m_LastCompositionCursorPos = CaretOffset; + const size_t DisplayCompositionEnd = DisplayCursorOffset + Input()->GetCompositionLength(); + Cursor.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_SET; + TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); + Cursor.m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_SET; + Cursor.m_SelectionHeightFactor = 0.1f; + TextRender()->UTF8OffToDecodedOff(pDisplayStr, DisplayCursorOffset, Cursor.m_SelectionStart); + TextRender()->UTF8OffToDecodedOff(pDisplayStr, DisplayCompositionEnd, Cursor.m_SelectionEnd); + TextRender()->TextSelectionColor(1.0f, 1.0f, 1.0f, 0.8f); + TextRender()->TextEx(&Cursor, pDisplayStr); + TextRender()->TextSelectionColor(TextRender()->DefaultTextSelectionColor()); + } + else if(GetSelectionLength()) + { + const size_t Start = OffsetFromActualToDisplay(GetSelectionStart()); + const size_t End = OffsetFromActualToDisplay(GetSelectionEnd()); + Cursor.m_CursorMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET; + TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); + Cursor.m_CalculateSelectionMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_SET; + TextRender()->UTF8OffToDecodedOff(pDisplayStr, Start, Cursor.m_SelectionStart); + TextRender()->UTF8OffToDecodedOff(pDisplayStr, End, Cursor.m_SelectionEnd); + TextRender()->TextEx(&Cursor, pDisplayStr); + } + else + { + Cursor.m_CursorMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET; + TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); + Cursor.m_CalculateSelectionMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; + TextRender()->TextEx(&Cursor, pDisplayStr); + } + + if(Cursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) + { + int NewCursorOffset; + TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_CursorCharacter, NewCursorOffset); + if(NewCursorOffset >= 0) + { + SetCursorOffset(OffsetFromDisplayToActual(NewCursorOffset)); + } + } + if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) + { + int NewSelectionStart, NewSelectionEnd; + TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_SelectionStart, NewSelectionStart); + TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_SelectionEnd, NewSelectionEnd); + if(NewSelectionStart >= 0 && NewSelectionEnd >= 0) + { + SetSelection(OffsetFromDisplayToActual(NewSelectionStart), OffsetFromDisplayToActual(NewSelectionEnd)); + } + } + + m_CaretPosition = Cursor.m_CursorRenderedPosition; + + STextBoundingBox CaretBoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, DisplayCursorOffset, LineWidth); + CaretBoundingBox.MoveBy(CursorPos); + SetCompositionWindowPosition(vec2(CaretBoundingBox.Right(), CaretBoundingBox.Bottom()), CaretBoundingBox.m_H); + } + else + { + const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); + const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); + TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); + Cursor.m_LineWidth = LineWidth; + TextRender()->TextEx(&Cursor, pDisplayStr); + } + + TextRender()->TextColor(TextRender()->DefaultTextColor()); + + return Cursor.BoundingBox(); +} + +void CLineInput::RenderCandidates() +{ + if(!Input()->HasComposition() || !Input()->GetCandidateCount()) + return; + + const float FontSize = 7.0f; + const float Padding = 1.0f; + const float Margin = 4.0f; + const float Height = 300.0f; + const float Width = Height * Graphics()->ScreenAspect(); + const int ScreenWidth = Graphics()->ScreenWidth(); + const int ScreenHeight = Graphics()->ScreenHeight(); + + Graphics()->MapScreen(0.0f, 0.0f, Width, Height); + + // Determine longest candidate width + float LongestCandidateWidth = 0.0f; + for(int i = 0; i < Input()->GetCandidateCount(); ++i) + LongestCandidateWidth = maximum(LongestCandidateWidth, TextRender()->TextWidth(FontSize, Input()->GetCandidate(i))); + + const float NumOffset = 8.0f; + const float RectWidth = LongestCandidateWidth + Margin + NumOffset + 2.0f * Padding; + const float RectHeight = Input()->GetCandidateCount() * (FontSize + 2.0f * Padding) + Margin; + + vec2 Position = ms_CompositionWindowPosition / vec2(ScreenWidth, ScreenHeight) * vec2(Width, Height); + Position.y += Margin; + + // Move candidate window left if needed + if(Position.x + RectWidth + Margin > Width) + Position.x -= Position.x + RectWidth + Margin - Width; + + // Move candidate window up if needed + if(Position.y + RectHeight + Margin > Height) + Position.y -= RectHeight + ms_CompositionLineHeight / ScreenHeight * Height + 2.0f * Margin; + + Graphics()->TextureClear(); + Graphics()->QuadsBegin(); + Graphics()->BlendNormal(); + + // Draw window shadow + Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.8f); + IGraphics::CQuadItem Quad = IGraphics::CQuadItem(Position.x + 0.75f, Position.y + 0.75f, RectWidth, RectHeight); + Graphics()->QuadsDrawTL(&Quad, 1); + + // Draw window background + Graphics()->SetColor(0.15f, 0.15f, 0.15f, 1.0f); + Quad = IGraphics::CQuadItem(Position.x, Position.y, RectWidth, RectHeight); + Graphics()->QuadsDrawTL(&Quad, 1); + + // Draw selected entry highlight + Graphics()->SetColor(0.1f, 0.4f, 0.8f, 1.0f); + Quad = IGraphics::CQuadItem(Position.x + Margin / 4.0f, Position.y + Margin / 2.0f + Input()->GetCandidateSelectedIndex() * (FontSize + 2.0f * Padding), RectWidth - Margin / 2.0f, FontSize + 2.0f * Padding); + Graphics()->QuadsDrawTL(&Quad, 1); + Graphics()->QuadsEnd(); + + // Draw candidates + for(int i = 0; i < Input()->GetCandidateCount(); ++i) + { + char aBuf[3]; + str_format(aBuf, sizeof(aBuf), "%d.", (i + 1) % 10); + + const float PosX = Position.x + Margin / 2.0f + Padding; + const float PosY = Position.y + Margin / 2.0f + i * (FontSize + 2.0f * Padding) + Padding; + TextRender()->TextColor(0.6f, 0.6f, 0.6f, 1.0f); + TextRender()->Text(PosX, PosY, FontSize, aBuf); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + TextRender()->Text(PosX + NumOffset, PosY, FontSize, Input()->GetCandidate(i)); + } +} + +void CLineInput::SetCompositionWindowPosition(vec2 Anchor, float LineHeight) +{ + float ScreenX0, ScreenY0, ScreenX1, ScreenY1; + const int ScreenWidth = Graphics()->ScreenWidth(); + const int ScreenHeight = Graphics()->ScreenHeight(); + Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); + + const vec2 ScreenScale = vec2(ScreenWidth / (ScreenX1 - ScreenX0), ScreenHeight / (ScreenY1 - ScreenY0)); + ms_CompositionWindowPosition = Anchor * ScreenScale; + ms_CompositionLineHeight = LineHeight * ScreenScale.y; + Input()->SetCompositionWindowPosition(ms_CompositionWindowPosition.x, ms_CompositionWindowPosition.y - ms_CompositionLineHeight, ms_CompositionLineHeight); +} + +void CLineInput::Activate(EInputPriority Priority) +{ + if(IsActive()) + return; + if(ms_ActiveInputPriority != EInputPriority::NONE && Priority < ms_ActiveInputPriority) + return; // do not replace a higher priority input + if(ms_pActiveInput) + ms_pActiveInput->OnDeactivate(); + ms_pActiveInput = this; + ms_pActiveInput->OnActivate(); + ms_ActiveInputPriority = Priority; +} + +void CLineInput::Deactivate() +{ + if(!IsActive()) + return; + ms_pActiveInput->OnDeactivate(); + ms_pActiveInput = nullptr; + ms_ActiveInputPriority = EInputPriority::NONE; +} + +void CLineInput::OnActivate() +{ + Input()->StartTextInput(); +} + +void CLineInput::OnDeactivate() +{ + Input()->StopTextInput(); + m_MouseSelection.m_Selecting = false; +} + +void CLineInputNumber::SetInteger(int Number, int Base) +{ + char aBuf[32]; + switch(Base) + { + case 10: + str_format(aBuf, sizeof(aBuf), "%d", Number); + break; + case 16: + str_format(aBuf, sizeof(aBuf), "%06X", Number); + break; + default: + dbg_assert(false, "Base unsupported"); + return; + } + if(str_comp(aBuf, GetDisplayedString()) != 0) + Set(aBuf); +} + +int CLineInputNumber::GetInteger(int Base) const +{ + return str_toint_base(GetString(), Base); +} + +void CLineInputNumber::SetFloat(float Number) +{ + char aBuf[32]; + str_format(aBuf, sizeof(aBuf), "%.3f", Number); + if(str_comp(aBuf, GetDisplayedString()) != 0) + Set(aBuf); +} + +float CLineInputNumber::GetFloat() const +{ + return str_tofloat(GetString()); } diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h index 3c241ffc6..824fd1c18 100644 --- a/src/game/client/lineinput.h +++ b/src/game/client/lineinput.h @@ -3,55 +3,221 @@ #ifndef GAME_CLIENT_LINEINPUT_H #define GAME_CLIENT_LINEINPUT_H +#include + +#include +#include #include +#include + +#include + +enum class EInputPriority +{ + NONE = 0, + UI, + CHAT, + CONSOLE, +}; // line input helper class CLineInput { - enum +public: + struct SMouseSelection { - MAX_SIZE = 512, - MAX_CHARS = MAX_SIZE / 2, + bool m_Selecting = false; + vec2 m_PressMouse = vec2(0.0f, 0.0f); + vec2 m_ReleaseMouse = vec2(0.0f, 0.0f); + vec2 m_Offset = vec2(0.0f, 0.0f); }; - char m_aStr[MAX_SIZE]; - int m_Len; - int m_CursorPos; - int m_NumChars; - char m_aDisplayStr[MAX_SIZE + IInput::INPUT_TEXT_SIZE + 2]; - int m_FakeLen; - int m_FakeCursorPos; + typedef std::function FClipboardLineCallback; + +private: + static IClient *ms_pClient; + static IGraphics *ms_pGraphics; + static IInput *ms_pInput; + static ITextRender *ms_pTextRender; + + static IClient *Client() { return ms_pClient; } + static IGraphics *Graphics() { return ms_pGraphics; } + static IInput *Input() { return ms_pInput; } + static ITextRender *TextRender() { return ms_pTextRender; } + + static CLineInput *ms_pActiveInput; + static EInputPriority ms_ActiveInputPriority; + + static vec2 ms_CompositionWindowPosition; + static float ms_CompositionLineHeight; + + static char ms_aStars[128]; + + char *m_pStr = nullptr; // explicitly set to nullptr outside of contructor, so SetBuffer works in this case + size_t m_MaxSize; + size_t m_MaxChars; + size_t m_Len; + size_t m_NumChars; + + size_t m_CursorPos; + size_t m_SelectionStart; + size_t m_SelectionEnd; + + float m_ScrollOffset; + float m_ScrollOffsetChange; + vec2 m_CaretPosition; + SMouseSelection m_MouseSelection; + size_t m_LastCompositionCursorPos; + + bool m_Hidden; + const char *m_pEmptyText; + FClipboardLineCallback m_pfnClipboardLineCallback; + bool m_WasChanged; + + char m_ClearButtonId; + + void UpdateStrData(); + enum EMoveDirection + { + FORWARD, + REWIND + }; + static void MoveCursor(EMoveDirection Direction, bool MoveWord, const char *pStr, size_t MaxSize, size_t *pCursorPos); + static void SetCompositionWindowPosition(vec2 Anchor, float LineHeight); + + void OnActivate(); + void OnDeactivate(); public: - enum ELineInputChanges + static void Init(IClient *pClient, IGraphics *pGraphics, IInput *pInput, ITextRender *pTextRender) { - // string was changed - LINE_INPUT_CHANGE_STRING = 1 << 0, - // characters were removed from the string - LINE_INPUT_CHANGE_CHARACTERS_DELETE = 1 << 1, - // cursor was changed or tried to change(e.g. pressing right at the last char in the string) - LINE_INPUT_CHANGE_CURSOR = 1 << 2, - LINE_INPUT_CHANGE_WARP_CURSOR = 1 << 3, - }; - enum ELineInputModifyFlags - { - // don't delete characters - LINE_INPUT_MODIFY_DONT_DELETE = 1 << 0, - }; - static int32_t Manipulate(IInput::CEvent e, char *pStr, int StrMaxSize, int StrMaxChars, int *pStrLenPtr, int *pCursorPosPtr, int *pNumCharsPtr, int32_t ModifyFlags, int ModifierKey); + ms_pClient = pClient; + ms_pGraphics = pGraphics; + ms_pInput = pInput; + ms_pTextRender = pTextRender; + } + static void RenderCandidates(); + + static CLineInput *GetActiveInput() { return ms_pActiveInput; } + + CLineInput() + { + SetBuffer(nullptr, 0, 0); + } + + CLineInput(char *pStr, size_t MaxSize) + { + SetBuffer(pStr, MaxSize); + } + + CLineInput(char *pStr, size_t MaxSize, size_t MaxChars) + { + SetBuffer(pStr, MaxSize, MaxChars); + } + + void SetBuffer(char *pStr, size_t MaxSize) { SetBuffer(pStr, MaxSize, MaxSize); } + void SetBuffer(char *pStr, size_t MaxSize, size_t MaxChars); - CLineInput(); void Clear(); - void ProcessInput(IInput::CEvent e); - void Editing(const char *pString, int Cursor); void Set(const char *pString); - void SetRange(const char *pString, int Begin, int End); - void Insert(const char *pString, int Begin); + void SetRange(const char *pString, size_t Begin, size_t End); + void Insert(const char *pString, size_t Begin); void Append(const char *pString); - const char *GetString(bool Editing = false) const { return Editing ? m_aDisplayStr : m_aStr; } - int GetLength(bool Editing = false) const { return Editing ? m_FakeLen : m_Len; } - int GetCursorOffset(bool Editing = false) const { return Editing ? m_FakeCursorPos : m_CursorPos; } - void SetCursorOffset(int Offset) { m_CursorPos = Offset > m_Len ? m_Len : Offset < 0 ? 0 : Offset; } + + const char *GetString() const { return m_pStr; } + const char *GetDisplayedString(); + size_t GetMaxSize() const { return m_MaxSize; } + size_t GetMaxChars() const { return m_MaxChars; } + size_t GetLength() const { return m_Len; } + size_t GetNumChars() const { return m_NumChars; } + bool IsEmpty() const { return GetLength() == 0; } + + size_t GetCursorOffset() const { return m_CursorPos; } + void SetCursorOffset(size_t Offset); + size_t GetSelectionStart() const { return m_SelectionStart; } + size_t GetSelectionEnd() const { return m_SelectionEnd; } + size_t GetSelectionLength() const { return m_SelectionEnd - m_SelectionStart; } + bool HasSelection() const { return GetSelectionLength() > 0; } + void SetSelection(size_t Start, size_t End); + void SelectNothing() { SetSelection(GetCursorOffset(), GetCursorOffset()); } + void SelectAll() { SetSelection(0, GetLength()); } + + size_t OffsetFromActualToDisplay(size_t ActualOffset) const; + size_t OffsetFromDisplayToActual(size_t DisplayOffset) const; + + // used either for vertical or horizontal scrolling + float GetScrollOffset() const { return m_ScrollOffset; } + void SetScrollOffset(float ScrollOffset) { m_ScrollOffset = ScrollOffset; } + float GetScrollOffsetChange() const { return m_ScrollOffsetChange; } + void SetScrollOffsetChange(float ScrollOffsetChange) { m_ScrollOffsetChange = ScrollOffsetChange; } + + vec2 GetCaretPosition() const { return m_CaretPosition; } // only updated while the input is active + + bool IsHidden() const { return m_Hidden; } + void SetHidden(bool Hidden) { m_Hidden = Hidden; } + + const char *GetEmptyText() const { return m_pEmptyText; } + void SetEmptyText(const char *pText) { m_pEmptyText = pText; } + + void SetClipboardLineCallback(FClipboardLineCallback pfnClipboardLineCallback) { m_pfnClipboardLineCallback = pfnClipboardLineCallback; } + + bool ProcessInput(const IInput::CEvent &Event); + bool WasChanged() + { + const bool Changed = m_WasChanged; + m_WasChanged = false; + return Changed; + } + + STextBoundingBox Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth); + SMouseSelection *GetMouseSelection() { return &m_MouseSelection; } + + const void *GetClearButtonId() const { return &m_ClearButtonId; } + + bool IsActive() const { return GetActiveInput() == this; } + void Activate(EInputPriority Priority); + void Deactivate(); +}; + +template +class CLineInputBuffered : public CLineInput +{ + char m_aBuffer[MaxSize]; + +public: + CLineInputBuffered() : + CLineInput() + { + m_aBuffer[0] = '\0'; + SetBuffer(m_aBuffer, MaxSize, MaxChars); + } +}; + +class CLineInputNumber : public CLineInputBuffered<32> +{ +public: + CLineInputNumber() : + CLineInputBuffered() + { + } + + CLineInputNumber(int Number) : + CLineInputBuffered() + { + SetInteger(Number); + } + + CLineInputNumber(float Number) : + CLineInputBuffered() + { + SetFloat(Number); + } + + void SetInteger(int Number, int Base = 10); + int GetInteger(int Base = 10) const; + + void SetFloat(float Number); + float GetFloat() const; }; #endif diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index bf1c129b1..e2836c542 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -81,7 +80,10 @@ void CUIElement::SUIElementRect::Draw(const CUIRect *pRect, ColorRGBA Color, int const CLinearScrollbarScale CUI::ms_LinearScrollbarScale; const CLogarithmicScrollbarScale CUI::ms_LogarithmicScrollbarScale(25); -float CUI::ms_FontmodHeight = 0.8f; +const CDarkButtonColorFunction CUI::ms_DarkButtonColorFunction; +const CLightButtonColorFunction CUI::ms_LightButtonColorFunction; +const CScrollBarColorFunction CUI::ms_ScrollBarColorFunction; +const float CUI::ms_FontmodHeight = 0.8f; CUI *CUIElementBase::s_pUI = nullptr; @@ -96,17 +98,11 @@ void CUI::Init(IKernel *pKernel) m_pGraphics = pKernel->RequestInterface(); m_pInput = pKernel->RequestInterface(); m_pTextRender = pKernel->RequestInterface(); - InitInputs(m_pInput->GetEventsRaw(), m_pInput->GetEventCountRaw()); CUIRect::Init(m_pGraphics); + CLineInput::Init(m_pClient, m_pGraphics, m_pInput, m_pTextRender); CUIElementBase::Init(this); } -void CUI::InitInputs(IInput::CEvent *pInputEventsArray, size_t *pInputEventCount) -{ - m_pInputEventsArray = pInputEventsArray; - m_pInputEventCount = pInputEventCount; -} - CUI::CUI() { m_Enabled = true; @@ -206,7 +202,14 @@ void CUI::Update(float MouseX, float MouseY, float MouseWorldX, float MouseWorld if(m_pActiveItem) m_pHotItem = m_pActiveItem; m_pBecomingHotItem = 0; - if(!Enabled()) + + if(Enabled()) + { + CLineInput *pActiveInput = CLineInput::GetActiveInput(); + if(pActiveInput && m_pLastActiveItem && pActiveInput != m_pLastActiveItem) + pActiveInput->Deactivate(); + } + else { m_pHotItem = nullptr; m_pActiveItem = nullptr; @@ -254,6 +257,10 @@ bool CUI::OnInput(const IInput::CEvent &Event) if(!Enabled()) return false; + CLineInput *pActiveInput = CLineInput::GetActiveInput(); + if(pActiveInput && pActiveInput->ProcessInput(Event)) + return true; + if(Event.m_Flags & IInput::FLAG_PRESS) { unsigned LastHotkeysPressed = m_HotkeysPressed; @@ -488,14 +495,21 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float * return 1; } -void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, float ScrollSpeed) +void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp, float ScrollSpeed) { + // reset scrolling if it's not necessary anymore + if(TotalSize < ViewPortSize) + { + *pScrollOffsetChange = -*pScrollOffset; + } + // instant scrolling if distance too long - if(absolute(*pScrollOffsetChange) > ViewPortSize) + if(absolute(*pScrollOffsetChange) > 2.0f * ViewPortSize) { *pScrollOffset += *pScrollOffsetChange; *pScrollOffsetChange = 0.0f; } + // smooth scrolling if(*pScrollOffsetChange) { @@ -503,21 +517,54 @@ void CUI::DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, *pScrollOffset += Delta; *pScrollOffsetChange -= Delta; } + // clamp to first item if(*pScrollOffset < 0.0f) { - *pScrollOffset = 0.0f; - *pScrollOffsetChange = 0.0f; + if(SmoothClamp && *pScrollOffset < -0.1f) + { + *pScrollOffsetChange = -*pScrollOffset; + } + else + { + *pScrollOffset = 0.0f; + *pScrollOffsetChange = 0.0f; + } } + // clamp to last item if(TotalSize > ViewPortSize && *pScrollOffset > TotalSize - ViewPortSize) { - *pScrollOffset = TotalSize - ViewPortSize; - *pScrollOffsetChange = 0.0f; + if(SmoothClamp && *pScrollOffset - (TotalSize - ViewPortSize) > 0.1f) + { + *pScrollOffsetChange = (TotalSize - ViewPortSize) - *pScrollOffset; + } + else + { + *pScrollOffset = TotalSize - ViewPortSize; + *pScrollOffsetChange = 0.0f; + } } } -static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align) +static vec2 CalcFontSizeAndBoundingBox(ITextRender *pTextRender, const char *pText, int Flags, float &Size, float MaxWidth, const SLabelProperties &LabelProps) +{ + float TextHeight = 0.0f; + float MaxTextWidth = LabelProps.m_MaxWidth != -1 ? LabelProps.m_MaxWidth : MaxWidth; + float TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); + while(TextWidth > MaxTextWidth + 0.001f) + { + if(!LabelProps.m_EnableWidthCheck) + break; + if(Size < 4.0f) + break; + Size -= 1.0f; + TextWidth = pTextRender->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); + } + return vec2(TextWidth, TextHeight); +} + +vec2 CUI::CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align) { vec2 Cursor(pRect->x, pRect->y); @@ -546,62 +593,20 @@ static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align) void CUI::DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps) { - int Flags = LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; - float TextHeight = 0.0f; - float MaxTextWidth = LabelProps.m_MaxWidth != -1 ? LabelProps.m_MaxWidth : pRect->w; - float TextWidth = TextRender()->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); - while(TextWidth > MaxTextWidth + 0.001f) - { - if(!LabelProps.m_EnableWidthCheck) - break; - if(Size < 4.0f) - break; - Size -= 1.0f; - TextWidth = TextRender()->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); - } - - const vec2 CursorPos = CalcAlignedCursorPos(pRect, vec2(TextWidth, TextHeight), Align); + const int Flags = LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; + const vec2 TextBounds = CalcFontSizeAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); + const vec2 CursorPos = CalcAlignedCursorPos(pRect, TextBounds, Align); CTextCursor Cursor; TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, Size, TEXTFLAG_RENDER | Flags); Cursor.m_LineWidth = (float)LabelProps.m_MaxWidth; - if(LabelProps.m_pSelCursor) - { - Cursor.m_CursorMode = LabelProps.m_pSelCursor->m_CursorMode; - Cursor.m_CursorCharacter = LabelProps.m_pSelCursor->m_CursorCharacter; - Cursor.m_CalculateSelectionMode = LabelProps.m_pSelCursor->m_CalculateSelectionMode; - Cursor.m_PressMouseX = LabelProps.m_pSelCursor->m_PressMouseX; - Cursor.m_PressMouseY = LabelProps.m_pSelCursor->m_PressMouseY; - Cursor.m_ReleaseMouseX = LabelProps.m_pSelCursor->m_ReleaseMouseX; - Cursor.m_ReleaseMouseY = LabelProps.m_pSelCursor->m_ReleaseMouseY; - - Cursor.m_SelectionStart = LabelProps.m_pSelCursor->m_SelectionStart; - Cursor.m_SelectionEnd = LabelProps.m_pSelCursor->m_SelectionEnd; - } - TextRender()->TextEx(&Cursor, pText, -1); - - if(LabelProps.m_pSelCursor) - { - *LabelProps.m_pSelCursor = Cursor; - } } void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps, int StrLen, const CTextCursor *pReadCursor) { - int Flags = pReadCursor ? (pReadCursor->m_Flags & ~TEXTFLAG_RENDER) : LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; - float TextHeight = 0.0f; - float MaxTextWidth = LabelProps.m_MaxWidth != -1 ? LabelProps.m_MaxWidth : pRect->w; - float TextWidth = TextRender()->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); - while(TextWidth > MaxTextWidth + 0.001f) - { - if(!LabelProps.m_EnableWidthCheck) - break; - if(Size < 4.0f) - break; - Size -= 1.0f; - TextWidth = TextRender()->TextWidth(Size, pText, -1, LabelProps.m_MaxWidth, Flags, &TextHeight); - } + const int Flags = pReadCursor ? (pReadCursor->m_Flags & ~TEXTFLAG_RENDER) : LabelProps.m_StopAtEnd ? TEXTFLAG_STOP_AT_END : 0; + const vec2 TextBounds = CalcFontSizeAndBoundingBox(TextRender(), pText, Flags, Size, pRect->w, LabelProps); CTextCursor Cursor; if(pReadCursor) @@ -610,7 +615,7 @@ void CUI::DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, cons } else { - const vec2 CursorPos = CalcAlignedCursorPos(pRect, vec2(TextWidth, TextHeight), Align); + const vec2 CursorPos = CalcAlignedCursorPos(pRect, TextBounds, Align); TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, Size, TEXTFLAG_RENDER | Flags); } Cursor.m_LineWidth = LabelProps.m_MaxWidth; @@ -683,431 +688,121 @@ void CUI::DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRe } } -bool CUI::DoEditBox(const void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden, int Corners, const SUIExEditBoxProperties &Properties) +bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners) { const bool Inside = MouseHovered(pRect); - bool ReturnValue = false; - bool UpdateOffset = false; + const bool Active = LastActiveItem() == pLineInput; + const bool Changed = pLineInput->WasChanged(); - auto &&SetHasSelection = [&](bool HasSelection) { - m_HasSelection = HasSelection; - m_pSelItem = m_HasSelection ? pID : nullptr; - }; + const float VSpacing = 2.0f; + CUIRect Textbox; + pRect->VMargin(VSpacing, &Textbox); - auto &&SelectAllText = [&]() { - m_CurSelStart = 0; - int StrLen = str_length(pStr); - TextRender()->UTF8OffToDecodedOff(pStr, StrLen, m_CurSelEnd); - SetHasSelection(true); - m_CurCursor = StrLen; - }; - - if(LastActiveItem() == pID) + bool JustGotActive = false; + if(CheckActiveItem(pLineInput)) { - if(m_HasSelection && m_pSelItem != pID) + if(MouseButton(0)) { - SetHasSelection(false); - } - - m_CurCursor = minimum(str_length(pStr), m_CurCursor); - - const bool IsShiftPressed = Input()->ShiftIsPressed(); - const bool IsModPressed = Input()->ModifierIsPressed(); - - if(Enabled() && !IsShiftPressed && IsModPressed && Input()->KeyPress(KEY_V)) - { - const char *pText = Input()->GetClipboardText(); - if(pText) + if(pLineInput->IsActive() && (Input()->HasComposition() || Input()->GetCandidateCount())) { - int OffsetL = clamp(m_CurCursor, 0, str_length(pStr)); - int OffsetR = OffsetL; - - if(m_HasSelection) - { - int SelLeft = minimum(m_CurSelStart, m_CurSelEnd); - int SelRight = maximum(m_CurSelStart, m_CurSelEnd); - int UTF8SelLeft = -1; - int UTF8SelRight = -1; - if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft, SelRight, UTF8SelLeft, UTF8SelRight)) - { - OffsetL = UTF8SelLeft; - OffsetR = UTF8SelRight; - SetHasSelection(false); - } - } - - std::string NewStr(pStr, OffsetL); - - int WrittenChars = 0; - - const char *pIt = pText; - while(*pIt) - { - const char *pTmp = pIt; - int Character = str_utf8_decode(&pTmp); - if(Character == -1 || Character == 0) - break; - - if(Character == '\r' || Character == '\n') - { - NewStr.append(1, ' '); - ++WrittenChars; - } - else - { - NewStr.append(pIt, (std::intptr_t)(pTmp - pIt)); - WrittenChars += (int)(std::intptr_t)(pTmp - pIt); - } - - pIt = pTmp; - } - - NewStr.append(pStr + OffsetR); - - str_copy(pStr, NewStr.c_str(), StrSize); - - m_CurCursor = OffsetL + WrittenChars; - ReturnValue = true; + // Clear IME composition/candidates on mouse press + Input()->StopTextInput(); + Input()->StartTextInput(); } } - - if(Enabled() && !IsShiftPressed && IsModPressed && (Input()->KeyPress(KEY_C) || Input()->KeyPress(KEY_X))) + else { - if(m_HasSelection) - { - int SelLeft = minimum(m_CurSelStart, m_CurSelEnd); - int SelRight = maximum(m_CurSelStart, m_CurSelEnd); - int UTF8SelLeft = -1; - int UTF8SelRight = -1; - if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft, SelRight, UTF8SelLeft, UTF8SelRight)) - { - std::string NewStr(&pStr[UTF8SelLeft], UTF8SelRight - UTF8SelLeft); - Input()->SetClipboardText(NewStr.c_str()); - if(Input()->KeyPress(KEY_X)) - { - NewStr = std::string(pStr, UTF8SelLeft) + std::string(pStr + UTF8SelRight); - str_copy(pStr, NewStr.c_str(), StrSize); - SetHasSelection(false); - if(m_CurCursor > UTF8SelLeft) - m_CurCursor = maximum(0, m_CurCursor - (UTF8SelRight - UTF8SelLeft)); - else - m_CurCursor = UTF8SelLeft; - } - } - } - else - Input()->SetClipboardText(pStr); + SetActiveItem(0); } - - if(Properties.m_SelectText || (Enabled() && !IsShiftPressed && IsModPressed && Input()->KeyPress(KEY_A))) + } + else if(HotItem() == pLineInput) + { + if(MouseButton(0)) { - SelectAllText(); - } - - if(Enabled() && !IsShiftPressed && IsModPressed && Input()->KeyPress(KEY_U)) - { - pStr[0] = '\0'; - m_CurCursor = 0; - SetHasSelection(false); - ReturnValue = true; - } - - for(size_t i = 0; i < *m_pInputEventCount; i++) - { - int LastCursor = m_CurCursor; - int Len, NumChars; - str_utf8_stats(pStr, StrSize, StrSize, &Len, &NumChars); - int32_t ManipulateChanges = CLineInput::Manipulate(m_pInputEventsArray[i], pStr, StrSize, StrSize, &Len, &m_CurCursor, &NumChars, m_HasSelection ? CLineInput::LINE_INPUT_MODIFY_DONT_DELETE : 0, IsModPressed ? KEY_LCTRL : 0); - ReturnValue |= (ManipulateChanges & (CLineInput::LINE_INPUT_CHANGE_STRING | CLineInput::LINE_INPUT_CHANGE_CHARACTERS_DELETE)) != 0; - - // if cursor changed, reset selection - if(ManipulateChanges != 0) - { - if(m_HasSelection && (ManipulateChanges & (CLineInput::LINE_INPUT_CHANGE_STRING | CLineInput::LINE_INPUT_CHANGE_CHARACTERS_DELETE)) != 0) - { - int OffsetL = 0; - int OffsetR = 0; - - bool IsReverseSel = m_CurSelStart > m_CurSelEnd; - - int ExtraNew = 0; - int ExtraOld = 0; - // selection correction from added chars - if(IsReverseSel) - { - TextRender()->UTF8OffToDecodedOff(pStr, m_CurCursor, ExtraNew); - TextRender()->UTF8OffToDecodedOff(pStr, LastCursor, ExtraOld); - } - - int SelLeft = minimum(m_CurSelStart, m_CurSelEnd); - int SelRight = maximum(m_CurSelStart, m_CurSelEnd); - int UTF8SelLeft = -1; - int UTF8SelRight = -1; - if(TextRender()->SelectionToUTF8OffSets(pStr, SelLeft + (ExtraNew - ExtraOld), SelRight + (ExtraNew - ExtraOld), UTF8SelLeft, UTF8SelRight)) - { - OffsetL = UTF8SelLeft; - OffsetR = UTF8SelRight; - SetHasSelection(false); - } - - std::string NewStr(pStr, OffsetL); - - NewStr.append(pStr + OffsetR); - - str_copy(pStr, NewStr.c_str(), StrSize); - - if(!IsReverseSel) - m_CurCursor = clamp(m_CurCursor - (UTF8SelRight - UTF8SelLeft), 0, NewStr.length()); - } - - if(IsShiftPressed && (ManipulateChanges & CLineInput::LINE_INPUT_CHANGE_STRING) == 0) - { - int CursorPosDecoded = -1; - int LastCursorPosDecoded = -1; - - if(!m_HasSelection) - { - m_CurSelStart = -1; - m_CurSelEnd = -1; - } - - if(TextRender()->UTF8OffToDecodedOff(pStr, m_CurCursor, CursorPosDecoded)) - { - if(TextRender()->UTF8OffToDecodedOff(pStr, LastCursor, LastCursorPosDecoded)) - { - if(!m_HasSelection) - { - m_CurSelStart = LastCursorPosDecoded; - m_CurSelEnd = LastCursorPosDecoded; - } - m_CurSelEnd += (CursorPosDecoded - LastCursorPosDecoded); - } - } - if(m_CurSelStart == m_CurSelEnd) - SetHasSelection(false); - else - SetHasSelection(true); - } - else - { - if(m_HasSelection && (ManipulateChanges & CLineInput::LINE_INPUT_CHANGE_CURSOR) != 0) - { - if(m_CurSelStart < m_CurSelEnd) - { - if(m_CurCursor >= LastCursor) - m_CurCursor = LastCursor; - else - TextRender()->DecodedOffToUTF8Off(pStr, m_CurSelStart, m_CurCursor); - } - else - { - if(m_CurCursor <= LastCursor) - m_CurCursor = LastCursor; - else - TextRender()->DecodedOffToUTF8Off(pStr, m_CurSelStart, m_CurCursor); - } - } - SetHasSelection(false); - } - } + if(!Active) + JustGotActive = true; + SetActiveItem(pLineInput); } } + if(Inside) + SetHotItem(pLineInput); + + if(Enabled() && Active && !JustGotActive) + pLineInput->Activate(EInputPriority::UI); + else + pLineInput->Deactivate(); + + float ScrollOffset = pLineInput->GetScrollOffset(); + float ScrollOffsetChange = pLineInput->GetScrollOffsetChange(); + + // Update mouse selection information + CLineInput::SMouseSelection *pMouseSelection = pLineInput->GetMouseSelection(); if(Inside) { - SetHotItem(pID); - } - - CUIRect Textbox = *pRect; - Textbox.Draw(ColorRGBA(1, 1, 1, 0.5f), Corners, 3.0f); - Textbox.Margin(2.0f, &Textbox); - - const char *pDisplayStr = pStr; - char aStars[128]; - - if(Hidden) - { - unsigned s = str_length(pDisplayStr); - if(s >= sizeof(aStars)) - s = sizeof(aStars) - 1; - for(unsigned int i = 0; i < s; ++i) - aStars[i] = '*'; - aStars[s] = 0; - pDisplayStr = aStars; - } - - char aDispEditingText[128 + IInput::INPUT_TEXT_SIZE + 2] = {0}; - int DispCursorPos = m_CurCursor; - if(LastActiveItem() == pID && Input()->GetIMEEditingTextLength() > -1) - { - int EditingTextCursor = Input()->GetEditingCursor(); - str_copy(aDispEditingText, pDisplayStr); - char aEditingText[IInput::INPUT_TEXT_SIZE + 2]; - if(Hidden) + if(!pMouseSelection->m_Selecting && MouseButtonClicked(0)) { - // Do not show editing text in password field - str_copy(aEditingText, "[*]"); - EditingTextCursor = 1; - } - else - { - str_format(aEditingText, sizeof(aEditingText), "[%s]", Input()->GetIMEEditingText()); - } - int NewTextLen = str_length(aEditingText); - int CharsLeft = (int)sizeof(aDispEditingText) - str_length(aDispEditingText) - 1; - int FillCharLen = minimum(NewTextLen, CharsLeft); - for(int i = str_length(aDispEditingText) - 1; i >= m_CurCursor; i--) - aDispEditingText[i + FillCharLen] = aDispEditingText[i]; - for(int i = 0; i < FillCharLen; i++) - aDispEditingText[m_CurCursor + i] = aEditingText[i]; - DispCursorPos = m_CurCursor + EditingTextCursor + 1; - pDisplayStr = aDispEditingText; - UpdateOffset = true; - } - - bool IsEmptyText = false; - if(pDisplayStr[0] == '\0') - { - pDisplayStr = Properties.m_pEmptyText; - IsEmptyText = true; - TextRender()->TextColor(1, 1, 1, 0.75f); - } - - DispCursorPos = minimum(DispCursorPos, str_length(pDisplayStr)); - - bool JustGotActive = false; - if(CheckActiveItem(pID)) - { - if(!MouseButton(0)) - { - SetActiveItem(nullptr); + pMouseSelection->m_Selecting = true; + pMouseSelection->m_PressMouse = MousePos(); + pMouseSelection->m_Offset.x = ScrollOffset; } } - else if(HotItem() == pID) + if(pMouseSelection->m_Selecting) { - if(MouseButton(0)) + pMouseSelection->m_ReleaseMouse = MousePos(); + if(MouseButtonReleased(0)) { - if(LastActiveItem() != pID) - JustGotActive = true; - SetActiveItem(pID); + pMouseSelection->m_Selecting = false; } } + if(ScrollOffset != pMouseSelection->m_Offset.x) + { + // When the scroll offset is changed, update the position that the mouse was pressed at, + // so the existing text selection still stays mostly the same. + // TODO: The selection may change by one character temporarily, due to different character widths. + // Needs text render adjustment: keep selection start based on character. + pMouseSelection->m_PressMouse.x -= ScrollOffset - pMouseSelection->m_Offset.x; + pMouseSelection->m_Offset.x = ScrollOffset; + } - // check if the text has to be moved - if(LastActiveItem() == pID && !JustGotActive && (UpdateOffset || *m_pInputEventCount)) - { - float w = TextRender()->CaretPosition(FontSize, pDisplayStr, DispCursorPos).x; - if(w - *pOffset > Textbox.w) - { - // move to the left - float wt = TextRender()->TextWidth(FontSize, pDisplayStr); - do - { - *pOffset += minimum(wt - *pOffset - Textbox.w, Textbox.w / 3); - } while(w - *pOffset > Textbox.w + 0.0001f); - } - else if(w - *pOffset < 0.0f) - { - // move to the right - do - { - *pOffset = maximum(0.0f, *pOffset - Textbox.w / 3); - } while(w - *pOffset < -0.0001f); - } - } + // Render + pRect->Draw(ms_LightButtonColorFunction.GetColor(Active, Inside), Corners, 3.0f); ClipEnable(pRect); - Textbox.x -= *pOffset; - - CTextCursor SelCursor; - TextRender()->SetCursor(&SelCursor, 0, 0, 16, 0); - - bool HasMouseSel = false; - if(LastActiveItem() == pID) - { - if(!m_MouseIsPress && MouseButtonClicked(0)) - { - m_MouseIsPress = true; - m_MousePressX = MouseX(); - m_MousePressY = MouseY(); - } - } - - if(m_MouseIsPress) - { - m_MouseCurX = MouseX(); - m_MouseCurY = MouseY(); - } - HasMouseSel = m_MouseIsPress && !IsEmptyText; - if(m_MouseIsPress && MouseButtonReleased(0)) - { - m_MouseIsPress = false; - } - - if(LastActiveItem() == pID) - { - int CursorPos = -1; - TextRender()->UTF8OffToDecodedOff(pDisplayStr, DispCursorPos, CursorPos); - - SelCursor.m_CursorMode = HasMouseSel ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET; - SelCursor.m_CursorCharacter = CursorPos; - SelCursor.m_CalculateSelectionMode = HasMouseSel ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : (m_HasSelection ? TEXT_CURSOR_SELECTION_MODE_SET : TEXT_CURSOR_SELECTION_MODE_NONE); - SelCursor.m_PressMouseX = m_MousePressX; - SelCursor.m_PressMouseY = m_MousePressY; - SelCursor.m_ReleaseMouseX = m_MouseCurX; - SelCursor.m_ReleaseMouseY = m_MouseCurY; - SelCursor.m_SelectionStart = m_CurSelStart; - SelCursor.m_SelectionEnd = m_CurSelEnd; - } - - SLabelProperties Props; - Props.m_pSelCursor = &SelCursor; - Props.m_EnableWidthCheck = IsEmptyText; - DoLabel(&Textbox, pDisplayStr, FontSize, TEXTALIGN_ML, Props); - - if(LastActiveItem() == pID) - { - if(SelCursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) - { - m_CurSelStart = SelCursor.m_SelectionStart; - m_CurSelEnd = SelCursor.m_SelectionEnd; - SetHasSelection(m_CurSelStart != m_CurSelEnd); - } - if(SelCursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) - { - TextRender()->DecodedOffToUTF8Off(pDisplayStr, SelCursor.m_CursorCharacter, DispCursorPos); - m_CurCursor = DispCursorPos; - } - } - - TextRender()->TextColor(1, 1, 1, 1); - - // set the ime cursor - if(LastActiveItem() == pID && !JustGotActive) - { - float w = TextRender()->CaretPosition(FontSize, pDisplayStr, DispCursorPos).x; - Textbox.x += w; - Input()->SetEditingPosition(Textbox.x, Textbox.y + FontSize); - } - + Textbox.x -= ScrollOffset; + const STextBoundingBox BoundingBox = pLineInput->Render(&Textbox, FontSize, TEXTALIGN_ML, Changed, -1.0f); ClipDisable(); - return ReturnValue; + // Scroll left or right if necessary + if(Active && !JustGotActive && (Changed || Input()->HasComposition())) + { + const float CaretPositionX = pLineInput->GetCaretPosition().x - Textbox.x - ScrollOffset - ScrollOffsetChange; + if(CaretPositionX > Textbox.w) + ScrollOffsetChange += CaretPositionX - Textbox.w; + else if(CaretPositionX < 0.0f) + ScrollOffsetChange += CaretPositionX; + } + + DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, Textbox.w, BoundingBox.m_W, true); + + pLineInput->SetScrollOffset(ScrollOffset); + pLineInput->SetScrollOffsetChange(ScrollOffsetChange); + + return Changed; } -bool CUI::DoClearableEditBox(const void *pID, const void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden, int Corners, const SUIExEditBoxProperties &Properties) +bool CUI::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners) { - CUIRect EditBox; - CUIRect ClearButton; + CUIRect EditBox, ClearButton; pRect->VSplitRight(pRect->h, &EditBox, &ClearButton); - bool ReturnValue = DoEditBox(pID, &EditBox, pStr, StrSize, FontSize, pOffset, Hidden, Corners & ~IGraphics::CORNER_R, Properties); + bool ReturnValue = DoEditBox(pLineInput, &EditBox, FontSize, Corners & ~IGraphics::CORNER_R); - ClearButton.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f * ButtonColorMul(pClearID)), Corners & ~IGraphics::CORNER_L, 3.0f); + ClearButton.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.33f * ButtonColorMul(pLineInput->GetClearButtonId())), Corners & ~IGraphics::CORNER_L, 3.0f); DoLabel(&ClearButton, "×", ClearButton.h * CUI::ms_FontmodHeight * 0.8f, TEXTALIGN_MC); - if(DoButtonLogic(pClearID, 0, &ClearButton)) + if(DoButtonLogic(pLineInput->GetClearButtonId(), 0, &ClearButton)) { - pStr[0] = 0; - SetActiveItem(pID); + pLineInput->Clear(); + SetActiveItem(pLineInput); ReturnValue = true; } @@ -1188,16 +883,7 @@ float CUI::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current) // render Rail.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, Rail.w / 2.0f); - - float ColorSlider; - if(CheckActiveItem(pID)) - ColorSlider = 0.9f; - else if(HotItem() == pID) - ColorSlider = 1.0f; - else - ColorSlider = 0.8f; - - Handle.Draw(ColorRGBA(ColorSlider, ColorSlider, ColorSlider, 1.0f), IGraphics::CORNER_ALL, Handle.w / 2.0f); + Handle.Draw(ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pID), HotItem() == pID), IGraphics::CORNER_ALL, Handle.w / 2.0f); return ReturnValue; } @@ -1279,16 +965,7 @@ float CUI::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current, co else { Rail.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, Rail.h / 2.0f); - - float ColorSlider; - if(CheckActiveItem(pID)) - ColorSlider = 0.9f; - else if(HotItem() == pID) - ColorSlider = 1.0f; - else - ColorSlider = 0.8f; - - Handle.Draw(ColorRGBA(ColorSlider, ColorSlider, ColorSlider, 1.0f), IGraphics::CORNER_ALL, Handle.h / 2.0f); + Handle.Draw(ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pID), HotItem() == pID), IGraphics::CORNER_ALL, Handle.h / 2.0f); } return ReturnValue; diff --git a/src/game/client/ui.h b/src/game/client/ui.h index eabd2e601..bb0ab6bce 100644 --- a/src/game/client/ui.h +++ b/src/game/client/ui.h @@ -3,6 +3,7 @@ #ifndef GAME_CLIENT_UI_H #define GAME_CLIENT_UI_H +#include "lineinput.h" #include "ui_rect.h" #include @@ -83,10 +84,46 @@ public: } }; -struct SUIExEditBoxProperties +class IButtonColorFunction { - bool m_SelectText = false; - const char *m_pEmptyText = ""; +public: + virtual ColorRGBA GetColor(bool Active, bool Hovered) const = 0; +}; +class CDarkButtonColorFunction : public IButtonColorFunction +{ +public: + ColorRGBA GetColor(bool Active, bool Hovered) const override + { + if(Active) + return ColorRGBA(0.15f, 0.15f, 0.15f, 0.25f); + else if(Hovered) + return ColorRGBA(0.5f, 0.5f, 0.5f, 0.25f); + return ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f); + } +}; +class CLightButtonColorFunction : public IButtonColorFunction +{ +public: + ColorRGBA GetColor(bool Active, bool Hovered) const override + { + if(Active) + return ColorRGBA(1.0f, 1.0f, 1.0f, 0.4f); + else if(Hovered) + return ColorRGBA(1.0f, 1.0f, 1.0f, 0.6f); + return ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + } +}; +class CScrollBarColorFunction : public IButtonColorFunction +{ +public: + ColorRGBA GetColor(bool Active, bool Hovered) const override + { + if(Active) + return ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f); + else if(Hovered) + return ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + return ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f); + } }; class CUI; @@ -154,7 +191,6 @@ struct SLabelProperties { float m_MaxWidth = -1; bool m_StopAtEnd = false; - class CTextCursor *m_pSelCursor = nullptr; bool m_EnableWidthCheck = true; }; @@ -242,23 +278,8 @@ private: unsigned m_LastMouseButtons; bool m_MouseSlow = false; - IInput::CEvent *m_pInputEventsArray; - size_t *m_pInputEventCount; unsigned m_HotkeysPressed = 0; - bool m_MouseIsPress = false; - bool m_HasSelection = false; - - int m_MousePressX = 0; - int m_MousePressY = 0; - int m_MouseCurX = 0; - int m_MouseCurY = 0; - int m_CurSelStart = 0; - int m_CurSelEnd = 0; - const void *m_pSelItem = nullptr; - - int m_CurCursor = 0; - CUIRect m_Screen; std::vector m_vClips; @@ -293,11 +314,13 @@ private: public: static const CLinearScrollbarScale ms_LinearScrollbarScale; static const CLogarithmicScrollbarScale ms_LogarithmicScrollbarScale; + static const CDarkButtonColorFunction ms_DarkButtonColorFunction; + static const CLightButtonColorFunction ms_LightButtonColorFunction; + static const CScrollBarColorFunction ms_ScrollBarColorFunction; - static float ms_FontmodHeight; + static const float ms_FontmodHeight; void Init(IKernel *pKernel); - void InitInputs(IInput::CEvent *pInputEventsArray, size_t *pInputEventCount); IClient *Client() const { return m_pClient; } IGraphics *Graphics() const { return m_pGraphics; } IInput *Input() const { return m_pInput; } @@ -339,6 +362,7 @@ public: float MouseDeltaY() const { return m_MouseDeltaY; } float MouseX() const { return m_MouseX; } float MouseY() const { return m_MouseY; } + vec2 MousePos() const { return vec2(m_MouseX, m_MouseY); } float MouseWorldX() const { return m_MouseWorldX; } float MouseWorldY() const { return m_MouseWorldY; } int MouseButton(int Index) const { return (m_MouseButtons >> Index) & 1; } @@ -408,15 +432,16 @@ public: int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect); int DoDraggableButtonLogic(const void *pID, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted); int DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *pY); - void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, float ScrollSpeed = 10.0f); + void DoSmoothScrollLogic(float *pScrollOffset, float *pScrollOffsetChange, float ViewPortSize, float TotalSize, bool SmoothClamp = false, float ScrollSpeed = 10.0f); + static vec2 CalcAlignedCursorPos(const CUIRect *pRect, vec2 TextSize, int Align); void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}); void DoLabel(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {}, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); void DoLabelStreamed(CUIElement::SUIElementRect &RectEl, const CUIRect *pRect, const char *pText, float Size, int Align, float MaxWidth = -1, bool StopAtEnd = false, int StrLen = -1, const CTextCursor *pReadCursor = nullptr); - bool DoEditBox(const void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden = false, int Corners = IGraphics::CORNER_ALL, const SUIExEditBoxProperties &Properties = {}); - bool DoClearableEditBox(const void *pID, const void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden = false, int Corners = IGraphics::CORNER_ALL, const SUIExEditBoxProperties &Properties = {}); + bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); + bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL); int DoButton_PopupMenu(CButtonContainer *pButtonContainer, const char *pText, const CUIRect *pRect, int Align); diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4683dcc80..45b4311e6 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -312,20 +313,20 @@ void CEditor::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, v OTHER *********************************************************/ -bool CEditor::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden, int Corners, const char *pToolTip) +bool CEditor::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip) { - if(UI()->LastActiveItem() == pID) + if(UI()->LastActiveItem() == pLineInput) m_EditBoxActive = 2; - UpdateTooltip(pID, pRect, pToolTip); - return UI()->DoEditBox(pID, pRect, pStr, StrSize, FontSize, pOffset, Hidden, Corners); + UpdateTooltip(pLineInput, pRect, pToolTip); + return UI()->DoEditBox(pLineInput, pRect, FontSize, Corners); } -bool CEditor::DoClearableEditBox(void *pID, void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden, int Corners, const char *pToolTip) +bool CEditor::DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners, const char *pToolTip) { - if(UI()->LastActiveItem() == pID) + if(UI()->LastActiveItem() == pLineInput) m_EditBoxActive = 2; - UpdateTooltip(pID, pRect, pToolTip); - return UI()->DoClearableEditBox(pID, pClearID, pRect, pStr, StrSize, FontSize, pOffset, Hidden, Corners); + UpdateTooltip(pLineInput, pRect, pToolTip); + return UI()->DoClearableEditBox(pLineInput, pRect, FontSize, Corners); } ColorRGBA CEditor::GetButtonColor(const void *pID, int Checked) @@ -573,20 +574,18 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in { // logic static float s_Value; - static char s_aNumStr[64]; + static CLineInputNumber s_NumberInput; static bool s_TextMode = false; - static void *s_pLastTextpID = pID; + static void *s_pLastTextID = pID; const bool Inside = UI()->MouseInside(pRect); + const int Base = IsHex ? 16 : 10; if(UI()->MouseButton(1) && UI()->HotItem() == pID) { - s_pLastTextpID = pID; + s_pLastTextID = pID; s_TextMode = true; m_LockMouse = false; - if(IsHex) - str_format(s_aNumStr, sizeof(s_aNumStr), "%06X", Current); - else - str_format(s_aNumStr, sizeof(s_aNumStr), "%d", Current); + s_NumberInput.SetInteger(Current, Base); } if(UI()->CheckActiveItem(pID)) @@ -599,22 +598,18 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in } } - if(s_TextMode && s_pLastTextpID == pID) + if(s_TextMode && s_pLastTextID == pID) { m_pTooltip = "Type your number"; - static float s_NumberBoxID = 0; - DoEditBox(&s_NumberBoxID, pRect, s_aNumStr, sizeof(s_aNumStr), 10.0f, &s_NumberBoxID, false, Corners); + DoEditBox(&s_NumberInput, pRect, 10.0f); - UI()->SetActiveItem(&s_NumberBoxID); + UI()->SetActiveItem(&s_NumberInput); if(Input()->KeyIsPressed(KEY_RETURN) || Input()->KeyIsPressed(KEY_KP_ENTER) || ((UI()->MouseButton(1) || UI()->MouseButton(0)) && !Inside)) { - if(IsHex) - Current = clamp(str_toint_base(s_aNumStr, 16), Min, Max); - else - Current = clamp(str_toint(s_aNumStr), Min, Max); + Current = clamp(s_NumberInput.GetInteger(Base), Min, Max); m_LockMouse = false; UI()->SetActiveItem(nullptr); s_TextMode = false; @@ -689,6 +684,9 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in UI()->DoLabel(pRect, aBuf, 10, TEXTALIGN_MC); } + if(!s_TextMode) + s_NumberInput.Clear(); + return Current; } @@ -4746,15 +4744,14 @@ void CEditor::RenderFileDialog() if(m_FileDialogStorageType == IStorage::TYPE_SAVE) { UI()->DoLabel(&FileBoxLabel, "Filename:", 10.0f, TEXTALIGN_ML); - static float s_FileBoxID = 0; - if(DoEditBox(&s_FileBoxID, &FileBox, m_aFileDialogFileName, sizeof(m_aFileDialogFileName), 10.0f, &s_FileBoxID)) + if(DoEditBox(&m_FileDialogFileNameInput, &FileBox, 10.0f)) { // remove '/' and '\' - for(int i = 0; m_aFileDialogFileName[i]; ++i) + for(int i = 0; m_FileDialogFileNameInput.GetString()[i]; ++i) { - if(m_aFileDialogFileName[i] == '/' || m_aFileDialogFileName[i] == '\\') + if(m_FileDialogFileNameInput.GetString()[i] == '/' || m_FileDialogFileNameInput.GetString()[i] == '\\') { - str_copy(&m_aFileDialogFileName[i], &m_aFileDialogFileName[i + 1], (int)(sizeof(m_aFileDialogFileName)) - i); + m_FileDialogFileNameInput.SetRange(m_FileDialogFileNameInput.GetString() + i + 1, i, m_FileDialogFileNameInput.GetLength()); --i; } } @@ -4763,7 +4760,7 @@ void CEditor::RenderFileDialog() // find first valid entry, if it exists for(size_t i = 0; i < m_vpFilteredFileList.size(); i++) { - if(str_comp_nocase(m_vpFilteredFileList[i]->m_aName, m_aFileDialogFileName) == 0) + if(str_comp_nocase(m_vpFilteredFileList[i]->m_aName, m_FileDialogFileNameInput.GetString()) == 0) { m_FilesSelectedIndex = i; str_copy(m_aFilesSelectedName, m_vpFilteredFileList[i]->m_aName); @@ -4775,48 +4772,28 @@ void CEditor::RenderFileDialog() } if(m_FileDialogOpening) - UI()->SetActiveItem(&s_FileBoxID); + UI()->SetActiveItem(&m_FileDialogFileNameInput); } else { // render search bar - FileBox.VSplitRight(250, &FileBox, nullptr); - CUIRect ClearBox; - FileBox.VSplitRight(15, &FileBox, &ClearBox); - UI()->DoLabel(&FileBoxLabel, "Search:", 10.0f, TEXTALIGN_ML); - static float s_SearchBoxID = 0; - bool SearchUpdated = DoEditBox(&s_SearchBoxID, &FileBox, m_aFileDialogFilterString, sizeof(m_aFileDialogFilterString), 10.0f, &s_SearchBoxID, false, IGraphics::CORNER_L); if(m_FileDialogOpening) - UI()->SetActiveItem(&s_SearchBoxID); - - // clear search button - { - static int s_ClearButton = 0; - ClearBox.Draw(ColorRGBA(1, 1, 1, 0.33f * UI()->ButtonColorMul(&s_ClearButton)), IGraphics::CORNER_R, 3.0f); - UI()->DoLabel(&ClearBox, "×", 10.0f, TEXTALIGN_MC); - if(UI()->DoButtonLogic(&s_ClearButton, 0, &ClearBox)) - { - SearchUpdated = true; - m_aFileDialogFilterString[0] = 0; - UI()->SetActiveItem(&s_SearchBoxID); - } - } - - if(SearchUpdated) + UI()->SetActiveItem(&m_FileDialogFilterInput); + if(UI()->DoClearableEditBox(&m_FileDialogFilterInput, &FileBox, 10.0f)) { RefreshFilteredFileList(); if(m_vpFilteredFileList.empty()) { m_FilesSelectedIndex = -1; } - else if(m_FilesSelectedIndex == -1 || (m_aFileDialogFilterString[0] && !str_find_nocase(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName, m_aFileDialogFilterString))) + else if(m_FilesSelectedIndex == -1 || (!m_FileDialogFilterInput.IsEmpty() && !str_find_nocase(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName, m_FileDialogFilterInput.GetString()))) { // we need to refresh selection m_FilesSelectedIndex = -1; for(size_t i = 0; i < m_vpFilteredFileList.size(); i++) { - if(str_find_nocase(m_vpFilteredFileList[i]->m_aName, m_aFileDialogFilterString)) + if(str_find_nocase(m_vpFilteredFileList[i]->m_aName, m_FileDialogFilterInput.GetString())) { m_FilesSelectedIndex = i; break; @@ -4833,9 +4810,9 @@ void CEditor::RenderFileDialog() else m_aFilesSelectedName[0] = '\0'; if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); + m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); else - m_aFileDialogFileName[0] = '\0'; + m_FileDialogFileNameInput.Clear(); s_ListBox.ScrollToSelected(); m_PreviewImageState = PREVIEWIMAGE_UNLOADED; } @@ -4945,9 +4922,9 @@ void CEditor::RenderFileDialog() m_FilesSelectedIndex = NewSelection; str_copy(m_aFilesSelectedName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aName); if(!m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); + m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); else - m_aFileDialogFileName[0] = '\0'; + m_FileDialogFileNameInput.Clear(); m_PreviewImageState = PREVIEWIMAGE_UNLOADED; } @@ -4968,7 +4945,7 @@ void CEditor::RenderFileDialog() { if(IsDir) // folder { - m_aFileDialogFilterString[0] = '\0'; + m_FileDialogFilterInput.Clear(); if(str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0) // parent folder { if(fs_parent_dir(m_pFileDialogPath)) @@ -4991,13 +4968,13 @@ void CEditor::RenderFileDialog() FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType); if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) - str_copy(m_aFileDialogFileName, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, sizeof(m_aFileDialogFileName)); + m_FileDialogFileNameInput.Set(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); else - m_aFileDialogFileName[0] = 0; + m_FileDialogFileNameInput.Clear(); } else // file { - str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_aFileDialogFileName); + str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_FileDialogFileNameInput.GetString()); if(!str_comp(m_pFileDialogButtonText, "Save")) { IOHANDLE File = Storage()->OpenFile(m_aFileSaveName, IOFLAG_READ, IStorage::TYPE_SAVE); @@ -5077,12 +5054,12 @@ void CEditor::RenderFileDialog() ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, nullptr)) { - m_aFileDialogNewFolderName[0] = 0; + m_FileDialogNewFolderNameInput.Clear(); static SPopupMenuId s_PopupNewFolderId; constexpr float PopupWidth = 400.0f; constexpr float PopupHeight = 110.0f; UI()->DoPopupMenu(&s_PopupNewFolderId, Width / 2.0f - PopupWidth / 2.0f, Height / 2.0f - PopupHeight / 2.0f, PopupWidth, PopupHeight, this, PopupNewFolder); - UI()->SetActiveItem(nullptr); + UI()->SetActiveItem(&m_FileDialogNewFolderNameInput); } } } @@ -5092,7 +5069,7 @@ void CEditor::RefreshFilteredFileList() m_vpFilteredFileList.clear(); for(const CFilelistItem &Item : m_vCompleteFileList) { - if(!m_aFileDialogFilterString[0] || str_find_nocase(Item.m_aName, m_aFileDialogFilterString)) + if(m_FileDialogFilterInput.IsEmpty() || str_find_nocase(Item.m_aName, m_FileDialogFilterInput.GetString())) { m_vpFilteredFileList.push_back(&Item); } @@ -5158,8 +5135,8 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle m_pFileDialogButtonText = pButtonText; m_pfnFileDialogFunc = pfnFunc; m_pFileDialogUser = pUser; - m_aFileDialogFileName[0] = 0; - m_aFileDialogFilterString[0] = 0; + m_FileDialogFileNameInput.Clear(); + m_FileDialogFilterInput.Clear(); m_aFileDialogCurrentFolder[0] = 0; m_aFileDialogCurrentLink[0] = 0; m_pFileDialogPath = m_aFileDialogCurrentFolder; @@ -5168,7 +5145,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle m_FileDialogOpening = true; if(pDefaultName) - str_copy(m_aFileDialogFileName, pDefaultName, sizeof(m_aFileDialogFileName)); + m_FileDialogFileNameInput.Set(pDefaultName); if(pBasePath) str_copy(m_aFileDialogCurrentFolder, pBasePath, sizeof(m_aFileDialogCurrentFolder)); @@ -5478,8 +5455,9 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) ToolBar.VSplitLeft(3.0f, nullptr, &ToolBar); ToolBar.VSplitLeft(ToolBar.w > ToolBar.h * 40 ? 80.0f : 60.0f, &Button, &ToolBar); - static float s_NameBox = 0; - if(DoEditBox(&s_NameBox, &Button, pEnvelope->m_aName, sizeof(pEnvelope->m_aName), 10.0f, &s_NameBox, false, IGraphics::CORNER_ALL, "The name of the selected envelope")) + static CLineInput s_NameInput; + s_NameInput.SetBuffer(pEnvelope->m_aName, sizeof(pEnvelope->m_aName)); + if(DoEditBox(&s_NameInput, &Button, 10.0f, IGraphics::CORNER_ALL, "The name of the selected envelope")) { m_Map.m_Modified = true; } @@ -5680,17 +5658,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) // keep track of last Env static void *s_pID = nullptr; - // chars for textinput - static char s_aStrCurTime[32] = "0.000"; - static char s_aStrCurValue[32] = "0.000"; + static CLineInputNumber s_CurValueInput; + static CLineInputNumber s_CurTimeInput; if(CurrentEnvelopeSwitched) { s_pID = nullptr; // update displayed text - str_copy(s_aStrCurTime, "0.000"); - str_copy(s_aStrCurValue, "0.000"); + s_CurValueInput.SetFloat(0.0f); + s_CurTimeInput.SetFloat(0.0f); } { @@ -5783,8 +5760,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) s_pID = nullptr; // update displayed text - str_copy(s_aStrCurTime, "0.000"); - str_copy(s_aStrCurValue, "0.000"); + s_CurValueInput.SetFloat(0.0f); + s_CurTimeInput.SetFloat(0.0f); } pEnvelope->m_vPoints.erase(pEnvelope->m_vPoints.begin() + i); @@ -5801,7 +5778,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) { if(i != 0) { - pEnvelope->m_vPoints[i].m_Time = str_tofloat(s_aStrCurTime) * 1000.0f; + pEnvelope->m_vPoints[i].m_Time = s_CurTimeInput.GetFloat() * 1000.0f; if(pEnvelope->m_vPoints[i].m_Time < pEnvelope->m_vPoints[i - 1].m_Time) pEnvelope->m_vPoints[i].m_Time = pEnvelope->m_vPoints[i - 1].m_Time + 1; @@ -5811,9 +5788,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) else pEnvelope->m_vPoints[i].m_Time = 0.0f; - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "%.3f", pEnvelope->m_vPoints[i].m_Time / 1000.0f); + s_CurTimeInput.SetFloat(pEnvelope->m_vPoints[i].m_Time / 1000.0f); - pEnvelope->m_vPoints[i].m_aValues[c] = f2fx(str_tofloat(s_aStrCurValue)); + pEnvelope->m_vPoints[i].m_aValues[c] = f2fx(s_CurValueInput.GetFloat()); + s_CurValueInput.SetFloat(fx2f(pEnvelope->m_vPoints[i].m_aValues[c])); } if(UI()->CheckActiveItem(pID)) @@ -5822,8 +5800,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) CurrentValue = pEnvelope->m_vPoints[i].m_aValues[c]; // update displayed text - str_format(s_aStrCurTime, sizeof(s_aStrCurTime), "%.3f", CurrentTime / 1000.0f); - str_format(s_aStrCurValue, sizeof(s_aStrCurValue), "%.3f", fx2f(CurrentValue)); + s_CurValueInput.SetFloat(fx2f(CurrentValue)); + s_CurTimeInput.SetFloat(CurrentTime / 1000.0f); } if(m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == (int)i) @@ -5855,10 +5833,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View) UI()->DoLabel(&Label2, "Time (in s):", 10.0f, TEXTALIGN_MR); } - static float s_ValNumber = 0; - DoEditBox(&s_ValNumber, &ToolBar1, s_aStrCurValue, sizeof(s_aStrCurValue), 10.0f, &s_ValNumber, false, IGraphics::CORNER_ALL, "The value of the selected envelope point"); - static float s_TimeNumber = 0; - DoEditBox(&s_TimeNumber, &ToolBar2, s_aStrCurTime, sizeof(s_aStrCurTime), 10.0f, &s_TimeNumber, false, IGraphics::CORNER_ALL, "The time of the selected envelope point"); + DoEditBox(&s_CurValueInput, &ToolBar1, 10.0f, IGraphics::CORNER_ALL, "The value of the selected envelope point"); + DoEditBox(&s_CurTimeInput, &ToolBar2, 10.0f, IGraphics::CORNER_ALL, "The time of the selected envelope point"); } } } @@ -5881,22 +5857,20 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd Button.VSplitLeft(70.0f, nullptr, &Button); Button.VSplitLeft(180.0f, &Button, nullptr); - static int s_ClearButton = 0; - DoClearableEditBox(&m_CommandBox, &s_ClearButton, &Button, m_aSettingsCommand, sizeof(m_aSettingsCommand), 12.0f, &m_CommandBox, false, IGraphics::CORNER_ALL); - if(!ShowServerSettingsEditorLast) // Just activated - UI()->SetActiveItem(&m_CommandBox); + UI()->SetActiveItem(&m_SettingsCommandInput); + DoClearableEditBox(&m_SettingsCommandInput, &Button, 12.0f); // buttons ToolBar.VSplitRight(50.0f, &ToolBar, &Button); static int s_AddButton = 0; - if(DoButton_Editor(&s_AddButton, "Add", 0, &Button, 0, "Add a command to command list.") || ((Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_KP_ENTER)) && UI()->LastActiveItem() == &m_CommandBox && m_Dialog == DIALOG_NONE)) + if(DoButton_Editor(&s_AddButton, "Add", 0, &Button, 0, "Add a command to command list.") || ((Input()->KeyPress(KEY_RETURN) || Input()->KeyPress(KEY_KP_ENTER)) && UI()->LastActiveItem() == &m_SettingsCommandInput && m_Dialog == DIALOG_NONE)) { - if(m_aSettingsCommand[0] != 0 && str_find(m_aSettingsCommand, " ")) + if(!m_SettingsCommandInput.IsEmpty() && str_find(m_SettingsCommandInput.GetString(), " ")) { bool Found = false; for(const auto &Setting : m_Map.m_vSettings) - if(!str_comp(Setting.m_aCommand, m_aSettingsCommand)) + if(!str_comp(Setting.m_aCommand, m_SettingsCommandInput.GetString())) { Found = true; break; @@ -5905,12 +5879,12 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd if(!Found) { CEditorMap::CSetting Setting; - str_copy(Setting.m_aCommand, m_aSettingsCommand, sizeof(Setting.m_aCommand)); + str_copy(Setting.m_aCommand, m_SettingsCommandInput.GetString()); m_Map.m_vSettings.push_back(Setting); s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1; } } - UI()->SetActiveItem(&m_CommandBox); + UI()->SetActiveItem(&m_SettingsCommandInput); } if(!m_Map.m_vSettings.empty() && s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex < m_Map.m_vSettings.size()) @@ -5918,14 +5892,14 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd ToolBar.VSplitRight(50.0f, &ToolBar, &Button); Button.VSplitRight(5.0f, &Button, nullptr); static int s_ModButton = 0; - if(DoButton_Editor(&s_ModButton, "Mod", 0, &Button, 0, "Modify a command from the command list.") || (Input()->KeyPress(KEY_M) && UI()->LastActiveItem() != &m_CommandBox && m_Dialog == DIALOG_NONE)) + if(DoButton_Editor(&s_ModButton, "Mod", 0, &Button, 0, "Modify a command from the command list.") || (Input()->KeyPress(KEY_M) && UI()->LastActiveItem() != &m_SettingsCommandInput && m_Dialog == DIALOG_NONE)) { - if(str_comp(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_aSettingsCommand) != 0 && m_aSettingsCommand[0] != 0 && str_find(m_aSettingsCommand, " ")) + if(!m_SettingsCommandInput.IsEmpty() && str_comp(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_SettingsCommandInput.GetString()) != 0 && str_find(m_SettingsCommandInput.GetString(), " ")) { bool Found = false; int i; for(i = 0; i < (int)m_Map.m_vSettings.size(); i++) - if(i != s_CommandSelectedIndex && !str_comp(m_Map.m_vSettings[i].m_aCommand, m_aSettingsCommand)) + if(i != s_CommandSelectedIndex && !str_comp(m_Map.m_vSettings[i].m_aCommand, m_SettingsCommandInput.GetString())) { Found = true; break; @@ -5937,23 +5911,23 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd } else { - str_copy(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_aSettingsCommand, sizeof(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand)); + str_copy(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, m_SettingsCommandInput.GetString()); } } - UI()->SetActiveItem(&m_CommandBox); + UI()->SetActiveItem(&m_SettingsCommandInput); } ToolBar.VSplitRight(50.0f, &ToolBar, &Button); Button.VSplitRight(5.0f, &Button, nullptr); static int s_DelButton = 0; - if(DoButton_Editor(&s_DelButton, "Del", 0, &Button, 0, "Delete a command from the command list.") || (Input()->KeyPress(KEY_DELETE) && UI()->LastActiveItem() != &m_CommandBox && m_Dialog == DIALOG_NONE)) + if(DoButton_Editor(&s_DelButton, "Del", 0, &Button, 0, "Delete a command from the command list.") || (Input()->KeyPress(KEY_DELETE) && UI()->LastActiveItem() != &m_SettingsCommandInput && m_Dialog == DIALOG_NONE)) { m_Map.m_vSettings.erase(m_Map.m_vSettings.begin() + s_CommandSelectedIndex); if(s_CommandSelectedIndex >= (int)m_Map.m_vSettings.size()) s_CommandSelectedIndex = m_Map.m_vSettings.size() - 1; if(s_CommandSelectedIndex >= 0) - str_copy(m_aSettingsCommand, m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand, sizeof(m_aSettingsCommand)); - UI()->SetActiveItem(&m_CommandBox); + m_SettingsCommandInput.Set(m_Map.m_vSettings[s_CommandSelectedIndex].m_aCommand); + UI()->SetActiveItem(&m_SettingsCommandInput); } ToolBar.VSplitRight(25.0f, &ToolBar, &Button); @@ -6003,8 +5977,8 @@ void CEditor::RenderServerSettingsEditor(CUIRect View, bool ShowServerSettingsEd if(DoButton_MenuItem(&m_Map.m_vSettings[i], m_Map.m_vSettings[i].m_aCommand, s_CommandSelectedIndex >= 0 && (size_t)s_CommandSelectedIndex == i, &Button, 0, nullptr)) { s_CommandSelectedIndex = i; - str_copy(m_aSettingsCommand, m_Map.m_vSettings[i].m_aCommand); - UI()->SetActiveItem(&m_CommandBox); + m_SettingsCommandInput.Set(m_Map.m_vSettings[i].m_aCommand); + UI()->SetActiveItem(&m_SettingsCommandInput); } } } @@ -6074,7 +6048,7 @@ void CEditor::Render() RenderBackground(View, m_CheckerTexture, 32.0f, 1.0f); CUIRect MenuBar, CModeBar, ToolBar, StatusBar, ExtraEditor, ToolBox; - m_ShowPicker = Input()->KeyIsPressed(KEY_SPACE) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0 && UI()->LastActiveItem() != &m_CommandBox && m_vSelectedLayers.size() == 1; + m_ShowPicker = Input()->KeyIsPressed(KEY_SPACE) && m_Dialog == DIALOG_NONE && m_EditBoxActive == 0 && UI()->LastActiveItem() != &m_SettingsCommandInput && m_vSelectedLayers.size() == 1; if(m_GuiActive) { @@ -6856,9 +6830,6 @@ void CEditor::OnUpdate() Reset(); } - for(size_t i = 0; i < Input()->NumEvents(); i++) - UI()->OnInput(Input()->GetEvent(i)); - // handle cursor movement { static float s_MouseX = 0.0f; @@ -6941,6 +6912,9 @@ void CEditor::OnRender() ms_pUiGotContext = nullptr; UI()->StartCheck(); + for(size_t i = 0; i < Input()->NumEvents(); i++) + UI()->OnInput(Input()->GetEvent(i)); + UI()->Update(m_MouseX, m_MouseY, m_MouseWorldX, m_MouseWorldY); Render(); @@ -6959,6 +6933,8 @@ void CEditor::OnRender() UI()->FinishCheck(); UI()->ClearHotkeys(); Input()->Clear(); + + CLineInput::RenderCandidates(); } void CEditor::LoadCurrentMap() diff --git a/src/game/editor/editor.h b/src/game/editor/editor.h index f8ac0c5aa..ad255bfee 100644 --- a/src/game/editor/editor.h +++ b/src/game/editor/editor.h @@ -802,11 +802,9 @@ public: m_pFileDialogTitle = nullptr; m_pFileDialogButtonText = nullptr; m_pFileDialogUser = nullptr; - m_aFileDialogFileName[0] = '\0'; m_aFileDialogCurrentFolder[0] = '\0'; m_aFileDialogCurrentLink[0] = '\0'; m_aFilesSelectedName[0] = '\0'; - m_aFileDialogFilterString[0] = '\0'; m_pFileDialogPath = m_aFileDialogCurrentFolder; m_FileDialogOpening = false; m_FilesSelectedIndex = -1; @@ -859,9 +857,6 @@ public: m_BackgroundTexture.Invalidate(); m_CursorTexture.Invalidate(); - m_CommandBox = 0.0f; - m_aSettingsCommand[0] = 0; - ms_pUiGotContext = nullptr; // DDRace @@ -975,15 +970,15 @@ public: const char *m_pFileDialogButtonText; bool (*m_pfnFileDialogFunc)(const char *pFileName, int StorageType, void *pUser); void *m_pFileDialogUser; - char m_aFileDialogFileName[IO_MAX_PATH_LENGTH]; + CLineInputBuffered m_FileDialogFileNameInput; char m_aFileDialogCurrentFolder[IO_MAX_PATH_LENGTH]; char m_aFileDialogCurrentLink[IO_MAX_PATH_LENGTH]; char m_aFilesSelectedName[IO_MAX_PATH_LENGTH]; - char m_aFileDialogFilterString[IO_MAX_PATH_LENGTH]; + CLineInputBuffered m_FileDialogFilterInput; char *m_pFileDialogPath; int m_FileDialogFileType; int m_FilesSelectedIndex; - char m_aFileDialogNewFolderName[IO_MAX_PATH_LENGTH]; + CLineInputBuffered m_FileDialogNewFolderNameInput; IGraphics::CTextureHandle m_FilePreviewImage; EPreviewImageState m_PreviewImageState; CImageInfo m_FilePreviewImageInfo; @@ -1146,8 +1141,7 @@ public: static void EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser); - float m_CommandBox; - char m_aSettingsCommand[256]; + CLineInputBuffered<256> m_SettingsCommandInput; void PlaceBorderTiles(); @@ -1171,8 +1165,8 @@ public: int DoButton_DraggableEx(const void *pID, const char *pText, int Checked, const CUIRect *pRect, bool *pClicked, bool *pAbrupted, int Flags, const char *pToolTip = nullptr, int Corners = IGraphics::CORNER_ALL, float FontSize = 10.0f); - bool DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden = false, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr); - bool DoClearableEditBox(void *pID, void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *pOffset, bool Hidden = false, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr); + bool DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr); + bool DoClearableEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize, int Corners = IGraphics::CORNER_ALL, const char *pToolTip = nullptr); void RenderBackground(CUIRect View, IGraphics::CTextureHandle Texture, float Size, float Brightness); diff --git a/src/game/editor/popups.cpp b/src/game/editor/popups.cpp index ab28506ac..b855a4a4b 100644 --- a/src/game/editor/popups.cpp +++ b/src/game/editor/popups.cpp @@ -368,8 +368,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, View.HSplitBottom(12.0f, &View, &Button); pEditor->UI()->DoLabel(&Button, "Name:", 10.0f, TEXTALIGN_ML); Button.VSplitLeft(40.0f, nullptr, &Button); - static float s_Name = 0; - if(pEditor->DoEditBox(&s_Name, &Button, pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName), 10.0f, &s_Name)) + static CLineInput s_NameInput; + s_NameInput.SetBuffer(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName)); + if(pEditor->DoEditBox(&s_NameInput, &Button, 10.0f)) pEditor->m_Map.m_Modified = true; } @@ -538,8 +539,9 @@ CUI::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, View.HSplitBottom(12.0f, &View, &Label); Label.VSplitLeft(40.0f, &Label, &EditBox); pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, TEXTALIGN_ML); - static float s_Name = 0; - if(pEditor->DoEditBox(&s_Name, &EditBox, pCurrentLayer->m_aName, sizeof(pCurrentLayer->m_aName), 10.0f, &s_Name)) + static CLineInput s_NameInput; + s_NameInput.SetBuffer(pCurrentLayer->m_aName, sizeof(pCurrentLayer->m_aName)); + if(pEditor->DoEditBox(&s_NameInput, &EditBox, 10.0f)) pEditor->m_Map.m_Modified = true; } @@ -1316,8 +1318,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupNewFolder(void *pContext, CUIRect Vi pEditor->UI()->DoLabel(&Label, "Name:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(50.0f, nullptr, &Button); Button.HMargin(2.0f, &Button); - static float s_FolderBox = 0; - pEditor->DoEditBox(&s_FolderBox, &Button, pEditor->m_aFileDialogNewFolderName, sizeof(pEditor->m_aFileDialogNewFolderName), 12.0f, &s_FolderBox); + pEditor->DoEditBox(&pEditor->m_FileDialogNewFolderNameInput, &Button, 12.0f); // button bar ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar); @@ -1330,10 +1331,10 @@ CUI::EPopupMenuFunctionResult CEditor::PopupNewFolder(void *pContext, CUIRect Vi if(pEditor->DoButton_Editor(&s_CreateButton, "Create", 0, &Button, 0, nullptr) || (Active && pEditor->UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))) { // create the folder - if(pEditor->m_aFileDialogNewFolderName[0]) + if(!pEditor->m_FileDialogNewFolderNameInput.IsEmpty()) { char aBuf[IO_MAX_PATH_LENGTH]; - str_format(aBuf, sizeof(aBuf), "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_aFileDialogNewFolderName); + str_format(aBuf, sizeof(aBuf), "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_FileDialogNewFolderNameInput.GetString()); if(pEditor->Storage()->CreateFolder(aBuf, IStorage::TYPE_SAVE)) { pEditor->FilelistPopulate(IStorage::TYPE_SAVE); @@ -1368,32 +1369,36 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View pEditor->UI()->DoLabel(&Label, "Author:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); - static float s_AuthorBox = 0; - pEditor->DoEditBox(&s_AuthorBox, &Button, pEditor->m_Map.m_MapInfoTmp.m_aAuthor, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aAuthor), 10.0f, &s_AuthorBox); + static CLineInput s_AuthorInput; + s_AuthorInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aAuthor, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aAuthor)); + pEditor->DoEditBox(&s_AuthorInput, &Button, 10.0f); // version box View.HSplitTop(20.0f, &Label, &View); pEditor->UI()->DoLabel(&Label, "Version:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); - static float s_VersionBox = 0; - pEditor->DoEditBox(&s_VersionBox, &Button, pEditor->m_Map.m_MapInfoTmp.m_aVersion, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aVersion), 10.0f, &s_VersionBox); + static CLineInput s_VersionInput; + s_VersionInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aVersion, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aVersion)); + pEditor->DoEditBox(&s_VersionInput, &Button, 10.0f); // credits box View.HSplitTop(20.0f, &Label, &View); pEditor->UI()->DoLabel(&Label, "Credits:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); - static float s_CreditsBox = 0; - pEditor->DoEditBox(&s_CreditsBox, &Button, pEditor->m_Map.m_MapInfoTmp.m_aCredits, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aCredits), 10.0f, &s_CreditsBox); + static CLineInput s_CreditsInput; + s_CreditsInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aCredits, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aCredits)); + pEditor->DoEditBox(&s_CreditsInput, &Button, 10.0f); // license box View.HSplitTop(20.0f, &Label, &View); pEditor->UI()->DoLabel(&Label, "License:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); - static float s_LicenseBox = 0; - pEditor->DoEditBox(&s_LicenseBox, &Button, pEditor->m_Map.m_MapInfoTmp.m_aLicense, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aLicense), 10.0f, &s_LicenseBox); + static CLineInput s_LicenseInput; + s_LicenseInput.SetBuffer(pEditor->m_Map.m_MapInfoTmp.m_aLicense, sizeof(pEditor->m_Map.m_MapInfoTmp.m_aLicense)); + pEditor->DoEditBox(&s_LicenseInput, &Button, 10.0f); // button bar ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar); diff --git a/src/test/str.cpp b/src/test/str.cpp index ff083fd39..d6e0d8846 100644 --- a/src/test/str.cpp +++ b/src/test/str.cpp @@ -590,7 +590,7 @@ TEST(Str, Copy) TEST(Str, Utf8Stats) { - int Size, Count; + size_t Size, Count; str_utf8_stats("abc", 4, 3, &Size, &Count); EXPECT_EQ(Size, 3);