mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Port CScrollRegion
from upstream with some minor refactorings
Co-authored-by: LordSk <lordskelethom@gmail.com>
This commit is contained in:
parent
9cbca642ea
commit
f451a33361
|
@ -2103,6 +2103,8 @@ if(CLIENT)
|
|||
ui.h
|
||||
ui_rect.cpp
|
||||
ui_rect.h
|
||||
ui_scrollregion.cpp
|
||||
ui_scrollregion.h
|
||||
)
|
||||
set_src(GAME_EDITOR GLOB src/game/editor
|
||||
auto_map.cpp
|
||||
|
|
|
@ -374,6 +374,8 @@ void CGameClient::OnInit()
|
|||
|
||||
void CGameClient::OnUpdate()
|
||||
{
|
||||
CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI
|
||||
|
||||
// handle mouse movement
|
||||
float x = 0.0f, y = 0.0f;
|
||||
IInput::ECursorType CursorType = Input()->CursorRelative(&x, &y);
|
||||
|
|
|
@ -82,6 +82,13 @@ const CLinearScrollbarScale CUI::ms_LinearScrollbarScale;
|
|||
const CLogarithmicScrollbarScale CUI::ms_LogarithmicScrollbarScale(25);
|
||||
float CUI::ms_FontmodHeight = 0.8f;
|
||||
|
||||
CUI *CUIElementBase::s_pUI = nullptr;
|
||||
|
||||
IClient *CUIElementBase::Client() const { return s_pUI->Client(); }
|
||||
IGraphics *CUIElementBase::Graphics() const { return s_pUI->Graphics(); }
|
||||
IInput *CUIElementBase::Input() const { return s_pUI->Input(); }
|
||||
ITextRender *CUIElementBase::TextRender() const { return s_pUI->TextRender(); }
|
||||
|
||||
void CUI::Init(IKernel *pKernel)
|
||||
{
|
||||
m_pClient = pKernel->RequestInterface<IClient>();
|
||||
|
@ -90,6 +97,7 @@ void CUI::Init(IKernel *pKernel)
|
|||
m_pTextRender = pKernel->RequestInterface<ITextRender>();
|
||||
InitInputs(m_pInput->GetEventsRaw(), m_pInput->GetEventCountRaw());
|
||||
CUIRect::Init(m_pGraphics);
|
||||
CUIElementBase::Init(this);
|
||||
}
|
||||
|
||||
void CUI::InitInputs(IInput::CEvent *pInputEventsArray, int *pInputEventCount)
|
||||
|
|
|
@ -158,6 +158,21 @@ struct SLabelProperties
|
|||
bool m_EnableWidthCheck = true;
|
||||
};
|
||||
|
||||
class CUIElementBase
|
||||
{
|
||||
private:
|
||||
static CUI *s_pUI;
|
||||
|
||||
public:
|
||||
static void Init(CUI *pUI) { s_pUI = pUI; }
|
||||
|
||||
IClient *Client() const;
|
||||
IGraphics *Graphics() const;
|
||||
IInput *Input() const;
|
||||
ITextRender *TextRender() const;
|
||||
CUI *UI() const { return s_pUI; }
|
||||
};
|
||||
|
||||
class CButtonContainer
|
||||
{
|
||||
};
|
||||
|
|
220
src/game/client/ui_scrollregion.cpp
Normal file
220
src/game/client/ui_scrollregion.cpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
/* (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(Input()->KeyPress(KEY_MOUSE_WHEEL_UP))
|
||||
{
|
||||
m_AnimTime = AnimationDuration;
|
||||
m_AnimInitScrollY = m_ScrollY;
|
||||
m_AnimTargetScrollY -= ScrollUnit;
|
||||
}
|
||||
else if(Input()->KeyPress(KEY_MOUSE_WHEEL_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;
|
||||
}
|
123
src/game/client/ui_scrollregion.h
Normal file
123
src/game/client/ui_scrollregion.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/* (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. */
|
||||
#ifndef GAME_CLIENT_UI_SCROLLREGION_H
|
||||
#define GAME_CLIENT_UI_SCROLLREGION_H
|
||||
|
||||
#include "ui.h"
|
||||
|
||||
struct CScrollRegionParams
|
||||
{
|
||||
float m_ScrollbarWidth;
|
||||
float m_ScrollbarMargin;
|
||||
float m_SliderMinHeight;
|
||||
float m_ScrollUnit;
|
||||
ColorRGBA m_ClipBgColor;
|
||||
ColorRGBA m_ScrollbarBgColor;
|
||||
ColorRGBA m_RailBgColor;
|
||||
ColorRGBA m_SliderColor;
|
||||
ColorRGBA m_SliderColorHover;
|
||||
ColorRGBA m_SliderColorGrabbed;
|
||||
unsigned m_Flags;
|
||||
|
||||
enum
|
||||
{
|
||||
FLAG_CONTENT_STATIC_WIDTH = 1 << 0,
|
||||
};
|
||||
|
||||
CScrollRegionParams()
|
||||
{
|
||||
m_ScrollbarWidth = 20.0f;
|
||||
m_ScrollbarMargin = 5.0f;
|
||||
m_SliderMinHeight = 25.0f;
|
||||
m_ScrollUnit = 10.0f;
|
||||
m_ClipBgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
m_ScrollbarBgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
m_RailBgColor = ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f);
|
||||
m_SliderColor = ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f);
|
||||
m_SliderColorHover = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
m_SliderColorGrabbed = ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f);
|
||||
m_Flags = 0;
|
||||
}
|
||||
|
||||
ColorRGBA SliderColor(bool Active, bool Hovered) const
|
||||
{
|
||||
if(Active)
|
||||
return m_SliderColorGrabbed;
|
||||
else if(Hovered)
|
||||
return m_SliderColorHover;
|
||||
return m_SliderColor;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Usage:
|
||||
-- Initialization --
|
||||
static CScrollRegion s_ScrollRegion;
|
||||
vec2 ScrollOffset(0, 0);
|
||||
s_ScrollRegion.Begin(&ScrollRegionRect, &ScrollOffset);
|
||||
Content = ScrollRegionRect;
|
||||
Content.y += ScrollOffset.y;
|
||||
|
||||
-- "Register" your content rects --
|
||||
CUIRect Rect;
|
||||
Content.HSplitTop(SomeValue, &Rect, &Content);
|
||||
s_ScrollRegion.AddRect(Rect);
|
||||
|
||||
-- [Optional] Knowing if a rect is clipped --
|
||||
s_ScrollRegion.IsRectClipped(Rect);
|
||||
|
||||
-- [Optional] Scroll to a rect (to the last added rect)--
|
||||
...
|
||||
s_ScrollRegion.AddRect(Rect);
|
||||
s_ScrollRegion.ScrollHere(Option);
|
||||
|
||||
-- [Convenience] Add rect and check for visibility at the same time
|
||||
if(s_ScrollRegion.AddRect(Rect))
|
||||
// The rect is visible (not clipped)
|
||||
|
||||
-- [Convenience] Add rect and scroll to it if it's selected
|
||||
if(s_ScrollRegion.AddRect(Rect, ScrollToSelection && IsSelected))
|
||||
// The rect is visible (not clipped)
|
||||
|
||||
-- End --
|
||||
s_ScrollRegion.End();
|
||||
*/
|
||||
|
||||
// Instances of CScrollRegion must be static, as member addresses are used as UI item IDs
|
||||
class CScrollRegion : private CUIElementBase
|
||||
{
|
||||
private:
|
||||
float m_ScrollY;
|
||||
float m_ContentH;
|
||||
float m_RequestScrollY; // [0, ContentHeight]
|
||||
|
||||
float m_AnimTime;
|
||||
float m_AnimInitScrollY;
|
||||
float m_AnimTargetScrollY;
|
||||
|
||||
CUIRect m_ClipRect;
|
||||
CUIRect m_RailRect;
|
||||
CUIRect m_LastAddedRect; // saved for ScrollHere()
|
||||
vec2 m_SliderGrabPos; // where did user grab the slider
|
||||
vec2 m_ContentScrollOff;
|
||||
CScrollRegionParams m_Params;
|
||||
|
||||
public:
|
||||
enum EScrollOption
|
||||
{
|
||||
SCROLLHERE_KEEP_IN_VIEW = 0,
|
||||
SCROLLHERE_TOP,
|
||||
SCROLLHERE_BOTTOM,
|
||||
};
|
||||
|
||||
CScrollRegion();
|
||||
void Begin(CUIRect *pClipRect, vec2 *pOutOffset, CScrollRegionParams *pParams = nullptr);
|
||||
void End();
|
||||
bool AddRect(const CUIRect &Rect, bool ShouldScrollHere = false); // returns true if the added rect is visible (not clipped)
|
||||
void ScrollHere(EScrollOption Option = SCROLLHERE_KEEP_IN_VIEW);
|
||||
bool IsRectClipped(const CUIRect &Rect) const;
|
||||
bool IsScrollbarShown() const;
|
||||
bool IsAnimating() const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -6392,6 +6392,8 @@ void CEditor::PlaceBorderTiles()
|
|||
|
||||
void CEditor::OnUpdate()
|
||||
{
|
||||
CUIElementBase::Init(UI()); // update static pointer because game and editor use separate UI
|
||||
|
||||
if(!m_EditorWasUsedBefore)
|
||||
{
|
||||
m_EditorWasUsedBefore = true;
|
||||
|
|
Loading…
Reference in a new issue