summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoroy <tom_adams@web.de>2023-06-16 21:12:46 +0200
committerGitHub <noreply@github.com>2023-06-16 21:12:46 +0200
commit8a18e656c9348995540d9b937d98ea3493098094 (patch)
tree32fa0f38ca9b14dff4bf547bca8a3f2621977e60
parent507bfd08f2224ff13a0bfef4e2bde6473ed42d9d (diff)
parent369e85b6393410b27582abb1d116c142ced2d2d0 (diff)
Merge pull request #3150 from Robyt3/CLineInput-Smooth-Scrolling
Implement smooth scrolling for UI editbox and chat input, don't blink the caret shortly after moving
-rw-r--r--src/game/client/components/chat.cpp14
-rw-r--r--src/game/client/components/console.cpp46
-rw-r--r--src/game/client/lineinput.cpp17
-rw-r--r--src/game/client/lineinput.h15
-rw-r--r--src/game/client/ui.cpp93
-rw-r--r--src/game/client/ui.h1
6 files changed, 104 insertions, 82 deletions
diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp
index 15f29a370..b6ff10a6a 100644
--- a/src/game/client/components/chat.cpp
+++ b/src/game/client/components/chat.cpp
@@ -928,6 +928,7 @@ void CChat::OnRender()
pCursor->m_MaxWidth = Width-190.0f-s_CategoryCursor.Width();
float ScrollOffset = m_Input.GetScrollOffset();
+ float ScrollOffsetChange = m_Input.GetScrollOffsetChange();
pCursor->MoveTo(CursorPosition.x, CursorPosition.y - ScrollOffset);
pCursor->m_MaxLines = -1;
pCursor->m_Flags = TEXTFLAG_WORD_WRAP;
@@ -973,15 +974,20 @@ void CChat::OnRender()
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));
- m_Input.Render();
+ m_Input.Render(m_Input.WasChanged());
Graphics()->ClipDisable();
// scroll to keep the caret inside the clipping rect
- const float CaretPositionY = m_Input.GetCaretPosition().y + InputFontSize * 0.5f;
+ const float CaretPositionY = m_Input.GetCaretPosition().y + InputFontSize * 0.5f - ScrollOffsetChange;
if(CaretPositionY < ClippingRect.y)
- m_Input.SetScrollOffset(maximum(0.0f, ScrollOffset - InputFontSize));
+ ScrollOffsetChange -= InputFontSize;
else if(CaretPositionY + InputFontSize * 0.35f > ClippingRect.y + ClippingRect.h)
- m_Input.SetScrollOffset(ScrollOffset + InputFontSize);
+ ScrollOffsetChange += InputFontSize;
+
+ UI()->DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, ClippingRect.h, pCursor->BoundingBox().h);
+
+ m_Input.SetScrollOffset(ScrollOffset);
+ m_Input.SetScrollOffsetChange(ScrollOffsetChange);
}
}
diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp
index 6fa513200..074a84bbb 100644
--- a/src/game/client/components/console.cpp
+++ b/src/game/client/components/console.cpp
@@ -378,7 +378,7 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
if(pInfo->m_EnumCount == pInfo->m_WantedCompletion)
{
pInfo->m_pSelf->TextRender()->TextColor(0.05f, 0.05f, 0.05f,1);
- const float BeginX = pInfo->m_pCursor->AdvancePosition().x - pInfo->m_Offset;
+ const float BeginX = pInfo->m_pCursor->AdvancePosition().x + pInfo->m_Offset;
pInfo->m_pSelf->TextRender()->TextDeferred(pInfo->m_pCursor, pStr, -1);
CTextBoundingBox Box = pInfo->m_pCursor->BoundingBox();
CUIRect Rect = {Box.x - 5 + BeginX, Box.y, Box.w + 8 - BeginX, Box.h};
@@ -386,10 +386,12 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
Rect.Draw(vec4(229.0f/255.0f,185.0f/255.0f,4.0f/255.0f,0.85f), pInfo->m_pCursor->m_FontSize/3);
// scroll when out of sight
- if(Rect.x + *pInfo->m_pOffsetChange < 0.0f)
- *pInfo->m_pOffsetChange += -Rect.x + pInfo->m_Width/4.0f;
- else if(Rect.x + Rect.w + *pInfo->m_pOffsetChange > pInfo->m_Width)
- *pInfo->m_pOffsetChange -= Rect.x + Rect.w - pInfo->m_Width + pInfo->m_Width/4.0f;
+ 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;
}
else
{
@@ -412,7 +414,7 @@ void CGameConsole::PossibleCommandsRenderCallback(int Index, const char *pStr, v
pInfo->m_EnumCount++;
pInfo->m_pSelf->TextRender()->TextAdvance(pInfo->m_pCursor, 7.0f);
- pInfo->m_TotalWidth = pInfo->m_pCursor->AdvancePosition().x - pInfo->m_Offset;
+ pInfo->m_TotalWidth = pInfo->m_pCursor->AdvancePosition().x + pInfo->m_Offset;
}
void CGameConsole::OnRender()
@@ -519,7 +521,7 @@ void CGameConsole::OnRender()
static CTextCursor s_CompletionOptionsCursor;
s_CompletionOptionsCursor.Reset();
- s_CompletionOptionsCursor.MoveTo(x+pConsole->m_CompletionRenderOffset, y+RowHeight+2.0f);
+ s_CompletionOptionsCursor.MoveTo(x - pConsole->m_CompletionRenderOffset, y + RowHeight + 2.0f);
s_CompletionOptionsCursor.m_FontSize = FontSize;
s_CompletionOptionsCursor.m_MaxWidth = -1.0f;
@@ -560,12 +562,11 @@ void CGameConsole::OnRender()
pInputCursor->MoveTo(x, y + FontSize * 1.35f);
pConsole->m_Input.Activate(CONSOLE); // ensure the input is active
- pConsole->m_Input.Render();
+ pConsole->m_Input.Render(pConsole->m_Input.WasChanged());
y -= (pInputCursor->LineCount() - 1) * FontSize;
// render possible commands
- static float s_LastRender = Now;
if((m_ConsoleType == CONSOLETYPE_LOCAL || Client()->RconAuthed()) && pConsole->m_Input.GetString()[0])
{
CCompletionOptionRenderInfo Info;
@@ -610,33 +611,8 @@ void CGameConsole::OnRender()
TextRender()->TextDeferred(&s_InfoCursor, aBuf, -1);
}
- // instant scrolling if distance too long
- if(absolute(pConsole->m_CompletionRenderOffsetChange) > Info.m_Width)
- {
- pConsole->m_CompletionRenderOffset += pConsole->m_CompletionRenderOffsetChange;
- pConsole->m_CompletionRenderOffsetChange = 0.0f;
- }
- // smooth scrolling
- if(pConsole->m_CompletionRenderOffsetChange)
- {
- const float Delta = pConsole->m_CompletionRenderOffsetChange * clamp((Now - s_LastRender) * 10.0f, 0.0f, 1.0f);
- pConsole->m_CompletionRenderOffset += Delta;
- pConsole->m_CompletionRenderOffsetChange -= Delta;
- }
- // clamp to first item
- if(pConsole->m_CompletionRenderOffset > 0.0f)
- {
- pConsole->m_CompletionRenderOffset = 0.0f;
- pConsole->m_CompletionRenderOffsetChange = 0.0f;
- }
- // clamp to last item
- if(Info.m_TotalWidth > Info.m_Width && pConsole->m_CompletionRenderOffset < Info.m_Width - Info.m_TotalWidth)
- {
- pConsole->m_CompletionRenderOffset = Info.m_Width - Info.m_TotalWidth;
- pConsole->m_CompletionRenderOffsetChange = 0.0f;
- }
+ UI()->DoSmoothScrollLogic(&pConsole->m_CompletionRenderOffset, &pConsole->m_CompletionRenderOffsetChange, Info.m_Width, Info.m_TotalWidth);
}
- s_LastRender = Now;
TextRender()->DrawTextOutlined(&s_CompletionOptionsCursor);
TextRender()->DrawTextOutlined(&s_InfoCursor);
diff --git a/src/game/client/lineinput.cpp b/src/game/client/lineinput.cpp
index 49614fd66..ff6752f36 100644
--- a/src/game/client/lineinput.cpp
+++ b/src/game/client/lineinput.cpp
@@ -12,6 +12,7 @@
IInput *CLineInput::s_pInput = 0;
ITextRender *CLineInput::s_pTextRender = 0;
IGraphics *CLineInput::s_pGraphics = 0;
+IClient *CLineInput::s_pClient = 0;
CLineInput *CLineInput::s_pActiveInput = 0;
EInputPriority CLineInput::s_ActiveInputPriority = NONE;
@@ -32,7 +33,7 @@ void CLineInput::SetBuffer(char *pStr, int MaxSize, int MaxChars)
m_WasChanged = m_pStr && pLastStr && m_WasChanged;
if(!pLastStr)
{
- m_ScrollOffset = 0;
+ m_ScrollOffset = m_ScrollOffsetChange = 0.0f;
m_CaretPosition = vec2(0, 0);
m_Hidden = false;
}
@@ -352,7 +353,7 @@ bool CLineInput::ProcessInput(const IInput::CEvent &Event)
return m_WasChanged;
}
-void CLineInput::Render()
+void CLineInput::Render(bool Changed)
{
m_TextCursor.Reset();
@@ -399,9 +400,15 @@ void CLineInput::Render()
s_pTextRender->TextDeferred(&s_MarkerCursor, "|", -1);
s_MarkerCursor.MoveTo(s_pTextRender->CaretPosition(&m_TextCursor, HasComposition ? DisplayCompositionStart : DisplayCursorOffset));
- // render blinking caret
- if((2*time_get()/time_freq())%2)
- s_pTextRender->DrawTextOutlined(&s_MarkerCursor);
+ // render blinking caret, don't blink shortly after caret has been moved
+ {
+ const float LocalTime = s_pClient->LocalTime();
+ static float s_LastChanged = 0.0f;
+ if(Changed)
+ s_LastChanged = LocalTime;
+ if(fmod(LocalTime - s_LastChanged, 1.0f) < 0.5f)
+ s_pTextRender->DrawTextOutlined(&s_MarkerCursor);
+ }
m_CaretPosition = s_pTextRender->CaretPosition(&m_TextCursor, DisplayCursorOffset);
s_MarkerCursor.MoveTo(m_CaretPosition);
diff --git a/src/game/client/lineinput.h b/src/game/client/lineinput.h
index f7f75abbf..6ab4d5d46 100644
--- a/src/game/client/lineinput.h
+++ b/src/game/client/lineinput.h
@@ -5,6 +5,7 @@
#include <base/vmath.h>
+#include <engine/client.h>
#include <engine/graphics.h>
#include <engine/input.h>
#include <engine/textrender.h>
@@ -23,6 +24,7 @@ class CLineInput
static IInput *s_pInput;
static ITextRender *s_pTextRender;
static IGraphics *s_pGraphics;
+ static IClient *s_pClient;
static CLineInput *s_pActiveInput;
static EInputPriority s_ActiveInputPriority;
@@ -44,6 +46,7 @@ class CLineInput
int m_SelectionEnd;
float m_ScrollOffset;
+ float m_ScrollOffsetChange;
vec2 m_CaretPosition;
bool m_Hidden;
@@ -63,7 +66,13 @@ class CLineInput
void OnDeactivate();
public:
- static void Init(IInput *pInput, ITextRender *pTextRender, IGraphics *pGraphics) { s_pInput = pInput; s_pTextRender = pTextRender; s_pGraphics = pGraphics; }
+ static void Init(IInput *pInput, ITextRender *pTextRender, IGraphics *pGraphics, IClient *pClient)
+ {
+ s_pInput = pInput;
+ s_pTextRender = pTextRender;
+ s_pGraphics = pGraphics;
+ s_pClient = pClient;
+ }
static void RenderCandidates();
static CLineInput *GetActiveInput() { return s_pActiveInput; }
@@ -102,6 +111,8 @@ public:
// 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
@@ -111,7 +122,7 @@ public:
bool ProcessInput(const IInput::CEvent &Event);
bool WasChanged() { bool Changed = m_WasChanged; m_WasChanged = false; return Changed; }
- void Render();
+ void Render(bool Changed);
bool IsActive() const { return GetActiveInput() == this; }
void Activate(EInputPriority Priority);
diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp
index b71bec531..6b5815311 100644
--- a/src/game/client/ui.cpp
+++ b/src/game/client/ui.cpp
@@ -84,7 +84,7 @@ void CUI::Init(class IKernel *pKernel)
m_pInput = pKernel->RequestInterface<IInput>();
m_pTextRender = pKernel->RequestInterface<ITextRender>();
CUIRect::Init(m_pGraphics);
- CLineInput::Init(m_pInput, m_pTextRender, m_pGraphics);
+ CLineInput::Init(m_pInput, m_pTextRender, m_pGraphics, m_pClient);
CUIElementBase::Init(this);
}
@@ -305,6 +305,35 @@ bool CUI::DoPickerLogic(const void *pID, const CUIRect *pRect, float *pX, float
return true;
}
+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;
+ }
+}
+
void CUI::ApplyCursorAlign(class CTextCursor *pCursor, const CUIRect *pRect, int Align)
{
pCursor->m_Align = Align;
@@ -389,6 +418,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
bool UpdateOffset = false;
float ScrollOffset = pLineInput->GetScrollOffset();
+ float ScrollOffsetChange = pLineInput->GetScrollOffsetChange();
static bool s_DoScroll = false;
static int s_SelectionStartOffset = -1;
@@ -397,16 +427,14 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
CUIRect Textbox;
pRect->VMargin(VSpacing, &Textbox);
+ float ScrollSpeed = 10.0f;
if(Active)
{
- static float s_ScrollStartX = 0.0f;
-
int CursorOffset = pLineInput->GetCursorOffset();
if(Inside && MouseButton(0) && !Changed)
{
s_DoScroll = true;
- s_ScrollStartX = MouseX();
const float MxRel = MouseX() - Textbox.x;
float TotalTextWidth = 0.0f;
for(int i = 1, Offset = 0; i <= pLineInput->GetNumChars(); i++)
@@ -414,11 +442,12 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
const int PrevOffset = Offset;
Offset = str_utf8_forward(pDisplayStr, Offset);
const float AddedTextWidth = TextRender()->TextWidth(FontSize, pDisplayStr + PrevOffset, Offset - PrevOffset);
- if(TotalTextWidth + AddedTextWidth/2.0f - ScrollOffset > MxRel)
+ if(TotalTextWidth + AddedTextWidth/2.0f - ScrollOffset - ScrollOffsetChange > MxRel)
{
CursorOffset = PrevOffset;
if(s_SelectionStartOffset < 0)
s_SelectionStartOffset = CursorOffset;
+ UpdateOffset = true;
break;
}
TotalTextWidth += AddedTextWidth;
@@ -428,6 +457,7 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
CursorOffset = pLineInput->GetLength();
if(s_SelectionStartOffset < 0)
s_SelectionStartOffset = CursorOffset;
+ UpdateOffset = true;
}
}
}
@@ -438,18 +468,20 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
}
else if(s_DoScroll)
{
- // do scrolling
- if(MouseX() < Textbox.x && s_ScrollStartX-MouseX() > 10.0f)
- {
- CursorOffset = str_utf8_rewind(pDisplayStr, CursorOffset);
- s_ScrollStartX = MouseX();
- UpdateOffset = true;
- }
- else if(MouseX() > Textbox.x+Textbox.w && MouseX()-s_ScrollStartX > 10.0f)
+ if(absolute(ScrollOffsetChange) < 10.0f)
{
- CursorOffset = str_utf8_forward(pDisplayStr, CursorOffset);
- s_ScrollStartX = MouseX();
- UpdateOffset = true;
+ if(MouseX() < Textbox.x)
+ {
+ CursorOffset = str_utf8_rewind(pDisplayStr, CursorOffset);
+ ScrollSpeed *= clamp(Textbox.x - MouseX(), 1.0f, Textbox.w / 8.0f);
+ UpdateOffset = true;
+ }
+ else if(MouseX() > Textbox.x + Textbox.w)
+ {
+ CursorOffset = str_utf8_forward(pDisplayStr, CursorOffset);
+ ScrollSpeed *= clamp(MouseX() - Textbox.x - Textbox.w, 1.0f, Textbox.w / 8.0f);
+ UpdateOffset = true;
+ }
}
}
else if(!Inside && MouseButton(0))
@@ -513,34 +545,23 @@ bool CUI::DoEditBox(CLineInput *pLineInput, const CUIRect *pRect, float FontSize
ClipEnable(pRect);
Textbox.x -= ScrollOffset;
ApplyCursorAlign(pCursor, &Textbox, TEXTALIGN_ML);
- pLineInput->Render();
+ pLineInput->Render(UpdateOffset || Changed);
ClipDisable();
// check if the text has to be moved
if(Active && !JustGotActive && (UpdateOffset || Changed))
{
- const float CaretX = pLineInput->GetCaretPosition().x - Textbox.x;
- if(CaretX - ScrollOffset > Textbox.w)
- {
- // move to the left
- do
- {
- ScrollOffset += clamp(pLineInput->GetCursor()->Width() - ScrollOffset - Textbox.w, 0.1f, Textbox.w / 3.0f);
- }
- while(CaretX - ScrollOffset > Textbox.w);
- }
- else if(CaretX - ScrollOffset < 0.0f)
- {
- // move to the right
- do
- {
- ScrollOffset = maximum(0.0f, ScrollOffset - Textbox.w / 3.0f);
- }
- while(CaretX - ScrollOffset < 0.0f);
- }
+ const float CaretX = pLineInput->GetCaretPosition().x - Textbox.x - ScrollOffset - ScrollOffsetChange;
+ if(CaretX > Textbox.w)
+ ScrollOffsetChange += CaretX - Textbox.w;
+ else if(CaretX < 0.0f)
+ ScrollOffsetChange += CaretX;
}
+ DoSmoothScrollLogic(&ScrollOffset, &ScrollOffsetChange, Textbox.w, pCursor->Width(), ScrollSpeed);
+
pLineInput->SetScrollOffset(ScrollOffset);
+ pLineInput->SetScrollOffsetChange(ScrollOffsetChange);
return Changed;
}
diff --git a/src/game/client/ui.h b/src/game/client/ui.h
index f541013b1..943dec673 100644
--- a/src/game/client/ui.h
+++ b/src/game/client/ui.h
@@ -259,6 +259,7 @@ public:
bool DoButtonLogic(const void *pID, const CUIRect *pRect, int Button = 0);
bool 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);
// labels
void DoLabel(const CUIRect *pRect, const char *pText, float FontSize, int Align = TEXTALIGN_TL, float LineWidth = -1.0f, bool MultiLine = true);