Port line input and IME support from 0.7

Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397.

General
------------------------------

Fix issues with the text input. Closes #4346. Closes #4524.

Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference.

Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved.

UI text input
------------------------------

Fix inconsistent mouse-based left and right scrolling (closes #4347).

Support smooth left and right scrolling.

Chat
------------------------------

Support keyboard-based text selection of the chat input.

Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor.

Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling.

Console
------------------------------

Also support mouse-based text selection of the command input.

Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console.

Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice).

When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log.

Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected.

Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end.

Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead.

IME support
------------------------------

Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems.

Improve composition rendering by underlining the composition text instead of putting it in square brackets.

Track active input globally to properly activate and deactivate IME through the SDL functions.

Closes #1030. Closes #1008.

Password rendering
------------------------------

Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint.

Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could:

1. Use a latin password and switch off the IME for the password input with the IME hotkey.
2. Blank your screen with an external program while you are streaming and entering passwords.
3. Use binds to authenticate in rcon or to set the server browser password.

Refactoring
------------------------------

Move all text input logic and general rendering to `CLineInput`.

A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead).

Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer.

Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input.

Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text.

Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input.

Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer.

Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`.

Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering.

Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer.

Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use.

IME usage guide (Windows)
------------------------------

1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean).
2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally.
2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing).
3. Switch from Latin/English input mode to the respective asian input mode.
   - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings.
   - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings.
   - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet.
   - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately.
4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list.

SDL version-specific issues
------------------------------

- 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor.
- 2.0.18, 2.0.20: IME candidates work.
- 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode.
- 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
This commit is contained in:
Robert Müller 2023-01-03 22:28:38 +01:00
parent f77af29094
commit ebb2e4253d
29 changed files with 1558 additions and 1485 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -24,6 +24,16 @@
#define SDL_JOYSTICK_AXIS_MAX 32767
#endif
#if defined(CONF_FAMILY_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// windows.h must be included before imm.h, but clang-format requires includes to be sorted alphabetically, hence this comment.
#include <imm.h>
#endif
// for platform specific features that aren't available or are broken in SDL
#include <SDL_syswm.h>
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<IEngineGraphics>();
m_pConsole = Kernel()->RequestInterface<IConsole>();
// 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')

View file

@ -9,6 +9,9 @@
#include <engine/input.h>
#include <engine/keys.h>
#include <string>
#include <vector>
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<std::string> 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;
};

View file

@ -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<float>::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<float>::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<float>::lowest(), 0, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_ReleaseMouseX, pCursor->m_ReleaseMouseY, SelectionEndChar, SelectionUsedRelease, std::numeric_limits<float>::lowest(), 0, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits<float>::lowest(), 0, CharX, CharWidth, TmpY);
CheckSelectionStart(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits<float>::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<float>::max(), 0, DrawY + Size);
CheckSelectionEnd(true, pCursor->m_PressMouseX, pCursor->m_PressMouseY, SelectionStartChar, SelectionUsedPress, std::numeric_limits<float>::max(), 0, DrawY + Size);
CheckSelectionEnd(true, pCursor->m_ReleaseMouse, SelectionEndChar, SelectionUsedRelease, std::numeric_limits<float>::max(), 0, DrawY + Size);
CheckSelectionEnd(true, pCursor->m_PressMouse, SelectionStartChar, SelectionUsedPress, std::numeric_limits<float>::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<float>::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<float>::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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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];

View file

@ -2,15 +2,11 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/logger.h>
#include <climits>
#include <cmath>
#include <limits>
#include <base/math.h>
#include <base/system.h>
#include <game/generated/client_data.h>
#include <base/system.h>
#include <engine/console.h>
#include <engine/graphics.h>
#include <engine/keys.h>
@ -19,17 +15,12 @@
#include <engine/storage.h>
#include <engine/textrender.h>
#include <game/client/ui.h>
#include <game/localization.h>
#include <game/version.h>
#include <game/client/lineinput.h>
#include <game/client/render.h>
#include <game/client/gameclient.h>
#include <base/math.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#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)

View file

@ -37,7 +37,7 @@ class CGameConsole : public CComponent
CStaticRingBuffer<char, 64 * 1024, CRingBufferBase::FLAG_RECYCLE> 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);

View file

@ -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()

View file

@ -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<VOTE_REASON_LENGTH> 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<IO_MAX_PATH_LENGTH> m_DemoRenameInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoSliceInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenderInput;
int m_DemolistSelectedIndex;
bool m_DemolistSelectedIsDir;
int m_DemolistStorageType;

View file

@ -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<MAX_NAME_LENGTH> 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<MAX_CLAN_LENGTH> 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();
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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;

View file

@ -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<const TName *> &vpSearchList, std::vector<TName>
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;
}

View file

@ -28,6 +28,7 @@
#include <base/vmath.h>
#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;

View file

@ -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 <base/math.h>
#include <base/system.h>
#include "lineinput.h"
#include <engine/keys.h>
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<size_t>(Begin, 0, m_Len);
End = clamp<size_t>(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<size_t>(m_CursorPos, 0, m_Len))
SetCursorOffset(m_CursorPos);
if(!in_range<size_t>(m_SelectionStart, 0, m_Len) || !in_range<size_t>(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<size_t>(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<size_t>(Start, 0, m_Len);
m_SelectionEnd = clamp<size_t>(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());
}

View file

@ -3,55 +3,221 @@
#ifndef GAME_CLIENT_LINEINPUT_H
#define GAME_CLIENT_LINEINPUT_H
#include <base/vmath.h>
#include <engine/client.h>
#include <engine/graphics.h>
#include <engine/input.h>
#include <engine/textrender.h>
#include <game/client/ui_rect.h>
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<void(const char *pLine)> 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<size_t MaxSize, size_t MaxChars = MaxSize>
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

View file

@ -11,7 +11,6 @@
#include <engine/keys.h>
#include <engine/shared/config.h>
#include <game/client/lineinput.h>
#include <game/localization.h>
#include <limits>
@ -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<IGraphics>();
m_pInput = pKernel->RequestInterface<IInput>();
m_pTextRender = pKernel->RequestInterface<ITextRender>();
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<int>(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;

View file

@ -3,6 +3,7 @@
#ifndef GAME_CLIENT_UI_H
#define GAME_CLIENT_UI_H
#include "lineinput.h"
#include "ui_rect.h"
#include <engine/input.h>
@ -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<CUIRect> 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);

View file

@ -23,6 +23,7 @@
#include <game/client/components/camera.h>
#include <game/client/components/menu_background.h>
#include <game/client/gameclient.h>
#include <game/client/lineinput.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/client/ui_listbox.h>
@ -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()

View file

@ -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<IO_MAX_PATH_LENGTH> 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<IO_MAX_PATH_LENGTH> m_FileDialogFilterInput;
char *m_pFileDialogPath;
int m_FileDialogFileType;
int m_FilesSelectedIndex;
char m_aFileDialogNewFolderName[IO_MAX_PATH_LENGTH];
CLineInputBuffered<IO_MAX_PATH_LENGTH> 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);

View file

@ -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);

View file

@ -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);