mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-09 17:48:19 +00:00
Merge pull request #7577 from archimede67/pr-console-search
Added search in console (ctrl+f)
This commit is contained in:
commit
b9cce63bc7
|
@ -1292,6 +1292,8 @@ public:
|
|||
pCursor->m_ForceCursorRendering = false;
|
||||
pCursor->m_CursorCharacter = -1;
|
||||
pCursor->m_CursorRenderedPosition = vec2(-1.0f, -1.0f);
|
||||
|
||||
pCursor->m_vColorSplits = {};
|
||||
}
|
||||
|
||||
void MoveCursor(CTextCursor *pCursor, float x, float y) const override
|
||||
|
@ -1614,6 +1616,8 @@ public:
|
|||
bool GotNewLine = false;
|
||||
bool GotNewLineLast = false;
|
||||
|
||||
int ColorOption = 0;
|
||||
|
||||
while(pCurrent < pEnd && pCurrent != pEllipsis)
|
||||
{
|
||||
bool NewLine = false;
|
||||
|
@ -1665,6 +1669,7 @@ public:
|
|||
|
||||
while(pCurrent < pBatchEnd && pCurrent != pEllipsis)
|
||||
{
|
||||
const int PrevCharCount = pCursor->m_CharCount;
|
||||
pCursor->m_CharCount += pTmp - pCurrent;
|
||||
pCurrent = pTmp;
|
||||
int Character = NextCharacter;
|
||||
|
@ -1745,8 +1750,19 @@ public:
|
|||
const float CharX = (DrawX + CharKerning) + BearingX;
|
||||
const float CharY = TmpY - BearingY;
|
||||
|
||||
// Check if we have any color split
|
||||
ColorRGBA Color = m_Color;
|
||||
if(ColorOption < (int)pCursor->m_vColorSplits.size())
|
||||
{
|
||||
STextColorSplit &Split = pCursor->m_vColorSplits.at(ColorOption);
|
||||
if(PrevCharCount >= Split.m_CharIndex && PrevCharCount < Split.m_CharIndex + Split.m_Length)
|
||||
Color = Split.m_Color;
|
||||
if(PrevCharCount >= (Split.m_CharIndex + Split.m_Length - 1))
|
||||
ColorOption++;
|
||||
}
|
||||
|
||||
// don't add text that isn't drawn, the color overwrite is used for that
|
||||
if(m_Color.a != 0.f && IsRendered)
|
||||
if(Color.a != 0.f && IsRendered)
|
||||
{
|
||||
TextContainer.m_StringInfo.m_vCharacterQuads.emplace_back();
|
||||
STextCharQuad &TextCharQuad = TextContainer.m_StringInfo.m_vCharacterQuads.back();
|
||||
|
@ -1755,37 +1771,37 @@ public:
|
|||
TextCharQuad.m_aVertices[0].m_Y = CharY;
|
||||
TextCharQuad.m_aVertices[0].m_U = pGlyph->m_aUVs[0];
|
||||
TextCharQuad.m_aVertices[0].m_V = pGlyph->m_aUVs[3];
|
||||
TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(m_Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(m_Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(m_Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(m_Color.a * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.r = (unsigned char)(Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.g = (unsigned char)(Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.b = (unsigned char)(Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[0].m_Color.a = (unsigned char)(Color.a * 255.f);
|
||||
|
||||
TextCharQuad.m_aVertices[1].m_X = CharX + CharWidth;
|
||||
TextCharQuad.m_aVertices[1].m_Y = CharY;
|
||||
TextCharQuad.m_aVertices[1].m_U = pGlyph->m_aUVs[2];
|
||||
TextCharQuad.m_aVertices[1].m_V = pGlyph->m_aUVs[3];
|
||||
TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(m_Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(m_Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(m_Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(m_Color.a * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.r = (unsigned char)(Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.g = (unsigned char)(Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.b = (unsigned char)(Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[1].m_Color.a = (unsigned char)(Color.a * 255.f);
|
||||
|
||||
TextCharQuad.m_aVertices[2].m_X = CharX + CharWidth;
|
||||
TextCharQuad.m_aVertices[2].m_Y = CharY - CharHeight;
|
||||
TextCharQuad.m_aVertices[2].m_U = pGlyph->m_aUVs[2];
|
||||
TextCharQuad.m_aVertices[2].m_V = pGlyph->m_aUVs[1];
|
||||
TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(m_Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(m_Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(m_Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(m_Color.a * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.r = (unsigned char)(Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.g = (unsigned char)(Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.b = (unsigned char)(Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[2].m_Color.a = (unsigned char)(Color.a * 255.f);
|
||||
|
||||
TextCharQuad.m_aVertices[3].m_X = CharX;
|
||||
TextCharQuad.m_aVertices[3].m_Y = CharY - CharHeight;
|
||||
TextCharQuad.m_aVertices[3].m_U = pGlyph->m_aUVs[0];
|
||||
TextCharQuad.m_aVertices[3].m_V = pGlyph->m_aUVs[1];
|
||||
TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(m_Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(m_Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(m_Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(m_Color.a * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.r = (unsigned char)(Color.r * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.g = (unsigned char)(Color.g * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.b = (unsigned char)(Color.b * 255.f);
|
||||
TextCharQuad.m_aVertices[3].m_Color.a = (unsigned char)(Color.a * 255.f);
|
||||
}
|
||||
|
||||
// calculate the full width from the last selection point to the end of this selection draw on screen
|
||||
|
|
|
@ -181,6 +181,18 @@ struct STextBoundingBox
|
|||
}
|
||||
};
|
||||
|
||||
// Allow to render multi colored text in one go without having to call TextEx() multiple times.
|
||||
// Needed to allow multi colored multi line texts
|
||||
struct STextColorSplit
|
||||
{
|
||||
int m_CharIndex; // Which index within the text should the split occur
|
||||
int m_Length; // How long is the split
|
||||
ColorRGBA m_Color; // The color the text should be starting from m_CharIndex
|
||||
|
||||
STextColorSplit(int CharIndex, int Length, const ColorRGBA &Color) :
|
||||
m_CharIndex(CharIndex), m_Length(Length), m_Color(Color) {}
|
||||
};
|
||||
|
||||
class CTextCursor
|
||||
{
|
||||
public:
|
||||
|
@ -220,6 +232,9 @@ public:
|
|||
int m_CursorCharacter;
|
||||
vec2 m_CursorRenderedPosition;
|
||||
|
||||
// Color splits of the cursor to allow multicolored text
|
||||
std::vector<STextColorSplit> m_vColorSplits;
|
||||
|
||||
float Height() const
|
||||
{
|
||||
return m_LineCount * m_AlignedFontSize + std::max(0, m_LineCount - 1) * m_LineSpacing;
|
||||
|
@ -229,6 +244,36 @@ public:
|
|||
{
|
||||
return {m_StartX, m_StartY, m_LongestLineWidth, Height()};
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_Flags = 0;
|
||||
m_LineCount = 0;
|
||||
m_GlyphCount = 0;
|
||||
m_CharCount = 0;
|
||||
m_MaxLines = 0;
|
||||
m_StartX = 0;
|
||||
m_StartY = 0;
|
||||
m_LineWidth = 0;
|
||||
m_X = 0;
|
||||
m_Y = 0;
|
||||
m_MaxCharacterHeight = 0;
|
||||
m_LongestLineWidth = 0;
|
||||
m_FontSize = 0;
|
||||
m_AlignedFontSize = 0;
|
||||
m_LineSpacing = 0;
|
||||
m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_NONE;
|
||||
m_SelectionHeightFactor = 0;
|
||||
m_PressMouse = vec2();
|
||||
m_ReleaseMouse = vec2();
|
||||
m_SelectionStart = 0;
|
||||
m_SelectionEnd = 0;
|
||||
m_CursorMode = TEXT_CURSOR_CURSOR_MODE_NONE;
|
||||
m_ForceCursorRendering = false;
|
||||
m_CursorCharacter = 0;
|
||||
m_CursorRenderedPosition = vec2();
|
||||
m_vColorSplits.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct STextContainerUsages
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <game/client/render.h>
|
||||
#include <game/client/ui.h>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include "console.h"
|
||||
|
||||
static constexpr float FONT_SIZE = 10.0f;
|
||||
|
@ -98,6 +100,9 @@ static bool IsSettingCommandPrefix(const char *pStr)
|
|||
return std::any_of(std::begin(gs_apSettingCommands), std::end(gs_apSettingCommands), [pStr](auto *pCmd) { return str_startswith_nocase(pStr, pCmd); });
|
||||
}
|
||||
|
||||
const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const ColorRGBA CGameConsole::ms_SearchSelectedColor = ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f);
|
||||
|
||||
CGameConsole::CInstance::CInstance(int Type)
|
||||
{
|
||||
m_pHistoryEntry = 0x0;
|
||||
|
@ -128,6 +133,9 @@ CGameConsole::CInstance::CInstance(int Type)
|
|||
m_IsCommand = false;
|
||||
|
||||
m_Input.SetClipboardLineCallback([this](const char *pStr) { ExecuteLine(pStr); });
|
||||
|
||||
m_CurrentMatchIndex = -1;
|
||||
m_aCurrentSearchString[0] = '\0';
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::Init(CGameConsole *pGameConsole)
|
||||
|
@ -146,6 +154,7 @@ void CGameConsole::CInstance::ClearBacklog()
|
|||
|
||||
m_Backlog.Init();
|
||||
m_BacklogCurLine = 0;
|
||||
UpdateSearch();
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::UpdateBacklogTextAttributes()
|
||||
|
@ -255,104 +264,147 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
|
|||
{
|
||||
bool Handled = false;
|
||||
|
||||
auto &&SelectNextSearchMatch = [&](int Direction) {
|
||||
if(!m_vSearchMatches.empty())
|
||||
{
|
||||
m_CurrentMatchIndex += Direction;
|
||||
if(m_CurrentMatchIndex >= (int)m_vSearchMatches.size())
|
||||
m_CurrentMatchIndex = 0;
|
||||
if(m_CurrentMatchIndex < 0)
|
||||
m_CurrentMatchIndex = (int)m_vSearchMatches.size() - 1;
|
||||
m_HasSelection = false;
|
||||
// Also scroll to the correct line
|
||||
ScrollToCenter(m_vSearchMatches[m_CurrentMatchIndex].m_StartLine, m_vSearchMatches[m_CurrentMatchIndex].m_EndLine);
|
||||
}
|
||||
};
|
||||
|
||||
if(Event.m_Flags & IInput::FLAG_PRESS)
|
||||
{
|
||||
if(Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)
|
||||
{
|
||||
if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot))
|
||||
if(!m_Searching)
|
||||
{
|
||||
if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
|
||||
if(!m_Input.IsEmpty() || (m_UsernameReq && !m_pGameConsole->Client()->RconAuthed() && !m_UserGot))
|
||||
{
|
||||
const char *pPrevEntry = m_History.Last();
|
||||
if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0)
|
||||
if(m_Type == CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
|
||||
{
|
||||
char *pEntry = m_History.Allocate(m_Input.GetLength() + 1);
|
||||
str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1);
|
||||
const char *pPrevEntry = m_History.Last();
|
||||
if(pPrevEntry == nullptr || str_comp(pPrevEntry, m_Input.GetString()) != 0)
|
||||
{
|
||||
char *pEntry = m_History.Allocate(m_Input.GetLength() + 1);
|
||||
str_copy(pEntry, m_Input.GetString(), m_Input.GetLength() + 1);
|
||||
}
|
||||
// print out the user's commands before they get run
|
||||
char aBuf[IConsole::CMDLINE_LENGTH + 3];
|
||||
str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString());
|
||||
m_pGameConsole->PrintLine(m_Type, aBuf);
|
||||
}
|
||||
// print out the user's commands before they get run
|
||||
char aBuf[IConsole::CMDLINE_LENGTH + 3];
|
||||
str_format(aBuf, sizeof(aBuf), "> %s", m_Input.GetString());
|
||||
m_pGameConsole->PrintLine(m_Type, aBuf);
|
||||
ExecuteLine(m_Input.GetString());
|
||||
m_Input.Clear();
|
||||
m_pHistoryEntry = 0x0;
|
||||
}
|
||||
ExecuteLine(m_Input.GetString());
|
||||
m_Input.Clear();
|
||||
m_pHistoryEntry = 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectNextSearchMatch(m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1);
|
||||
}
|
||||
|
||||
Handled = true;
|
||||
}
|
||||
else if(Event.m_Key == KEY_UP)
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
if(!m_Searching)
|
||||
{
|
||||
char *pTest = m_History.Prev(m_pHistoryEntry);
|
||||
if(m_pHistoryEntry)
|
||||
{
|
||||
char *pTest = m_History.Prev(m_pHistoryEntry);
|
||||
|
||||
if(pTest)
|
||||
m_pHistoryEntry = pTest;
|
||||
if(pTest)
|
||||
m_pHistoryEntry = pTest;
|
||||
}
|
||||
else
|
||||
m_pHistoryEntry = m_History.Last();
|
||||
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry);
|
||||
}
|
||||
else
|
||||
m_pHistoryEntry = m_History.Last();
|
||||
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry);
|
||||
{
|
||||
SelectNextSearchMatch(-1);
|
||||
}
|
||||
Handled = true;
|
||||
}
|
||||
else if(Event.m_Key == KEY_DOWN)
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
|
||||
if(!m_Searching)
|
||||
{
|
||||
if(m_pHistoryEntry)
|
||||
m_pHistoryEntry = m_History.Next(m_pHistoryEntry);
|
||||
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry);
|
||||
if(m_pHistoryEntry)
|
||||
m_Input.Set(m_pHistoryEntry);
|
||||
else
|
||||
m_Input.Clear();
|
||||
}
|
||||
else
|
||||
m_Input.Clear();
|
||||
{
|
||||
SelectNextSearchMatch(1);
|
||||
}
|
||||
Handled = true;
|
||||
}
|
||||
else if(Event.m_Key == KEY_TAB)
|
||||
{
|
||||
const int Direction = m_pGameConsole->m_pClient->Input()->ShiftIsPressed() ? -1 : 1;
|
||||
|
||||
// command completion
|
||||
const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands();
|
||||
int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands);
|
||||
if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
|
||||
if(!m_Searching)
|
||||
{
|
||||
// command completion
|
||||
const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands();
|
||||
int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands);
|
||||
if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
|
||||
{
|
||||
if(CompletionEnumerationCount)
|
||||
{
|
||||
if(m_CompletionChosen == -1 && Direction < 0)
|
||||
m_CompletionChosen = 0;
|
||||
m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
|
||||
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
|
||||
}
|
||||
else if(m_CompletionChosen != -1)
|
||||
{
|
||||
m_CompletionChosen = -1;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Argument completion
|
||||
const bool TuningCompletion = IsTuningCommandPrefix(GetString());
|
||||
const bool SettingCompletion = IsSettingCommandPrefix(GetString());
|
||||
if(TuningCompletion)
|
||||
CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument);
|
||||
else if(SettingCompletion)
|
||||
CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands);
|
||||
|
||||
if(CompletionEnumerationCount)
|
||||
{
|
||||
if(m_CompletionChosen == -1 && Direction < 0)
|
||||
m_CompletionChosen = 0;
|
||||
m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
|
||||
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
|
||||
if(m_CompletionChosenArgument == -1 && Direction < 0)
|
||||
m_CompletionChosenArgument = 0;
|
||||
m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
|
||||
if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE)
|
||||
PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this);
|
||||
else if(SettingCompletion)
|
||||
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this);
|
||||
}
|
||||
else if(m_CompletionChosen != -1)
|
||||
else if(m_CompletionChosenArgument != -1)
|
||||
{
|
||||
m_CompletionChosen = -1;
|
||||
m_CompletionChosenArgument = -1;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Argument completion
|
||||
const bool TuningCompletion = IsTuningCommandPrefix(GetString());
|
||||
const bool SettingCompletion = IsSettingCommandPrefix(GetString());
|
||||
if(TuningCompletion)
|
||||
CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument);
|
||||
else if(SettingCompletion)
|
||||
CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands);
|
||||
|
||||
if(CompletionEnumerationCount)
|
||||
else
|
||||
{
|
||||
if(m_CompletionChosenArgument == -1 && Direction < 0)
|
||||
m_CompletionChosenArgument = 0;
|
||||
m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
|
||||
if(TuningCompletion && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE)
|
||||
PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this);
|
||||
else if(SettingCompletion)
|
||||
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this);
|
||||
}
|
||||
else if(m_CompletionChosenArgument != -1)
|
||||
{
|
||||
m_CompletionChosenArgument = -1;
|
||||
Reset();
|
||||
// Use Tab / Shift-Tab to cycle through search matches
|
||||
SelectNextSearchMatch(Direction);
|
||||
}
|
||||
}
|
||||
else if(Event.m_Key == KEY_PAGEUP)
|
||||
|
@ -394,10 +446,21 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
|
|||
m_BacklogCurLine = 0;
|
||||
m_HasSelection = false;
|
||||
}
|
||||
else if(Event.m_Key == KEY_F && m_pGameConsole->Input()->ModifierIsPressed() && Event.m_Flags & IInput::FLAG_PRESS)
|
||||
{
|
||||
m_Searching = !m_Searching;
|
||||
ClearSearch();
|
||||
|
||||
Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!Handled)
|
||||
{
|
||||
Handled = m_Input.ProcessInput(Event);
|
||||
if(Handled)
|
||||
UpdateSearch();
|
||||
}
|
||||
|
||||
if(Event.m_Flags & (IInput::FLAG_PRESS | IInput::FLAG_TEXT))
|
||||
{
|
||||
|
@ -486,6 +549,26 @@ int CGameConsole::CInstance::GetLinesToScroll(int Direction, int LinesToScroll)
|
|||
return LinesToScroll > 0 ? minimum(Amount, LinesToScroll) : Amount;
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::ScrollToCenter(int StartLine, int EndLine)
|
||||
{
|
||||
// This method is used to scroll lines from `StartLine` to `EndLine` to the center of the screen, if possible.
|
||||
|
||||
// Find target line
|
||||
int Target = maximum(0, (int)ceil(StartLine - minimum(StartLine - EndLine, m_LinesRendered) / 2) - m_LinesRendered / 2);
|
||||
if(m_BacklogCurLine == Target)
|
||||
return;
|
||||
|
||||
// Compute acutal amount of lines to scroll to make sure lines fit in viewport and we don't have empty space
|
||||
int Direction = m_BacklogCurLine - Target < 0 ? -1 : 1;
|
||||
int LinesToScroll = absolute(Target - m_BacklogCurLine);
|
||||
int ComputedLines = GetLinesToScroll(Direction, LinesToScroll);
|
||||
|
||||
if(Direction == -1)
|
||||
m_BacklogCurLine += ComputedLines;
|
||||
else
|
||||
m_BacklogCurLine -= ComputedLines;
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry)
|
||||
{
|
||||
CTextCursor Cursor;
|
||||
|
@ -498,6 +581,119 @@ void CGameConsole::CInstance::UpdateEntryTextAttributes(CBacklogEntry *pEntry)
|
|||
pEntry->m_LineCount = Cursor.m_LineCount;
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::ClearSearch()
|
||||
{
|
||||
m_vSearchMatches.clear();
|
||||
m_CurrentMatchIndex = -1;
|
||||
m_Input.Clear();
|
||||
m_aCurrentSearchString[0] = '\0';
|
||||
}
|
||||
|
||||
void CGameConsole::CInstance::UpdateSearch()
|
||||
{
|
||||
if(!m_Searching)
|
||||
return;
|
||||
|
||||
const char *pSearchText = m_Input.GetString();
|
||||
bool SearchChanged = str_utf8_comp_nocase(pSearchText, m_aCurrentSearchString) != 0;
|
||||
|
||||
int SearchLength = m_Input.GetLength();
|
||||
str_copy(m_aCurrentSearchString, pSearchText);
|
||||
|
||||
m_vSearchMatches.clear();
|
||||
if(pSearchText[0] == '\0')
|
||||
{
|
||||
m_CurrentMatchIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if(SearchChanged)
|
||||
{
|
||||
m_CurrentMatchIndex = -1;
|
||||
m_HasSelection = false;
|
||||
}
|
||||
|
||||
ITextRender *pTextRender = m_pGameConsole->UI()->TextRender();
|
||||
const int LineWidth = m_pGameConsole->UI()->Screen()->w - 10.0f;
|
||||
|
||||
CBacklogEntry *pEntry = m_Backlog.Last();
|
||||
int EntryLine = 0, LineToScrollStart = 0, LineToScrollEnd = 0;
|
||||
|
||||
for(; pEntry; EntryLine += pEntry->m_LineCount, pEntry = m_Backlog.Prev(pEntry))
|
||||
{
|
||||
const char *pSearchPos = str_utf8_find_nocase(pEntry->m_aText, pSearchText);
|
||||
if(!pSearchPos)
|
||||
continue;
|
||||
|
||||
int EntryLineCount = pEntry->m_LineCount;
|
||||
|
||||
// Find all occurences of the search string and save their positions
|
||||
while(pSearchPos)
|
||||
{
|
||||
int Pos = pSearchPos - pEntry->m_aText;
|
||||
|
||||
if(EntryLineCount == 1)
|
||||
{
|
||||
m_vSearchMatches.emplace_back(Pos, EntryLine, EntryLine, EntryLine);
|
||||
if(EntryLine > LineToScrollStart)
|
||||
{
|
||||
LineToScrollStart = EntryLine;
|
||||
LineToScrollEnd = EntryLine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// A match can span multiple lines in case of a multiline entry, so we need to know which line the match starts at
|
||||
// and which line it ends at in order to put it in viewport properly
|
||||
STextSizeProperties Props;
|
||||
int LineCount;
|
||||
Props.m_pLineCount = &LineCount;
|
||||
|
||||
// Compute line of end match
|
||||
pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos + SearchLength, LineWidth, 0, Props);
|
||||
int EndLine = (EntryLineCount - LineCount);
|
||||
int MatchEndLine = EntryLine + EndLine;
|
||||
|
||||
// Compute line of start of match
|
||||
int MatchStartLine = MatchEndLine;
|
||||
if(LineCount > 1)
|
||||
{
|
||||
pTextRender->TextWidth(FONT_SIZE, pEntry->m_aText, Pos, LineWidth, 0, Props);
|
||||
int StartLine = (EntryLineCount - LineCount);
|
||||
MatchStartLine = EntryLine + StartLine;
|
||||
}
|
||||
|
||||
if(MatchStartLine > LineToScrollStart)
|
||||
{
|
||||
LineToScrollStart = MatchStartLine;
|
||||
LineToScrollEnd = MatchEndLine;
|
||||
}
|
||||
|
||||
m_vSearchMatches.emplace_back(Pos, MatchStartLine, MatchEndLine, EntryLine);
|
||||
}
|
||||
|
||||
pSearchPos = str_utf8_find_nocase(pEntry->m_aText + Pos + SearchLength, pSearchText);
|
||||
}
|
||||
}
|
||||
|
||||
if(!m_vSearchMatches.empty() && SearchChanged)
|
||||
m_CurrentMatchIndex = 0;
|
||||
else
|
||||
m_CurrentMatchIndex = std::clamp(m_CurrentMatchIndex, -1, (int)m_vSearchMatches.size() - 1);
|
||||
|
||||
// Reverse order of lines by sorting so we have matches from top to bottom instead of bottom to top
|
||||
std::sort(m_vSearchMatches.begin(), m_vSearchMatches.end(), [](const SSearchMatch &MatchA, const SSearchMatch &MatchB) {
|
||||
if(MatchA.m_StartLine == MatchB.m_StartLine)
|
||||
return MatchA.m_Pos < MatchB.m_Pos; // Make sure to keep position order
|
||||
return MatchA.m_StartLine > MatchB.m_StartLine;
|
||||
});
|
||||
|
||||
if(!m_vSearchMatches.empty() && SearchChanged)
|
||||
{
|
||||
ScrollToCenter(LineToScrollStart, LineToScrollEnd);
|
||||
}
|
||||
}
|
||||
|
||||
CGameConsole::CGameConsole() :
|
||||
m_LocalConsole(CONSOLETYPE_LOCAL), m_RemoteConsole(CONSOLETYPE_REMOTE)
|
||||
{
|
||||
|
@ -590,12 +786,39 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
|
|||
pInfo->m_TotalWidth = pInfo->m_Cursor.m_X + pInfo->m_Offset;
|
||||
}
|
||||
|
||||
void CGameConsole::Prompt(char (&aPrompt)[32])
|
||||
{
|
||||
CInstance *pConsole = CurrentConsole();
|
||||
if(pConsole->m_Searching)
|
||||
{
|
||||
str_format(aPrompt, sizeof(aPrompt), "%s: ", Localize("Searching"));
|
||||
}
|
||||
else if(m_ConsoleType == CONSOLETYPE_REMOTE)
|
||||
{
|
||||
if(Client()->State() == IClient::STATE_LOADING || Client()->State() == IClient::STATE_ONLINE)
|
||||
{
|
||||
if(Client()->RconAuthed())
|
||||
str_copy(aPrompt, "rcon> ");
|
||||
else if(pConsole->m_UsernameReq && !pConsole->m_UserGot)
|
||||
str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Username"));
|
||||
else
|
||||
str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("Enter Password"));
|
||||
}
|
||||
else
|
||||
str_format(aPrompt, sizeof(aPrompt), "%s> ", Localize("NOT CONNECTED"));
|
||||
}
|
||||
else
|
||||
{
|
||||
str_copy(aPrompt, "> ");
|
||||
}
|
||||
}
|
||||
|
||||
void CGameConsole::OnRender()
|
||||
{
|
||||
CUIRect Screen = *UI()->Screen();
|
||||
CInstance *pConsole = CurrentConsole();
|
||||
|
||||
float ConsoleMaxHeight = Screen.h * 3 / 5.0f;
|
||||
float MaxConsoleHeight = Screen.h * 3 / 5.0f;
|
||||
float ConsoleHeight;
|
||||
|
||||
float Progress = (Client()->GlobalTime() - (m_StateChangeEnd - m_StateChangeDuration)) / m_StateChangeDuration;
|
||||
|
@ -632,10 +855,10 @@ void CGameConsole::OnRender()
|
|||
ConsoleHeightScale = ConsoleScaleFunc(Progress);
|
||||
else if(m_ConsoleState == CONSOLE_CLOSING)
|
||||
ConsoleHeightScale = ConsoleScaleFunc(1.0f - Progress);
|
||||
else //if (console_state == CONSOLE_OPEN)
|
||||
else // if (console_state == CONSOLE_OPEN)
|
||||
ConsoleHeightScale = ConsoleScaleFunc(1.0f);
|
||||
|
||||
ConsoleHeight = ConsoleHeightScale * ConsoleMaxHeight;
|
||||
ConsoleHeight = ConsoleHeightScale * MaxConsoleHeight;
|
||||
|
||||
UI()->MapScreen();
|
||||
|
||||
|
@ -700,30 +923,10 @@ void CGameConsole::OnRender()
|
|||
// render prompt
|
||||
CTextCursor Cursor;
|
||||
TextRender()->SetCursor(&Cursor, x, y, FONT_SIZE, TEXTFLAG_RENDER);
|
||||
const char *pPrompt = "> ";
|
||||
if(m_ConsoleType == CONSOLETYPE_REMOTE)
|
||||
{
|
||||
if(Client()->State() == IClient::STATE_LOADING || Client()->State() == IClient::STATE_ONLINE)
|
||||
{
|
||||
if(Client()->RconAuthed())
|
||||
pPrompt = "rcon> ";
|
||||
else
|
||||
{
|
||||
if(pConsole->m_UsernameReq)
|
||||
{
|
||||
if(!pConsole->m_UserGot)
|
||||
pPrompt = "Enter Username> ";
|
||||
else
|
||||
pPrompt = "Enter Password> ";
|
||||
}
|
||||
else
|
||||
pPrompt = "Enter Password> ";
|
||||
}
|
||||
}
|
||||
else
|
||||
pPrompt = "NOT CONNECTED> ";
|
||||
}
|
||||
TextRender()->TextEx(&Cursor, pPrompt, -1);
|
||||
|
||||
char aPrompt[32];
|
||||
Prompt(aPrompt);
|
||||
TextRender()->TextEx(&Cursor, aPrompt);
|
||||
|
||||
// check if mouse is pressed
|
||||
if(!pConsole->m_MouseIsPress && Input()->NativeMousePressed(1))
|
||||
|
@ -786,7 +989,7 @@ void CGameConsole::OnRender()
|
|||
}
|
||||
|
||||
// render possible commands
|
||||
if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty())
|
||||
if(!pConsole->m_Searching && (m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && !pConsole->m_Input.IsEmpty())
|
||||
{
|
||||
CCompletionOptionRenderInfo Info;
|
||||
Info.m_pSelf = this;
|
||||
|
@ -831,8 +1034,26 @@ void CGameConsole::OnRender()
|
|||
|
||||
UI()->DoSmoothScrollLogic(&pConsole->m_CompletionRenderOffset, &pConsole->m_CompletionRenderOffsetChange, Info.m_Width, Info.m_TotalWidth);
|
||||
}
|
||||
else if(pConsole->m_Searching && !pConsole->m_Input.IsEmpty())
|
||||
{ // Render current match and match count
|
||||
CTextCursor MatchInfoCursor;
|
||||
TextRender()->SetCursor(&MatchInfoCursor, InitialX, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER);
|
||||
TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
if(!pConsole->m_vSearchMatches.empty())
|
||||
{
|
||||
char aBuf[64];
|
||||
str_format(aBuf, sizeof(aBuf), Localize("Match %d of %d"), pConsole->m_CurrentMatchIndex + 1, (int)pConsole->m_vSearchMatches.size());
|
||||
TextRender()->TextEx(&MatchInfoCursor, aBuf, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextRender()->TextEx(&MatchInfoCursor, Localize("No results"), -1);
|
||||
}
|
||||
}
|
||||
|
||||
pConsole->PumpBacklogPending();
|
||||
if(pConsole->m_NewLineCounter > 0)
|
||||
pConsole->UpdateSearch();
|
||||
|
||||
// render console log (current entry, status, wrap lines)
|
||||
CInstance::CBacklogEntry *pEntry = pConsole->m_Backlog.Last();
|
||||
|
@ -915,7 +1136,28 @@ void CGameConsole::OnRender()
|
|||
Cursor.m_CalculateSelectionMode = (m_ConsoleState == CONSOLE_OPEN && pConsole->m_MousePress.y < pConsole->m_BoundingBox.m_Y && (pConsole->m_MouseIsPress || (pConsole->m_CurSelStart != pConsole->m_CurSelEnd) || pConsole->m_HasSelection)) ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE;
|
||||
Cursor.m_PressMouse = pConsole->m_MousePress;
|
||||
Cursor.m_ReleaseMouse = pConsole->m_MouseRelease;
|
||||
|
||||
if(pConsole->m_Searching && pConsole->m_CurrentMatchIndex != -1)
|
||||
{
|
||||
std::vector<CInstance::SSearchMatch> vMatches;
|
||||
std::copy_if(pConsole->m_vSearchMatches.begin(), pConsole->m_vSearchMatches.end(), std::back_inserter(vMatches), [&](const CInstance::SSearchMatch &Match) { return Match.m_EntryLine == LineNum + 1 - pEntry->m_LineCount; });
|
||||
|
||||
auto CurrentSelectedOccurrence = pConsole->m_vSearchMatches[pConsole->m_CurrentMatchIndex];
|
||||
|
||||
std::vector<STextColorSplit> vColorSplits;
|
||||
for(const auto &Match : vMatches)
|
||||
{
|
||||
bool IsSelected = CurrentSelectedOccurrence.m_EntryLine == Match.m_EntryLine && CurrentSelectedOccurrence.m_Pos == Match.m_Pos;
|
||||
Cursor.m_vColorSplits.emplace_back(
|
||||
Match.m_Pos,
|
||||
pConsole->m_Input.GetLength(),
|
||||
IsSelected ? ms_SearchSelectedColor : ms_SearchHighlightColor);
|
||||
}
|
||||
}
|
||||
|
||||
TextRender()->TextEx(&Cursor, pEntry->m_aText, -1);
|
||||
Cursor.m_vColorSplits = {};
|
||||
|
||||
if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE)
|
||||
{
|
||||
pConsole->m_CurSelStart = minimum(Cursor.m_SelectionStart, Cursor.m_SelectionEnd);
|
||||
|
@ -947,8 +1189,6 @@ void CGameConsole::OnRender()
|
|||
|
||||
Graphics()->ClipDisable();
|
||||
|
||||
if(LineNum >= 0)
|
||||
pConsole->m_BacklogCurLine = clamp(pConsole->m_BacklogCurLine, 0, LineNum);
|
||||
pConsole->m_BacklogLastActiveLine = pConsole->m_BacklogCurLine;
|
||||
|
||||
if(m_WantsSelectionCopy && !SelectionString.empty())
|
||||
|
@ -1093,12 +1333,14 @@ void CGameConsole::ConConsolePageUp(IConsole::IResult *pResult, void *pUserData)
|
|||
{
|
||||
CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole();
|
||||
pConsole->m_BacklogCurLine += pConsole->GetLinesToScroll(-1, pConsole->m_LinesRendered);
|
||||
pConsole->m_HasSelection = false;
|
||||
}
|
||||
|
||||
void CGameConsole::ConConsolePageDown(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CInstance *pConsole = ((CGameConsole *)pUserData)->CurrentConsole();
|
||||
pConsole->m_BacklogCurLine -= pConsole->GetLinesToScroll(1, pConsole->m_LinesRendered);
|
||||
pConsole->m_HasSelection = false;
|
||||
if(pConsole->m_BacklogCurLine < 0)
|
||||
pConsole->m_BacklogCurLine = 0;
|
||||
}
|
||||
|
|
|
@ -78,6 +78,21 @@ class CGameConsole : public CComponent
|
|||
const char *m_pCommandHelp;
|
||||
const char *m_pCommandParams;
|
||||
|
||||
bool m_Searching = false;
|
||||
struct SSearchMatch
|
||||
{
|
||||
int m_Pos;
|
||||
int m_StartLine;
|
||||
int m_EndLine;
|
||||
int m_EntryLine;
|
||||
|
||||
SSearchMatch(int Pos, int StartLine, int EndLine, int EntryLine) :
|
||||
m_Pos(Pos), m_StartLine(StartLine), m_EndLine(EndLine), m_EntryLine(EntryLine) {}
|
||||
};
|
||||
int m_CurrentMatchIndex;
|
||||
char m_aCurrentSearchString[IConsole::CMDLINE_LENGTH];
|
||||
std::vector<SSearchMatch> m_vSearchMatches;
|
||||
|
||||
CInstance(int t);
|
||||
void Init(CGameConsole *pGameConsole);
|
||||
|
||||
|
@ -92,12 +107,19 @@ class CGameConsole : public CComponent
|
|||
bool OnInput(const IInput::CEvent &Event);
|
||||
void PrintLine(const char *pLine, int Len, ColorRGBA PrintColor) REQUIRES(!m_BacklogPendingLock);
|
||||
int GetLinesToScroll(int Direction, int LinesToScroll);
|
||||
void ScrollToCenter(int StartLine, int EndLine);
|
||||
void ClearSearch();
|
||||
|
||||
const char *GetString() const { return m_Input.GetString(); }
|
||||
static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser);
|
||||
static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser);
|
||||
|
||||
void UpdateEntryTextAttributes(CBacklogEntry *pEntry);
|
||||
|
||||
private:
|
||||
void UpdateSearch();
|
||||
|
||||
friend class CGameConsole;
|
||||
};
|
||||
|
||||
class IConsole *m_pConsole;
|
||||
|
@ -115,6 +137,9 @@ class CGameConsole : public CComponent
|
|||
|
||||
bool m_WantsSelectionCopy = false;
|
||||
|
||||
static const ColorRGBA ms_SearchHighlightColor;
|
||||
static const ColorRGBA ms_SearchSelectedColor;
|
||||
|
||||
void Toggle(int Type);
|
||||
void Dump(int Type);
|
||||
|
||||
|
@ -150,6 +175,7 @@ public:
|
|||
virtual void OnRender() override;
|
||||
virtual void OnMessage(int MsgType, void *pRawMsg) override;
|
||||
virtual bool OnInput(const IInput::CEvent &Event) override;
|
||||
void Prompt(char (&aPrompt)[32]);
|
||||
|
||||
bool IsClosed() { return m_ConsoleState == CONSOLE_CLOSED; }
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ void CUIElement::SUIElementRect::Reset()
|
|||
m_Rounding = -1.0f;
|
||||
m_Corners = -1;
|
||||
m_Text.clear();
|
||||
mem_zero(&m_Cursor, sizeof(m_Cursor));
|
||||
m_Cursor.Reset();
|
||||
m_TextColor = ColorRGBA(-1, -1, -1, -1);
|
||||
m_TextOutlineColor = ColorRGBA(-1, -1, -1, -1);
|
||||
m_QuadColor = ColorRGBA(-1, -1, -1, -1);
|
||||
|
|
Loading…
Reference in a new issue