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 "GUIListContainer.h"
10
11 #include "GUIListItemLayout.h"
12 #include "GUIMessage.h"
13 #include "input/Key.h"
14 #include "utils/StringUtils.h"
15
CGUIListContainer(int parentID,int controlID,float posX,float posY,float width,float height,ORIENTATION orientation,const CScroller & scroller,int preloadItems)16 CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
17 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
18 {
19 ControlType = GUICONTAINER_LIST;
20 m_type = VIEW_TYPE_LIST;
21 }
22
23 CGUIListContainer::~CGUIListContainer(void) = default;
24
OnAction(const CAction & action)25 bool CGUIListContainer::OnAction(const CAction &action)
26 {
27 switch (action.GetID())
28 {
29 case ACTION_PAGE_UP:
30 {
31 if (GetOffset() == 0)
32 { // already on the first page, so move to the first item
33 SetCursor(0);
34 }
35 else
36 { // scroll up to the previous page
37 Scroll( -m_itemsPerPage);
38 }
39 return true;
40 }
41 break;
42 case ACTION_PAGE_DOWN:
43 {
44 if (GetOffset() == (int)m_items.size() - m_itemsPerPage || (int)m_items.size() < m_itemsPerPage)
45 { // already at the last page, so move to the last item.
46 SetCursor(m_items.size() - GetOffset() - 1);
47 }
48 else
49 { // scroll down to the next page
50 Scroll(m_itemsPerPage);
51 }
52 return true;
53 }
54 break;
55 // smooth scrolling (for analog controls)
56 case ACTION_SCROLL_UP:
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 if (GetOffset() > 0 && GetCursor() <= m_itemsPerPage / 2)
65 {
66 Scroll(-1);
67 }
68 else if (GetCursor() > 0)
69 {
70 SetCursor(GetCursor() - 1);
71 }
72 }
73 return handled;
74 }
75 break;
76 case ACTION_SCROLL_DOWN:
77 {
78 m_analogScrollCount += action.GetAmount() * action.GetAmount();
79 bool handled = false;
80 while (m_analogScrollCount > 0.4)
81 {
82 handled = true;
83 m_analogScrollCount -= 0.4f;
84 if (GetOffset() + m_itemsPerPage < (int)m_items.size() && GetCursor() >= m_itemsPerPage / 2)
85 {
86 Scroll(1);
87 }
88 else if (GetCursor() < m_itemsPerPage - 1 && GetOffset() + GetCursor() < (int)m_items.size() - 1)
89 {
90 SetCursor(GetCursor() + 1);
91 }
92 }
93 return handled;
94 }
95 break;
96 }
97 return CGUIBaseContainer::OnAction(action);
98 }
99
OnMessage(CGUIMessage & message)100 bool CGUIListContainer::OnMessage(CGUIMessage& message)
101 {
102 if (message.GetControlId() == GetID() )
103 {
104 if (message.GetMessage() == GUI_MSG_LABEL_RESET)
105 {
106 SetCursor(0);
107 SetOffset(0);
108 m_scroller.SetValue(0);
109 }
110 }
111 return CGUIBaseContainer::OnMessage(message);
112 }
113
MoveUp(bool wrapAround)114 bool CGUIListContainer::MoveUp(bool wrapAround)
115 {
116 if (GetCursor() > 0)
117 {
118 SetCursor(GetCursor() - 1);
119 }
120 else if (GetCursor() == 0 && GetOffset())
121 {
122 ScrollToOffset(GetOffset() - 1);
123 }
124 else if (wrapAround)
125 {
126 if (!m_items.empty())
127 { // move 2 last item in list, and set our container moving up
128 int offset = m_items.size() - m_itemsPerPage;
129 if (offset < 0) offset = 0;
130 SetCursor(m_items.size() - offset - 1);
131 ScrollToOffset(offset);
132 SetContainerMoving(-1);
133 }
134 }
135 else
136 return false;
137 return true;
138 }
139
MoveDown(bool wrapAround)140 bool CGUIListContainer::MoveDown(bool wrapAround)
141 {
142 if (GetOffset() + GetCursor() + 1 < (int)m_items.size())
143 {
144 if (GetCursor() + 1 < m_itemsPerPage)
145 {
146 SetCursor(GetCursor() + 1);
147 }
148 else
149 {
150 ScrollToOffset(GetOffset() + 1);
151 }
152 }
153 else if(wrapAround)
154 { // move first item in list, and set our container moving in the "down" direction
155 SetCursor(0);
156 ScrollToOffset(0);
157 SetContainerMoving(1);
158 }
159 else
160 return false;
161 return true;
162 }
163
164 // scrolls the said amount
Scroll(int amount)165 void CGUIListContainer::Scroll(int amount)
166 {
167 // increase or decrease the offset
168 int offset = GetOffset() + amount;
169 if (offset > (int)m_items.size() - m_itemsPerPage)
170 {
171 offset = m_items.size() - m_itemsPerPage;
172 }
173 if (offset < 0) offset = 0;
174 ScrollToOffset(offset);
175 }
176
ValidateOffset()177 void CGUIListContainer::ValidateOffset()
178 {
179 if (!m_layout) return;
180 // first thing is we check the range of our offset
181 // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
182 int minOffset, maxOffset;
183 GetOffsetRange(minOffset, maxOffset);
184 if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
185 {
186 SetOffset(std::max(0, maxOffset));
187 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
188 }
189 if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
190 {
191 SetOffset(0);
192 m_scroller.SetValue(0);
193 }
194 }
195
SetCursor(int cursor)196 void CGUIListContainer::SetCursor(int cursor)
197 {
198 if (cursor > m_itemsPerPage - 1) cursor = m_itemsPerPage - 1;
199 if (cursor < 0) cursor = 0;
200 if (!m_wasReset)
201 SetContainerMoving(cursor - GetCursor());
202 CGUIBaseContainer::SetCursor(cursor);
203 }
204
SelectItem(int item)205 void CGUIListContainer::SelectItem(int item)
206 {
207 // Check that our offset is valid
208 ValidateOffset();
209 // only select an item if it's in a valid range
210 if (item >= 0 && item < (int)m_items.size())
211 {
212 // Select the item requested
213 if (item >= GetOffset() && item < GetOffset() + m_itemsPerPage)
214 { // the item is on the current page, so don't change it.
215 SetCursor(item - GetOffset());
216 }
217 else if (item < GetOffset())
218 { // item is on a previous page - make it the first item on the page
219 SetCursor(0);
220 ScrollToOffset(item);
221 }
222 else // (item >= GetOffset()+m_itemsPerPage)
223 { // item is on a later page - make it the last item on the page
224 SetCursor(m_itemsPerPage - 1);
225 ScrollToOffset(item - GetCursor());
226 }
227 }
228 }
229
GetCursorFromPoint(const CPoint & point,CPoint * itemPoint) const230 int CGUIListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
231 {
232 if (!m_focusedLayout || !m_layout)
233 return -1;
234
235 int row = 0;
236 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
237 while (row < m_itemsPerPage + 1) // 1 more to ensure we get the (possible) half item at the end.
238 {
239 const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
240 if (pos < layout->Size(m_orientation) && row + GetOffset() < (int)m_items.size())
241 { // found correct "row" -> check horizontal
242 if (!InsideLayout(layout, point))
243 return -1;
244
245 if (itemPoint)
246 *itemPoint = m_orientation == VERTICAL ? CPoint(point.x, pos) : CPoint(pos, point.y);
247 return row;
248 }
249 row++;
250 pos -= layout->Size(m_orientation);
251 }
252 return -1;
253 }
254
SelectItemFromPoint(const CPoint & point)255 bool CGUIListContainer::SelectItemFromPoint(const CPoint &point)
256 {
257 CPoint itemPoint;
258 int row = GetCursorFromPoint(point, &itemPoint);
259 if (row < 0)
260 return false;
261
262 SetCursor(row);
263 CGUIListItemLayout *focusedLayout = GetFocusedLayout();
264 if (focusedLayout)
265 focusedLayout->SelectItemFromPoint(itemPoint);
266 return true;
267 }
268
269 //#ifdef GUILIB_PYTHON_COMPATIBILITY
CGUIListContainer(int parentID,int controlID,float posX,float posY,float width,float height,const CLabelInfo & labelInfo,const CLabelInfo & labelInfo2,const CTextureInfo & textureButton,const CTextureInfo & textureButtonFocus,float textureHeight,float itemWidth,float itemHeight,float spaceBetweenItems)270 CGUIListContainer::CGUIListContainer(int parentID, int controlID, float posX, float posY, float width, float height,
271 const CLabelInfo& labelInfo, const CLabelInfo& labelInfo2,
272 const CTextureInfo& textureButton, const CTextureInfo& textureButtonFocus,
273 float textureHeight, float itemWidth, float itemHeight, float spaceBetweenItems)
274 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, VERTICAL, 200, 0)
275 {
276 m_layouts.emplace_back();
277 m_layouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, false, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, "", "");
278 std::string condition = StringUtils::Format("control.hasfocus(%i)", controlID);
279 std::string condition2 = "!" + condition;
280 m_focusedLayouts.emplace_back();
281 m_focusedLayouts.back().CreateListControlLayouts(width, textureHeight + spaceBetweenItems, true, labelInfo, labelInfo2, textureButton, textureButtonFocus, textureHeight, itemWidth, itemHeight, condition2, condition);
282 m_height = floor(m_height / (textureHeight + spaceBetweenItems)) * (textureHeight + spaceBetweenItems);
283 ControlType = GUICONTAINER_LIST;
284 }
285 //#endif
286
HasNextPage() const287 bool CGUIListContainer::HasNextPage() const
288 {
289 return (GetOffset() != (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
290 }
291
HasPreviousPage() const292 bool CGUIListContainer::HasPreviousPage() const
293 {
294 return (GetOffset() > 0);
295 }
296