diff options
author | oy <tom_adams@web.de> | 2023-06-16 21:12:46 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-16 21:12:46 +0200 |
commit | 8a18e656c9348995540d9b937d98ea3493098094 (patch) | |
tree | 32fa0f38ca9b14dff4bf547bca8a3f2621977e60 | |
parent | 507bfd08f2224ff13a0bfef4e2bde6473ed42d9d (diff) | |
parent | 369e85b6393410b27582abb1d116c142ced2d2d0 (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.cpp | 14 | ||||
-rw-r--r-- | src/game/client/components/console.cpp | 46 | ||||
-rw-r--r-- | src/game/client/lineinput.cpp | 17 | ||||
-rw-r--r-- | src/game/client/lineinput.h | 15 | ||||
-rw-r--r-- | src/game/client/ui.cpp | 93 | ||||
-rw-r--r-- | src/game/client/ui.h | 1 |
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); |