1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "GUIFixedListContainer.h"
10
11 #include "GUIListItemLayout.h"
12 #include "input/Key.h"
13
CGUIFixedListContainer(int parentID,int controlID,float posX,float posY,float width,float height,ORIENTATION orientation,const CScroller & scroller,int preloadItems,int fixedPosition,int cursorRange)14 CGUIFixedListContainer::CGUIFixedListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition, int cursorRange)
15 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
16 {
17 ControlType = GUICONTAINER_FIXEDLIST;
18 m_type = VIEW_TYPE_LIST;
19 m_fixedCursor = fixedPosition;
20 m_cursorRange = std::max(0, cursorRange);
21 SetCursor(m_fixedCursor);
22 }
23
24 CGUIFixedListContainer::~CGUIFixedListContainer(void) = default;
25
OnAction(const CAction & action)26 bool CGUIFixedListContainer::OnAction(const CAction &action)
27 {
28 switch (action.GetID())
29 {
30 case ACTION_PAGE_UP:
31 {
32 Scroll(-m_itemsPerPage);
33 return true;
34 }
35 break;
36 case ACTION_PAGE_DOWN:
37 {
38 Scroll(m_itemsPerPage);
39 return true;
40 }
41 break;
42 // smooth scrolling (for analog controls)
43 case ACTION_SCROLL_UP:
44 {
45 m_analogScrollCount += action.GetAmount() * action.GetAmount();
46 bool handled = false;
47 while (m_analogScrollCount > 0.4)
48 {
49 handled = true;
50 m_analogScrollCount -= 0.4f;
51 Scroll(-1);
52 }
53 return handled;
54 }
55 break;
56 case ACTION_SCROLL_DOWN:
57 {
58 m_analogScrollCount += action.GetAmount() * action.GetAmount();
59 bool handled = false;
60 while (m_analogScrollCount > 0.4)
61 {
62 handled = true;
63 m_analogScrollCount -= 0.4f;
64 Scroll(1);
65 }
66 return handled;
67 }
68 break;
69 }
70 return CGUIBaseContainer::OnAction(action);
71 }
72
MoveUp(bool wrapAround)73 bool CGUIFixedListContainer::MoveUp(bool wrapAround)
74 {
75 int item = GetSelectedItem();
76 if (item > 0)
77 SelectItem(item - 1);
78 else if (wrapAround)
79 {
80 SelectItem((int)m_items.size() - 1);
81 SetContainerMoving(-1);
82 }
83 else
84 return false;
85 return true;
86 }
87
MoveDown(bool wrapAround)88 bool CGUIFixedListContainer::MoveDown(bool wrapAround)
89 {
90 int item = GetSelectedItem();
91 if (item < (int)m_items.size() - 1)
92 SelectItem(item + 1);
93 else if (wrapAround)
94 { // move first item in list
95 SelectItem(0);
96 SetContainerMoving(1);
97 }
98 else
99 return false;
100 return true;
101 }
102
Scroll(int amount)103 void CGUIFixedListContainer::Scroll(int amount)
104 {
105 // increase or decrease the offset within [-minCursor, m_items.size() - maxCursor]
106 int minCursor, maxCursor;
107 GetCursorRange(minCursor, maxCursor);
108 const int nextCursor = GetCursor() + amount;
109 int offset = GetOffset() + amount;
110 if (offset < -minCursor)
111 {
112 offset = -minCursor;
113 SetCursor(nextCursor < minCursor ? minCursor : nextCursor);
114 }
115 if (offset > (int)m_items.size() - 1 - maxCursor)
116 {
117 offset = m_items.size() - 1 - maxCursor;
118 SetCursor(nextCursor > maxCursor ? maxCursor : nextCursor);
119 }
120 ScrollToOffset(offset);
121 }
122
GetOffsetRange(int & minOffset,int & maxOffset) const123 bool CGUIFixedListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
124 {
125 GetCursorRange(minOffset, maxOffset);
126 minOffset = -minOffset;
127 maxOffset = m_items.size() - maxOffset - 1;
128 return true;
129 }
130
ValidateOffset()131 void CGUIFixedListContainer::ValidateOffset()
132 {
133 if (!m_layout) return;
134 // ensure our fixed cursor position is valid
135 if (m_fixedCursor >= m_itemsPerPage)
136 m_fixedCursor = m_itemsPerPage - 1;
137 if (m_fixedCursor < 0)
138 m_fixedCursor = 0;
139 // compute our minimum and maximum cursor positions
140 int minCursor, maxCursor;
141 GetCursorRange(minCursor, maxCursor);
142 // assure our cursor is between these limits
143 SetCursor(std::max(GetCursor(), minCursor));
144 SetCursor(std::min(GetCursor(), maxCursor));
145 int minOffset, maxOffset;
146 GetOffsetRange(minOffset, maxOffset);
147 // and finally ensure our offset is valid
148 // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
149 if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
150 {
151 SetOffset(std::max(-minCursor, maxOffset));
152 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
153 }
154 if (GetOffset() < minOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() < minOffset * m_layout->Size(m_orientation)))
155 {
156 SetOffset(minOffset);
157 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
158 }
159 }
160
GetCursorFromPoint(const CPoint & point,CPoint * itemPoint) const161 int CGUIFixedListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
162 {
163 if (!m_focusedLayout || !m_layout)
164 return -1;
165 int minCursor, maxCursor;
166 GetCursorRange(minCursor, maxCursor);
167 // see if the point is either side of our focus range
168 float start = (minCursor + 0.2f) * m_layout->Size(m_orientation);
169 float end = (maxCursor - 0.2f) * m_layout->Size(m_orientation) + m_focusedLayout->Size(m_orientation);
170 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
171 if (pos >= start && pos <= end)
172 { // select the appropriate item
173 pos -= minCursor * m_layout->Size(m_orientation);
174 for (int row = minCursor; row <= maxCursor; row++)
175 {
176 const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
177 if (pos < layout->Size(m_orientation))
178 {
179 if (!InsideLayout(layout, point))
180 return -1;
181 return row;
182 }
183 pos -= layout->Size(m_orientation);
184 }
185 }
186 return -1;
187 }
188
SelectItemFromPoint(const CPoint & point)189 bool CGUIFixedListContainer::SelectItemFromPoint(const CPoint &point)
190 {
191 if (!m_focusedLayout || !m_layout)
192 return false;
193
194 MarkDirtyRegion();
195
196 const float mouse_scroll_speed = 0.25f;
197 const float mouse_max_amount = 1.5f;
198 float sizeOfItem = m_layout->Size(m_orientation);
199 int minCursor, maxCursor;
200 GetCursorRange(minCursor, maxCursor);
201 // see if the point is either side of our focus range
202 float start = (minCursor + 0.2f) * sizeOfItem;
203 float end = (maxCursor - 0.2f) * sizeOfItem + m_focusedLayout->Size(m_orientation);
204 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
205 if (pos < start && GetOffset() > -minCursor)
206 { // scroll backward
207 if (!InsideLayout(m_layout, point))
208 return false;
209 float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
210 m_analogScrollCount += amount * amount * mouse_scroll_speed;
211 if (m_analogScrollCount > 1)
212 {
213 ScrollToOffset(GetOffset() - 1);
214 m_analogScrollCount = 0;
215 }
216 return true;
217 }
218 else if (pos > end && GetOffset() + maxCursor < (int)m_items.size() - 1)
219 {
220 if (!InsideLayout(m_layout, point))
221 return false;
222 // scroll forward
223 float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
224 m_analogScrollCount += amount * amount * mouse_scroll_speed;
225 if (m_analogScrollCount > 1)
226 {
227 ScrollToOffset(GetOffset() + 1);
228 m_analogScrollCount = 0;
229 }
230 return true;
231 }
232 else
233 { // select the appropriate item
234 int cursor = GetCursorFromPoint(point);
235 if (cursor < 0)
236 return false;
237 // calling SelectItem() here will focus the item and scroll, which isn't really what we're after
238 SetCursor(cursor);
239 return true;
240 }
241 }
242
SelectItem(int item)243 void CGUIFixedListContainer::SelectItem(int item)
244 {
245 // Check that GetOffset() is valid
246 ValidateOffset();
247 // only select an item if it's in a valid range
248 if (item >= 0 && item < (int)m_items.size())
249 {
250 // Select the item requested - we first set the cursor position
251 // which may be different at either end of the list, then the offset
252 int minCursor, maxCursor;
253 GetCursorRange(minCursor, maxCursor);
254
255 int cursor;
256 if ((int)m_items.size() - 1 - item <= maxCursor - m_fixedCursor)
257 cursor = std::max(m_fixedCursor, maxCursor + item - (int)m_items.size() + 1);
258 else if (item <= m_fixedCursor - minCursor)
259 cursor = std::min(m_fixedCursor, minCursor + item);
260 else
261 cursor = m_fixedCursor;
262 if (cursor != GetCursor())
263 SetContainerMoving(cursor - GetCursor());
264 SetCursor(cursor);
265 ScrollToOffset(item - GetCursor());
266 MarkDirtyRegion();
267 }
268 }
269
HasPreviousPage() const270 bool CGUIFixedListContainer::HasPreviousPage() const
271 {
272 return (GetOffset() > 0);
273 }
274
HasNextPage() const275 bool CGUIFixedListContainer::HasNextPage() const
276 {
277 return (GetOffset() < (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
278 }
279
GetCurrentPage() const280 int CGUIFixedListContainer::GetCurrentPage() const
281 {
282 int offset = CorrectOffset(GetOffset(), GetCursor());
283 if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page
284 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
285 return offset / m_itemsPerPage + 1;
286 }
287
GetCursorRange(int & minCursor,int & maxCursor) const288 void CGUIFixedListContainer::GetCursorRange(int &minCursor, int &maxCursor) const
289 {
290 minCursor = std::max(m_fixedCursor - m_cursorRange, 0);
291 maxCursor = std::min(m_fixedCursor + m_cursorRange, m_itemsPerPage);
292
293 if (!m_items.size())
294 {
295 minCursor = m_fixedCursor;
296 maxCursor = m_fixedCursor;
297 return;
298 }
299
300 while (maxCursor - minCursor > (int)m_items.size() - 1)
301 {
302 if (maxCursor - m_fixedCursor > m_fixedCursor - minCursor)
303 maxCursor--;
304 else
305 minCursor++;
306 }
307 }
308
309