mirror of
https://github.com/ddnet/ddnet.git
synced 2024-10-21 00:08:19 +00:00
55726e8da3
Store the list box active state inside `CListBox` instead of tracking it separately with a pointer. Allow activating list boxes by clicking the scrollbar. Previously it was only possible to activate list boxes by selecting an item. Fix country selection list box in players settings not being deactivated properly when name/clan edit boxes are active, because wrong UI element IDs were being used.
256 lines
7.6 KiB
C++
256 lines
7.6 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
|
#include <base/system.h>
|
|
#include <base/vmath.h>
|
|
|
|
#include <engine/client.h>
|
|
#include <engine/keys.h>
|
|
#include <engine/shared/config.h>
|
|
|
|
#include "ui_scrollregion.h"
|
|
|
|
CScrollRegion::CScrollRegion()
|
|
{
|
|
m_ScrollY = 0.0f;
|
|
m_ContentH = 0.0f;
|
|
m_RequestScrollY = -1.0f;
|
|
m_ScrollDirection = SCROLLRELATIVE_NONE;
|
|
m_ScrollSpeedMultiplier = 1.0f;
|
|
|
|
m_AnimTimeMax = 0.0f;
|
|
m_AnimTime = 0.0f;
|
|
m_AnimInitScrollY = 0.0f;
|
|
m_AnimTargetScrollY = 0.0f;
|
|
|
|
m_ContentScrollOff = vec2(0.0f, 0.0f);
|
|
m_Params = CScrollRegionParams();
|
|
}
|
|
|
|
void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, const CScrollRegionParams *pParams)
|
|
{
|
|
if(pParams)
|
|
m_Params = *pParams;
|
|
|
|
const bool ContentOverflows = m_ContentH > pClipRect->h;
|
|
const bool ForceShowScrollbar = m_Params.m_Flags & CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH;
|
|
|
|
CUIRect ScrollBarBg;
|
|
bool HasScrollBar = ContentOverflows || ForceShowScrollbar;
|
|
CUIRect *pModifyRect = HasScrollBar ? pClipRect : nullptr;
|
|
pClipRect->VSplitRight(m_Params.m_ScrollbarWidth, pModifyRect, &ScrollBarBg);
|
|
ScrollBarBg.Margin(m_Params.m_ScrollbarMargin, &m_RailRect);
|
|
|
|
// only show scrollbar if required
|
|
if(HasScrollBar)
|
|
{
|
|
if(m_Params.m_ScrollbarBgColor.a > 0.0f)
|
|
ScrollBarBg.Draw(m_Params.m_ScrollbarBgColor, IGraphics::CORNER_R, 4.0f);
|
|
if(m_Params.m_RailBgColor.a > 0.0f)
|
|
m_RailRect.Draw(m_Params.m_RailBgColor, IGraphics::CORNER_ALL, m_RailRect.w / 2.0f);
|
|
}
|
|
if(!ContentOverflows)
|
|
m_ContentScrollOff.y = 0.0f;
|
|
|
|
if(m_Params.m_ClipBgColor.a > 0.0f)
|
|
pClipRect->Draw(m_Params.m_ClipBgColor, HasScrollBar ? IGraphics::CORNER_L : IGraphics::CORNER_ALL, 4.0f);
|
|
|
|
UI()->ClipEnable(pClipRect);
|
|
|
|
m_ClipRect = *pClipRect;
|
|
m_ContentH = 0.0f;
|
|
*pOutOffset = m_ContentScrollOff;
|
|
}
|
|
|
|
void CScrollRegion::End()
|
|
{
|
|
UI()->ClipDisable();
|
|
|
|
// only show scrollbar if content overflows
|
|
if(m_ContentH <= m_ClipRect.h)
|
|
return;
|
|
|
|
// scroll wheel
|
|
CUIRect RegionRect = m_ClipRect;
|
|
RegionRect.w += m_Params.m_ScrollbarWidth;
|
|
|
|
if(m_ScrollDirection != SCROLLRELATIVE_NONE || (UI()->Enabled() && m_Params.m_Active && UI()->MouseHovered(&RegionRect)))
|
|
{
|
|
bool ProgrammaticScroll = false;
|
|
if(UI()->ConsumeHotkey(CUI::HOTKEY_SCROLL_UP))
|
|
m_ScrollDirection = SCROLLRELATIVE_UP;
|
|
else if(UI()->ConsumeHotkey(CUI::HOTKEY_SCROLL_DOWN))
|
|
m_ScrollDirection = SCROLLRELATIVE_DOWN;
|
|
else
|
|
ProgrammaticScroll = true;
|
|
|
|
if(!ProgrammaticScroll)
|
|
m_ScrollSpeedMultiplier = 1.0f;
|
|
|
|
if(m_ScrollDirection != SCROLLRELATIVE_NONE)
|
|
{
|
|
const bool IsPageScroll = Input()->AltIsPressed();
|
|
const float ScrollUnit = IsPageScroll && !ProgrammaticScroll ? m_ClipRect.h : m_Params.m_ScrollUnit;
|
|
|
|
m_AnimTimeMax = g_Config.m_UiSmoothScrollTime / 1000.0f;
|
|
m_AnimTime = m_AnimTimeMax;
|
|
m_AnimInitScrollY = m_ScrollY;
|
|
m_AnimTargetScrollY = (ProgrammaticScroll ? m_ScrollY : m_AnimTargetScrollY) + (int)m_ScrollDirection * ScrollUnit * m_ScrollSpeedMultiplier;
|
|
m_ScrollDirection = SCROLLRELATIVE_NONE;
|
|
m_ScrollSpeedMultiplier = 1.0f;
|
|
}
|
|
}
|
|
|
|
const float SliderHeight = maximum(m_Params.m_SliderMinHeight, m_ClipRect.h / m_ContentH * m_RailRect.h);
|
|
|
|
CUIRect Slider = m_RailRect;
|
|
Slider.h = SliderHeight;
|
|
|
|
const float MaxSlider = m_RailRect.h - SliderHeight;
|
|
const float MaxScroll = m_ContentH - m_ClipRect.h;
|
|
|
|
if(m_RequestScrollY >= 0.0f)
|
|
{
|
|
m_AnimTargetScrollY = m_RequestScrollY;
|
|
m_AnimTime = 0.0f;
|
|
m_RequestScrollY = -1.0f;
|
|
}
|
|
|
|
m_AnimTargetScrollY = clamp(m_AnimTargetScrollY, 0.0f, MaxScroll);
|
|
|
|
if(absolute(m_AnimInitScrollY - m_AnimTargetScrollY) < 0.5f)
|
|
m_AnimTime = 0.0f;
|
|
|
|
if(m_AnimTime > 0.0f)
|
|
{
|
|
m_AnimTime -= Client()->RenderFrameTime();
|
|
float AnimProgress = (1.0f - std::pow(m_AnimTime / m_AnimTimeMax, 3.0f)); // cubic ease out
|
|
m_ScrollY = m_AnimInitScrollY + (m_AnimTargetScrollY - m_AnimInitScrollY) * AnimProgress;
|
|
}
|
|
else
|
|
{
|
|
m_ScrollY = m_AnimTargetScrollY;
|
|
}
|
|
|
|
Slider.y += m_ScrollY / MaxScroll * MaxSlider;
|
|
|
|
bool Grabbed = false;
|
|
const void *pID = &m_ScrollY;
|
|
const bool InsideSlider = UI()->MouseHovered(&Slider);
|
|
const bool InsideRail = UI()->MouseHovered(&m_RailRect);
|
|
|
|
if(UI()->CheckActiveItem(pID) && UI()->MouseButton(0))
|
|
{
|
|
float MouseY = UI()->MouseY();
|
|
m_ScrollY += (MouseY - (Slider.y + m_SliderGrabPos.y)) / MaxSlider * MaxScroll;
|
|
m_SliderGrabPos.y = clamp(m_SliderGrabPos.y, 0.0f, SliderHeight);
|
|
m_AnimTargetScrollY = m_ScrollY;
|
|
m_AnimTime = 0.0f;
|
|
Grabbed = true;
|
|
}
|
|
else if(InsideSlider)
|
|
{
|
|
UI()->SetHotItem(pID);
|
|
|
|
if(!UI()->CheckActiveItem(pID) && UI()->MouseButtonClicked(0))
|
|
{
|
|
UI()->SetActiveItem(pID);
|
|
m_SliderGrabPos.y = UI()->MouseY() - Slider.y;
|
|
m_AnimTargetScrollY = m_ScrollY;
|
|
m_AnimTime = 0.0f;
|
|
m_Params.m_Active = true;
|
|
}
|
|
}
|
|
else if(InsideRail && UI()->MouseButtonClicked(0))
|
|
{
|
|
m_ScrollY += (UI()->MouseY() - (Slider.y + Slider.h / 2.0f)) / MaxSlider * MaxScroll;
|
|
UI()->SetHotItem(pID);
|
|
UI()->SetActiveItem(pID);
|
|
m_SliderGrabPos.y = Slider.h / 2.0f;
|
|
m_AnimTargetScrollY = m_ScrollY;
|
|
m_AnimTime = 0.0f;
|
|
m_Params.m_Active = true;
|
|
}
|
|
else if(UI()->CheckActiveItem(pID) && !UI()->MouseButton(0))
|
|
{
|
|
UI()->SetActiveItem(nullptr);
|
|
}
|
|
|
|
m_ScrollY = clamp(m_ScrollY, 0.0f, MaxScroll);
|
|
m_ContentScrollOff.y = -m_ScrollY;
|
|
|
|
Slider.Draw(m_Params.SliderColor(Grabbed, UI()->HotItem() == pID), IGraphics::CORNER_ALL, Slider.w / 2.0f);
|
|
}
|
|
|
|
bool CScrollRegion::AddRect(const CUIRect &Rect, bool ShouldScrollHere)
|
|
{
|
|
m_LastAddedRect = Rect;
|
|
// Round up and add 1 to fix pixel clipping at the end of the scrolling area
|
|
m_ContentH = maximum(std::ceil(Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + 1.0f, m_ContentH);
|
|
if(ShouldScrollHere)
|
|
ScrollHere();
|
|
return !IsRectClipped(Rect);
|
|
}
|
|
|
|
void CScrollRegion::ScrollHere(EScrollOption Option)
|
|
{
|
|
const float MinHeight = minimum(m_ClipRect.h, m_LastAddedRect.h);
|
|
const float TopScroll = m_LastAddedRect.y - (m_ClipRect.y + m_ContentScrollOff.y);
|
|
|
|
switch(Option)
|
|
{
|
|
case SCROLLHERE_TOP:
|
|
m_RequestScrollY = TopScroll;
|
|
break;
|
|
|
|
case SCROLLHERE_BOTTOM:
|
|
m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight);
|
|
break;
|
|
|
|
case SCROLLHERE_KEEP_IN_VIEW:
|
|
default:
|
|
const float DeltaY = m_LastAddedRect.y - m_ClipRect.y;
|
|
if(DeltaY < 0)
|
|
m_RequestScrollY = TopScroll;
|
|
else if(DeltaY > (m_ClipRect.h - MinHeight))
|
|
m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CScrollRegion::ScrollRelative(EScrollRelative Direction, float SpeedMultiplier)
|
|
{
|
|
m_ScrollDirection = Direction;
|
|
m_ScrollSpeedMultiplier = SpeedMultiplier;
|
|
}
|
|
|
|
void CScrollRegion::DoEdgeScrolling()
|
|
{
|
|
if(!IsScrollbarShown())
|
|
return;
|
|
|
|
const float ScrollBorderSize = 20.0f;
|
|
const float MaxScrollMultiplier = 2.0f;
|
|
const float ScrollSpeedFactor = MaxScrollMultiplier / ScrollBorderSize;
|
|
const float TopScrollPosition = m_ClipRect.y + ScrollBorderSize;
|
|
const float BottomScrollPosition = m_ClipRect.y + m_ClipRect.h - ScrollBorderSize;
|
|
if(UI()->MouseY() < TopScrollPosition)
|
|
ScrollRelative(SCROLLRELATIVE_UP, minimum(MaxScrollMultiplier, (TopScrollPosition - UI()->MouseY()) * ScrollSpeedFactor));
|
|
else if(UI()->MouseY() > BottomScrollPosition)
|
|
ScrollRelative(SCROLLRELATIVE_DOWN, minimum(MaxScrollMultiplier, (UI()->MouseY() - BottomScrollPosition) * ScrollSpeedFactor));
|
|
}
|
|
|
|
bool CScrollRegion::IsRectClipped(const CUIRect &Rect) const
|
|
{
|
|
return (m_ClipRect.x > (Rect.x + Rect.w) || (m_ClipRect.x + m_ClipRect.w) < Rect.x || m_ClipRect.y > (Rect.y + Rect.h) || (m_ClipRect.y + m_ClipRect.h) < Rect.y);
|
|
}
|
|
|
|
bool CScrollRegion::IsScrollbarShown() const
|
|
{
|
|
return m_ContentH > m_ClipRect.h;
|
|
}
|
|
|
|
bool CScrollRegion::IsAnimating() const
|
|
{
|
|
return m_AnimTime > 0.0f;
|
|
}
|