5743: Smooth console completion scrolling r=def- a=Robyt3

More smoothly scroll selected console completion option into view.

See videos at https://github.com/teeworlds/teeworlds/pull/3054.

The smooth scrolling logic is extracted in `CUI::DoSmoothScrollLogic` so it can eventually be reused for smooth input scrolling (see https://github.com/teeworlds/teeworlds/pull/3150).

## Checklist

- [X] Tested the change ingame
- [X] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2022-08-15 22:31:12 +00:00 committed by GitHub
commit 12273bd0d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 16 deletions

View file

@ -147,6 +147,7 @@ void CGameConsole::CInstance::ClearHistory()
void CGameConsole::CInstance::Reset()
{
m_CompletionRenderOffset = 0.0f;
m_CompletionRenderOffsetChange = 0.0f;
}
void CGameConsole::CInstance::ExecuteLine(const char *pLine)
@ -533,25 +534,29 @@ struct CCompletionOptionRenderInfo
CTextCursor m_Cursor;
const char *m_pCurrentCmd;
int m_WantedCompletion;
int m_EnumCount;
float m_Offset;
float *m_pOffsetChange;
float m_Width;
float m_TotalWidth;
};
void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser)
{
CCompletionOptionRenderInfo *pInfo = static_cast<CCompletionOptionRenderInfo *>(pUser);
if(pInfo->m_EnumCount == pInfo->m_WantedCompletion)
if(Index == pInfo->m_WantedCompletion)
{
float tw = pInfo->m_pSelf->TextRender()->TextWidth(pInfo->m_Cursor.m_pFont, pInfo->m_Cursor.m_FontSize, pStr, -1, -1.0f);
pInfo->m_pSelf->Graphics()->DrawRect(pInfo->m_Cursor.m_X - 2.5f, pInfo->m_Cursor.m_Y - 4.f / 2.f, tw + 5.f, pInfo->m_Cursor.m_FontSize + 4.f, ColorRGBA(229.0f / 255.0f, 185.0f / 255.0f, 4.0f / 255.0f, 0.85f), IGraphics::CORNER_ALL, pInfo->m_Cursor.m_FontSize / 3.f);
float TextWidth = pInfo->m_pSelf->TextRender()->TextWidth(pInfo->m_Cursor.m_pFont, pInfo->m_Cursor.m_FontSize, pStr, -1, -1.0f);
const CUIRect Rect = {pInfo->m_Cursor.m_X - 2.5f, pInfo->m_Cursor.m_Y - 4.f / 2.f, TextWidth + 5.f, pInfo->m_Cursor.m_FontSize + 4.f};
Rect.Draw(ColorRGBA(229.0f / 255.0f, 185.0f / 255.0f, 4.0f / 255.0f, 0.85f), IGraphics::CORNER_ALL, pInfo->m_Cursor.m_FontSize / 3.f);
// scroll when out of sight
if(pInfo->m_Cursor.m_X < 3.0f)
pInfo->m_Offset = 0.0f;
else if(pInfo->m_Cursor.m_X + tw > pInfo->m_Width)
pInfo->m_Offset -= pInfo->m_Width / 2;
const bool MoveLeft = Rect.x - *pInfo->m_pOffsetChange < 0.0f;
const bool MoveRight = Rect.x + Rect.w - *pInfo->m_pOffsetChange > pInfo->m_Width;
if(MoveLeft && !MoveRight)
*pInfo->m_pOffsetChange -= -Rect.x + pInfo->m_Width / 4.0f;
else if(!MoveLeft && MoveRight)
*pInfo->m_pOffsetChange += Rect.x + Rect.w - pInfo->m_Width + pInfo->m_Width / 4.0f;
pInfo->m_pSelf->TextRender()->TextColor(0.05f, 0.05f, 0.05f, 1);
pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -1);
@ -576,8 +581,8 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
}
}
pInfo->m_EnumCount++;
pInfo->m_Cursor.m_X += 7.0f;
pInfo->m_TotalWidth = pInfo->m_Cursor.m_X + pInfo->m_Offset;
}
void CGameConsole::OnRender()
@ -761,28 +766,30 @@ void CGameConsole::OnRender()
CCompletionOptionRenderInfo Info;
Info.m_pSelf = this;
Info.m_WantedCompletion = pConsole->m_CompletionChosen;
Info.m_EnumCount = 0;
Info.m_Offset = pConsole->m_CompletionRenderOffset;
Info.m_pOffsetChange = &pConsole->m_CompletionRenderOffsetChange;
Info.m_Width = Screen.w;
Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer;
TextRender()->SetCursor(&Info.m_Cursor, InitialX + Info.m_Offset, InitialY + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Info.m_Cursor.m_LineWidth = std::numeric_limits<float>::max();
m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
const int NumCommands = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
pConsole->m_CompletionRenderOffset = Info.m_Offset;
if(Info.m_EnumCount <= 0 && pConsole->m_IsCommand)
if(NumCommands <= 0 && pConsole->m_IsCommand)
{
const bool TuningCompletion = IsTuningCommandPrefix(Info.m_pCurrentCmd);
int NumArguments = 0;
if(TuningCompletion)
{
Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument;
Info.m_EnumCount = 0;
Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument;
m_pClient->m_aTuning[g_Config.m_ClDummy].PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info);
NumArguments = m_pClient->m_aTuning[g_Config.m_ClDummy].PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info);
pConsole->m_CompletionRenderOffset = Info.m_Offset;
}
if(Info.m_EnumCount <= 0 && pConsole->m_IsCommand)
if(NumArguments <= 0 && pConsole->m_IsCommand)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp);
@ -792,6 +799,8 @@ void CGameConsole::OnRender()
TextRender()->TextEx(&Info.m_Cursor, aBuf, -1);
}
}
UI()->DoSmoothScrollLogic(&pConsole->m_CompletionRenderOffset, &pConsole->m_CompletionRenderOffsetChange, Info.m_Width, Info.m_TotalWidth);
}
pConsole->m_BacklogLock.lock();

View file

@ -50,6 +50,7 @@ class CGameConsole : public CComponent
int m_CompletionChosenArgument;
int m_CompletionFlagmask;
float m_CompletionRenderOffset;
float m_CompletionRenderOffsetChange;
char m_aUser[32];
bool m_UserGot;

View file

@ -5,6 +5,7 @@
#include <base/math.h>
#include <base/system.h>
#include <engine/client.h>
#include <engine/graphics.h>
#include <engine/input.h>
#include <engine/keys.h>
@ -83,6 +84,7 @@ float CUI::ms_FontmodHeight = 0.8f;
void CUI::Init(IKernel *pKernel)
{
m_pClient = pKernel->RequestInterface<IClient>();
m_pGraphics = pKernel->RequestInterface<IGraphics>();
m_pInput = pKernel->RequestInterface<IInput>();
m_pTextRender = pKernel->RequestInterface<ITextRender>();
@ -372,6 +374,35 @@ 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)
{
// instant scrolling if distance too long
if(absolute(*pScrollOffsetChange) > ViewPortSize)
{
*pScrollOffset += *pScrollOffsetChange;
*pScrollOffsetChange = 0.0f;
}
// smooth scrolling
if(*pScrollOffsetChange)
{
const float Delta = *pScrollOffsetChange * clamp(Client()->RenderFrameTime() * ScrollSpeed, 0.0f, 1.0f);
*pScrollOffset += Delta;
*pScrollOffsetChange -= Delta;
}
// clamp to first item
if(*pScrollOffset < 0.0f)
{
*pScrollOffset = 0.0f;
*pScrollOffsetChange = 0.0f;
}
// clamp to last item
if(TotalSize > ViewPortSize && *pScrollOffset > TotalSize - ViewPortSize)
{
*pScrollOffset = TotalSize - ViewPortSize;
*pScrollOffsetChange = 0.0f;
}
}
float CUI::DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, const SLabelProperties &LabelProps)
{
float AlignedSize = 0;

View file

@ -12,6 +12,7 @@
#include <string>
#include <vector>
class IClient;
class IGraphics;
class IKernel;
@ -203,6 +204,7 @@ class CUI
std::vector<CUIRect> m_vClips;
void UpdateClipping();
IClient *m_pClient;
IGraphics *m_pGraphics;
IInput *m_pInput;
ITextRender *m_pTextRender;
@ -218,6 +220,7 @@ public:
void Init(IKernel *pKernel);
void InitInputs(IInput::CEvent *pInputEventsArray, int *pInputEventCount);
IClient *Client() const { return m_pClient; }
IGraphics *Graphics() const { return m_pGraphics; }
IInput *Input() const { return m_pInput; }
ITextRender *TextRender() const { return m_pTextRender; }
@ -306,6 +309,7 @@ public:
int DoButtonLogic(const void *pID, int Checked, const CUIRect *pRect);
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);
float DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {});
void DoLabel(const CUIRect *pRect, const char *pText, float Size, int Align, const SLabelProperties &LabelProps = {});