1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include <base/system.h>
4#include <base/vmath.h>
5
6#include <engine/client.h>
7#include <engine/keys.h>
8#include <engine/shared/config.h>
9
10#include "ui_scrollregion.h"
11
12CScrollRegion::CScrollRegion()
13{
14 m_ScrollY = 0.0f;
15 m_ContentH = 0.0f;
16 m_RequestScrollY = -1.0f;
17 m_ScrollDirection = SCROLLRELATIVE_NONE;
18 m_ScrollSpeedMultiplier = 1.0f;
19
20 m_AnimTimeMax = 0.0f;
21 m_AnimTime = 0.0f;
22 m_AnimInitScrollY = 0.0f;
23 m_AnimTargetScrollY = 0.0f;
24
25 m_SliderGrabPos = 0.0f;
26 m_ContentScrollOff = vec2(0.0f, 0.0f);
27 m_Params = CScrollRegionParams();
28}
29
30void CScrollRegion::Begin(CUIRect *pClipRect, vec2 *pOutOffset, const CScrollRegionParams *pParams)
31{
32 if(pParams)
33 m_Params = *pParams;
34
35 const bool ContentOverflows = m_ContentH > pClipRect->h;
36 const bool ForceShowScrollbar = m_Params.m_Flags & CScrollRegionParams::FLAG_CONTENT_STATIC_WIDTH;
37
38 const bool HasScrollBar = ContentOverflows || ForceShowScrollbar;
39 CUIRect ScrollBarBg;
40 pClipRect->VSplitRight(Cut: m_Params.m_ScrollbarWidth, pLeft: HasScrollBar ? pClipRect : nullptr, pRight: &ScrollBarBg);
41 if(m_Params.m_ScrollbarNoMarginRight)
42 {
43 ScrollBarBg.HMargin(Cut: m_Params.m_ScrollbarMargin, pOtherRect: &m_RailRect);
44 m_RailRect.VSplitLeft(Cut: m_Params.m_ScrollbarMargin, pLeft: nullptr, pRight: &m_RailRect);
45 }
46 else
47 ScrollBarBg.Margin(Cut: m_Params.m_ScrollbarMargin, pOtherRect: &m_RailRect);
48
49 // only show scrollbar if required
50 if(HasScrollBar)
51 {
52 if(m_Params.m_ScrollbarBgColor.a > 0.0f)
53 ScrollBarBg.Draw(Color: m_Params.m_ScrollbarBgColor, Corners: IGraphics::CORNER_R, Rounding: 4.0f);
54 if(m_Params.m_RailBgColor.a > 0.0f)
55 m_RailRect.Draw(Color: m_Params.m_RailBgColor, Corners: IGraphics::CORNER_ALL, Rounding: m_RailRect.w / 2.0f);
56 }
57 if(!ContentOverflows)
58 m_ContentScrollOff.y = 0.0f;
59
60 if(m_Params.m_ClipBgColor.a > 0.0f)
61 pClipRect->Draw(Color: m_Params.m_ClipBgColor, Corners: HasScrollBar ? IGraphics::CORNER_L : IGraphics::CORNER_ALL, Rounding: 4.0f);
62
63 Ui()->ClipEnable(pRect: pClipRect);
64
65 m_ClipRect = *pClipRect;
66 m_ContentH = 0.0f;
67 *pOutOffset = m_ContentScrollOff;
68}
69
70void CScrollRegion::End()
71{
72 Ui()->ClipDisable();
73
74 // only show scrollbar if content overflows
75 if(m_ContentH <= m_ClipRect.h)
76 return;
77
78 // scroll wheel
79 CUIRect RegionRect = m_ClipRect;
80 RegionRect.w += m_Params.m_ScrollbarWidth;
81
82 if(m_ScrollDirection != SCROLLRELATIVE_NONE || Ui()->HotScrollRegion() == this)
83 {
84 bool ProgrammaticScroll = false;
85 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_SCROLL_UP))
86 m_ScrollDirection = SCROLLRELATIVE_UP;
87 else if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_SCROLL_DOWN))
88 m_ScrollDirection = SCROLLRELATIVE_DOWN;
89 else
90 ProgrammaticScroll = true;
91
92 if(!ProgrammaticScroll)
93 m_ScrollSpeedMultiplier = 1.0f;
94
95 if(m_ScrollDirection != SCROLLRELATIVE_NONE)
96 {
97 const bool IsPageScroll = Input()->AltIsPressed();
98 const float ScrollUnit = IsPageScroll && !ProgrammaticScroll ? m_ClipRect.h : m_Params.m_ScrollUnit;
99
100 m_AnimTimeMax = g_Config.m_UiSmoothScrollTime / 1000.0f;
101 m_AnimTime = m_AnimTimeMax;
102 m_AnimInitScrollY = m_ScrollY;
103 m_AnimTargetScrollY = (ProgrammaticScroll ? m_ScrollY : m_AnimTargetScrollY) + (int)m_ScrollDirection * ScrollUnit * m_ScrollSpeedMultiplier;
104 m_ScrollDirection = SCROLLRELATIVE_NONE;
105 m_ScrollSpeedMultiplier = 1.0f;
106 }
107 }
108
109 if(Ui()->Enabled() && Ui()->MouseHovered(pRect: &RegionRect))
110 {
111 Ui()->SetHotScrollRegion(this);
112 }
113
114 const float SliderHeight = maximum(a: m_Params.m_SliderMinHeight, b: m_ClipRect.h / m_ContentH * m_RailRect.h);
115
116 CUIRect Slider = m_RailRect;
117 Slider.h = SliderHeight;
118
119 const float MaxSlider = m_RailRect.h - SliderHeight;
120 const float MaxScroll = m_ContentH - m_ClipRect.h;
121
122 if(m_RequestScrollY >= 0.0f)
123 {
124 m_AnimTargetScrollY = m_RequestScrollY;
125 m_AnimTime = 0.0f;
126 m_RequestScrollY = -1.0f;
127 }
128
129 m_AnimTargetScrollY = clamp(val: m_AnimTargetScrollY, lo: 0.0f, hi: MaxScroll);
130
131 if(absolute(a: m_AnimInitScrollY - m_AnimTargetScrollY) < 0.5f)
132 m_AnimTime = 0.0f;
133
134 if(m_AnimTime > 0.0f)
135 {
136 m_AnimTime -= Client()->RenderFrameTime();
137 float AnimProgress = (1.0f - std::pow(x: m_AnimTime / m_AnimTimeMax, y: 3.0f)); // cubic ease out
138 m_ScrollY = m_AnimInitScrollY + (m_AnimTargetScrollY - m_AnimInitScrollY) * AnimProgress;
139 }
140 else
141 {
142 m_ScrollY = m_AnimTargetScrollY;
143 }
144
145 Slider.y += m_ScrollY / MaxScroll * MaxSlider;
146
147 bool Grabbed = false;
148 const void *pId = &m_ScrollY;
149 const bool InsideSlider = Ui()->MouseHovered(pRect: &Slider);
150 const bool InsideRail = Ui()->MouseHovered(pRect: &m_RailRect);
151
152 if(Ui()->CheckActiveItem(pId) && Ui()->MouseButton(Index: 0))
153 {
154 float MouseY = Ui()->MouseY();
155 m_ScrollY += (MouseY - (Slider.y + m_SliderGrabPos)) / MaxSlider * MaxScroll;
156 m_SliderGrabPos = clamp(val: m_SliderGrabPos, lo: 0.0f, hi: SliderHeight);
157 m_AnimTargetScrollY = m_ScrollY;
158 m_AnimTime = 0.0f;
159 Grabbed = true;
160 }
161 else if(InsideSlider)
162 {
163 if(!Ui()->MouseButton(Index: 0))
164 Ui()->SetHotItem(pId);
165
166 if(!Ui()->CheckActiveItem(pId) && Ui()->MouseButtonClicked(Index: 0))
167 {
168 Ui()->SetActiveItem(pId);
169 m_SliderGrabPos = Ui()->MouseY() - Slider.y;
170 m_AnimTargetScrollY = m_ScrollY;
171 m_AnimTime = 0.0f;
172 }
173 }
174 else if(InsideRail && Ui()->MouseButtonClicked(Index: 0))
175 {
176 m_ScrollY += (Ui()->MouseY() - (Slider.y + Slider.h / 2.0f)) / MaxSlider * MaxScroll;
177 Ui()->SetHotItem(pId);
178 Ui()->SetActiveItem(pId);
179 m_SliderGrabPos = Slider.h / 2.0f;
180 m_AnimTargetScrollY = m_ScrollY;
181 m_AnimTime = 0.0f;
182 }
183
184 if(Ui()->CheckActiveItem(pId) && !Ui()->MouseButton(Index: 0))
185 {
186 Ui()->SetActiveItem(nullptr);
187 }
188
189 m_ScrollY = clamp(val: m_ScrollY, lo: 0.0f, hi: MaxScroll);
190 m_ContentScrollOff.y = -m_ScrollY;
191
192 Slider.Draw(Color: m_Params.SliderColor(Active: Grabbed, Hovered: Ui()->HotItem() == pId), Corners: IGraphics::CORNER_ALL, Rounding: Slider.w / 2.0f);
193}
194
195bool CScrollRegion::AddRect(const CUIRect &Rect, bool ShouldScrollHere)
196{
197 m_LastAddedRect = Rect;
198 // Round up and add magic to fix pixel clipping at the end of the scrolling area
199 m_ContentH = maximum(a: std::ceil(x: Rect.y + Rect.h - (m_ClipRect.y + m_ContentScrollOff.y)) + HEIGHT_MAGIC_FIX, b: m_ContentH);
200 if(ShouldScrollHere)
201 ScrollHere();
202 return !RectClipped(Rect);
203}
204
205void CScrollRegion::ScrollHere(EScrollOption Option)
206{
207 const float MinHeight = minimum(a: m_ClipRect.h, b: m_LastAddedRect.h);
208 const float TopScroll = m_LastAddedRect.y - (m_ClipRect.y + m_ContentScrollOff.y);
209
210 switch(Option)
211 {
212 case SCROLLHERE_TOP:
213 m_RequestScrollY = TopScroll;
214 break;
215
216 case SCROLLHERE_BOTTOM:
217 m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight);
218 break;
219
220 case SCROLLHERE_KEEP_IN_VIEW:
221 default:
222 const float DeltaY = m_LastAddedRect.y - m_ClipRect.y;
223 if(DeltaY < 0)
224 m_RequestScrollY = TopScroll;
225 else if(DeltaY > (m_ClipRect.h - MinHeight))
226 m_RequestScrollY = TopScroll - (m_ClipRect.h - MinHeight);
227 break;
228 }
229}
230
231void CScrollRegion::ScrollRelative(EScrollRelative Direction, float SpeedMultiplier)
232{
233 m_ScrollDirection = Direction;
234 m_ScrollSpeedMultiplier = SpeedMultiplier;
235}
236
237void CScrollRegion::DoEdgeScrolling()
238{
239 if(!ScrollbarShown())
240 return;
241
242 const float ScrollBorderSize = 20.0f;
243 const float MaxScrollMultiplier = 2.0f;
244 const float ScrollSpeedFactor = MaxScrollMultiplier / ScrollBorderSize;
245 const float TopScrollPosition = m_ClipRect.y + ScrollBorderSize;
246 const float BottomScrollPosition = m_ClipRect.y + m_ClipRect.h - ScrollBorderSize;
247 if(Ui()->MouseY() < TopScrollPosition)
248 ScrollRelative(Direction: SCROLLRELATIVE_UP, SpeedMultiplier: minimum(a: MaxScrollMultiplier, b: (TopScrollPosition - Ui()->MouseY()) * ScrollSpeedFactor));
249 else if(Ui()->MouseY() > BottomScrollPosition)
250 ScrollRelative(Direction: SCROLLRELATIVE_DOWN, SpeedMultiplier: minimum(a: MaxScrollMultiplier, b: (Ui()->MouseY() - BottomScrollPosition) * ScrollSpeedFactor));
251}
252
253bool CScrollRegion::RectClipped(const CUIRect &Rect) const
254{
255 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);
256}
257
258bool CScrollRegion::ScrollbarShown() const
259{
260 return m_ContentH > m_ClipRect.h;
261}
262
263bool CScrollRegion::Animating() const
264{
265 return m_AnimTime > 0.0f;
266}
267
268bool CScrollRegion::Active() const
269{
270 return Ui()->ActiveItem() == &m_ScrollY;
271}
272