mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-21 10:04:18 +00:00
e7c3ce186f
Move hotkey/input handling from `CMenus` to `CUI`. Using the `ConsumeHotkey` method ensures that each hotkey is only handled once. By also handling the mouse scroll wheel as hotkeys, this fixes the scroll keys activating scroll regions while the controls binder is active.
221 lines
6.2 KiB
C++
221 lines
6.2 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 "ui_scrollregion.h"
|
|
|
|
CScrollRegion::CScrollRegion()
|
|
{
|
|
m_ScrollY = 0.0f;
|
|
m_ContentH = 0.0f;
|
|
m_AnimTime = 0.0f;
|
|
m_AnimInitScrollY = 0.0f;
|
|
m_AnimTargetScrollY = 0.0f;
|
|
m_RequestScrollY = -1.0f;
|
|
m_ContentScrollOff = vec2(0.0f, 0.0f);
|
|
m_Params = CScrollRegionParams();
|
|
}
|
|
|
|
void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, 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;
|
|
|
|
const float AnimationDuration = 0.5f;
|
|
|
|
if(UI()->Enabled() && UI()->MouseHovered(&RegionRect))
|
|
{
|
|
const bool IsPageScroll = Input()->KeyIsPressed(KEY_LALT) || Input()->KeyIsPressed(KEY_RALT);
|
|
const float ScrollUnit = IsPageScroll ? m_ClipRect.h : m_Params.m_ScrollUnit;
|
|
if(UI()->ConsumeHotkey(CUI::HOTKEY_SCROLL_UP))
|
|
{
|
|
m_AnimTime = AnimationDuration;
|
|
m_AnimInitScrollY = m_ScrollY;
|
|
m_AnimTargetScrollY -= ScrollUnit;
|
|
}
|
|
else if(UI()->ConsumeHotkey(CUI::HOTKEY_SCROLL_DOWN))
|
|
{
|
|
m_AnimTime = AnimationDuration;
|
|
m_AnimInitScrollY = m_ScrollY;
|
|
m_AnimTargetScrollY += ScrollUnit;
|
|
}
|
|
}
|
|
|
|
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 - powf(m_AnimTime / AnimationDuration, 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 Hovered = false;
|
|
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;
|
|
}
|
|
Hovered = true;
|
|
}
|
|
else if(InsideRail && UI()->MouseButtonClicked(0))
|
|
{
|
|
m_ScrollY += (UI()->MouseY() - (Slider.y + Slider.h / 2.0f)) / MaxSlider * MaxScroll;
|
|
UI()->SetActiveItem(pID);
|
|
m_SliderGrabPos.y = Slider.h / 2.0f;
|
|
m_AnimTargetScrollY = m_ScrollY;
|
|
m_AnimTime = 0.0f;
|
|
Hovered = 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, Hovered), 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(ceilf(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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|