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() void CGameConsole::CInstance::Reset()
{ {
m_CompletionRenderOffset = 0.0f; m_CompletionRenderOffset = 0.0f;
m_CompletionRenderOffsetChange = 0.0f;
} }
void CGameConsole::CInstance::ExecuteLine(const char *pLine) void CGameConsole::CInstance::ExecuteLine(const char *pLine)
@ -533,25 +534,29 @@ struct CCompletionOptionRenderInfo
CTextCursor m_Cursor; CTextCursor m_Cursor;
const char *m_pCurrentCmd; const char *m_pCurrentCmd;
int m_WantedCompletion; int m_WantedCompletion;
int m_EnumCount;
float m_Offset; float m_Offset;
float *m_pOffsetChange;
float m_Width; float m_Width;
float m_TotalWidth;
}; };
void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser) void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, void *pUser)
{ {
CCompletionOptionRenderInfo *pInfo = static_cast<CCompletionOptionRenderInfo *>(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); float TextWidth = 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); 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 // scroll when out of sight
if(pInfo->m_Cursor.m_X < 3.0f) const bool MoveLeft = Rect.x - *pInfo->m_pOffsetChange < 0.0f;
pInfo->m_Offset = 0.0f; const bool MoveRight = Rect.x + Rect.w - *pInfo->m_pOffsetChange > pInfo->m_Width;
else if(pInfo->m_Cursor.m_X + tw > pInfo->m_Width) if(MoveLeft && !MoveRight)
pInfo->m_Offset -= pInfo->m_Width / 2; *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()->TextColor(0.05f, 0.05f, 0.05f, 1);
pInfo->m_pSelf->TextRender()->TextEx(&pInfo->m_Cursor, pStr, -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_Cursor.m_X += 7.0f;
pInfo->m_TotalWidth = pInfo->m_Cursor.m_X + pInfo->m_Offset;
} }
void CGameConsole::OnRender() void CGameConsole::OnRender()
@ -761,28 +766,30 @@ void CGameConsole::OnRender()
CCompletionOptionRenderInfo Info; CCompletionOptionRenderInfo Info;
Info.m_pSelf = this; Info.m_pSelf = this;
Info.m_WantedCompletion = pConsole->m_CompletionChosen; Info.m_WantedCompletion = pConsole->m_CompletionChosen;
Info.m_EnumCount = 0;
Info.m_Offset = pConsole->m_CompletionRenderOffset; Info.m_Offset = pConsole->m_CompletionRenderOffset;
Info.m_pOffsetChange = &pConsole->m_CompletionRenderOffsetChange;
Info.m_Width = Screen.w; Info.m_Width = Screen.w;
Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer; 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(); 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; 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); const bool TuningCompletion = IsTuningCommandPrefix(Info.m_pCurrentCmd);
int NumArguments = 0;
if(TuningCompletion) if(TuningCompletion)
{ {
Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument; Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument;
Info.m_EnumCount = 0; Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument; 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; pConsole->m_CompletionRenderOffset = Info.m_Offset;
} }
if(Info.m_EnumCount <= 0 && pConsole->m_IsCommand) if(NumArguments <= 0 && pConsole->m_IsCommand)
{ {
char aBuf[512]; char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp); str_format(aBuf, sizeof(aBuf), "Help: %s ", pConsole->m_aCommandHelp);
@ -792,6 +799,8 @@ void CGameConsole::OnRender()
TextRender()->TextEx(&Info.m_Cursor, aBuf, -1); 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(); pConsole->m_BacklogLock.lock();

View file

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

View file

@ -5,6 +5,7 @@
#include <base/math.h> #include <base/math.h>
#include <base/system.h> #include <base/system.h>
#include <engine/client.h>
#include <engine/graphics.h> #include <engine/graphics.h>
#include <engine/input.h> #include <engine/input.h>
#include <engine/keys.h> #include <engine/keys.h>
@ -83,6 +84,7 @@ float CUI::ms_FontmodHeight = 0.8f;
void CUI::Init(IKernel *pKernel) void CUI::Init(IKernel *pKernel)
{ {
m_pClient = pKernel->RequestInterface<IClient>();
m_pGraphics = pKernel->RequestInterface<IGraphics>(); m_pGraphics = pKernel->RequestInterface<IGraphics>();
m_pInput = pKernel->RequestInterface<IInput>(); m_pInput = pKernel->RequestInterface<IInput>();
m_pTextRender = pKernel->RequestInterface<ITextRender>(); m_pTextRender = pKernel->RequestInterface<ITextRender>();
@ -372,6 +374,35 @@ int CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float *
return 1; 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 CUI::DoTextLabel(float x, float y, float w, float h, const char *pText, float Size, int Align, const SLabelProperties &LabelProps)
{ {
float AlignedSize = 0; float AlignedSize = 0;

View file

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