Merge pull request #26350 from jjd-uk/estuary_media_align
[xbmc.git] / xbmc / guilib / GUIFixedListContainer.cpp
blobab24f5cf583362d7c3969bd12a69a4dce3d4271c
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIFixedListContainer.h"
11 #include "GUIListItemLayout.h"
12 #include "input/actions/Action.h"
13 #include "input/actions/ActionIDs.h"
15 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)
16 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
18 ControlType = GUICONTAINER_FIXEDLIST;
19 m_type = VIEW_TYPE_LIST;
20 m_fixedCursor = fixedPosition;
21 m_cursorRange = std::max(0, cursorRange);
22 SetCursor(m_fixedCursor);
25 CGUIFixedListContainer::~CGUIFixedListContainer(void) = default;
27 bool CGUIFixedListContainer::OnAction(const CAction &action)
29 switch (action.GetID())
31 case ACTION_PAGE_UP:
33 Scroll(-m_itemsPerPage);
34 return true;
36 break;
37 case ACTION_PAGE_DOWN:
39 Scroll(m_itemsPerPage);
40 return true;
42 break;
43 // smooth scrolling (for analog controls)
44 case ACTION_SCROLL_UP:
46 m_analogScrollCount += action.GetAmount() * action.GetAmount();
47 bool handled = false;
48 while (m_analogScrollCount > 0.4f)
50 handled = true;
51 m_analogScrollCount -= 0.4f;
52 Scroll(-1);
54 return handled;
56 break;
57 case ACTION_SCROLL_DOWN:
59 m_analogScrollCount += action.GetAmount() * action.GetAmount();
60 bool handled = false;
61 while (m_analogScrollCount > 0.4f)
63 handled = true;
64 m_analogScrollCount -= 0.4f;
65 Scroll(1);
67 return handled;
69 break;
71 return CGUIBaseContainer::OnAction(action);
74 bool CGUIFixedListContainer::MoveUp(bool wrapAround)
76 int item = GetSelectedItem();
77 if (item > 0)
78 SelectItem(item - 1);
79 else if (wrapAround)
81 SelectItem((int)m_items.size() - 1);
82 SetContainerMoving(-1);
84 else
85 return false;
86 return true;
89 bool CGUIFixedListContainer::MoveDown(bool wrapAround)
91 int item = GetSelectedItem();
92 if (item < (int)m_items.size() - 1)
93 SelectItem(item + 1);
94 else if (wrapAround)
95 { // move first item in list
96 SelectItem(0);
97 SetContainerMoving(1);
99 else
100 return false;
101 return true;
104 void CGUIFixedListContainer::Scroll(int amount)
106 // increase or decrease the offset within [-minCursor, m_items.size() - maxCursor]
107 int minCursor, maxCursor;
108 GetCursorRange(minCursor, maxCursor);
109 const int nextCursor = GetCursor() + amount;
110 int offset = GetOffset() + amount;
111 if (offset < -minCursor)
113 offset = -minCursor;
114 SetCursor(nextCursor < minCursor ? minCursor : nextCursor);
116 if (offset > (int)m_items.size() - 1 - maxCursor)
118 offset = m_items.size() - 1 - maxCursor;
119 SetCursor(nextCursor > maxCursor ? maxCursor : nextCursor);
121 ScrollToOffset(offset);
124 bool CGUIFixedListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
126 GetCursorRange(minOffset, maxOffset);
127 minOffset = -minOffset;
128 maxOffset = m_items.size() - maxOffset - 1;
129 return true;
132 void CGUIFixedListContainer::ValidateOffset()
134 if (!m_layout) return;
135 // ensure our fixed cursor position is valid
136 if (m_fixedCursor >= m_itemsPerPage)
137 m_fixedCursor = m_itemsPerPage - 1;
138 if (m_fixedCursor < 0)
139 m_fixedCursor = 0;
140 // compute our minimum and maximum cursor positions
141 int minCursor, maxCursor;
142 GetCursorRange(minCursor, maxCursor);
143 // assure our cursor is between these limits
144 SetCursor(std::max(GetCursor(), minCursor));
145 SetCursor(std::min(GetCursor(), maxCursor));
146 int minOffset, maxOffset;
147 GetOffsetRange(minOffset, maxOffset);
148 // and finally ensure our offset is valid
149 // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
150 if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
152 SetOffset(std::max(-minCursor, maxOffset));
153 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
155 if (GetOffset() < minOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() < minOffset * m_layout->Size(m_orientation)))
157 SetOffset(minOffset);
158 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
162 int CGUIFixedListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
164 if (!m_focusedLayout || !m_layout)
165 return -1;
166 int minCursor, maxCursor;
167 GetCursorRange(minCursor, maxCursor);
168 // see if the point is either side of our focus range
169 float start = (minCursor + 0.2f) * m_layout->Size(m_orientation);
170 float end = (maxCursor - 0.2f) * m_layout->Size(m_orientation) + m_focusedLayout->Size(m_orientation);
171 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
172 if (pos >= start && pos <= end)
173 { // select the appropriate item
174 pos -= minCursor * m_layout->Size(m_orientation);
175 for (int row = minCursor; row <= maxCursor; row++)
177 const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
178 if (pos < layout->Size(m_orientation))
180 if (!InsideLayout(layout, point))
181 return -1;
182 return row;
184 pos -= layout->Size(m_orientation);
187 return -1;
190 bool CGUIFixedListContainer::SelectItemFromPoint(const CPoint &point)
192 if (!m_focusedLayout || !m_layout)
193 return false;
195 MarkDirtyRegion();
197 const float mouse_scroll_speed = 0.25f;
198 const float mouse_max_amount = 1.5f;
199 float sizeOfItem = m_layout->Size(m_orientation);
200 int minCursor, maxCursor;
201 GetCursorRange(minCursor, maxCursor);
202 // see if the point is either side of our focus range
203 float start = (minCursor + 0.2f) * sizeOfItem;
204 float end = (maxCursor - 0.2f) * sizeOfItem + m_focusedLayout->Size(m_orientation);
205 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
206 if (pos < start && GetOffset() > -minCursor)
207 { // scroll backward
208 if (!InsideLayout(m_layout, point))
209 return false;
210 float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
211 m_analogScrollCount += amount * amount * mouse_scroll_speed;
212 if (m_analogScrollCount > 1)
214 ScrollToOffset(GetOffset() - 1);
215 m_analogScrollCount = 0;
217 return true;
219 else if (pos > end && GetOffset() + maxCursor < (int)m_items.size() - 1)
221 if (!InsideLayout(m_layout, point))
222 return false;
223 // scroll forward
224 float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
225 m_analogScrollCount += amount * amount * mouse_scroll_speed;
226 if (m_analogScrollCount > 1)
228 ScrollToOffset(GetOffset() + 1);
229 m_analogScrollCount = 0;
231 return true;
233 else
234 { // select the appropriate item
235 int cursor = GetCursorFromPoint(point);
236 if (cursor < 0)
237 return false;
238 // calling SelectItem() here will focus the item and scroll, which isn't really what we're after
239 SetCursor(cursor);
240 return true;
244 void CGUIFixedListContainer::SelectItem(int item)
246 // Check that GetOffset() is valid
247 ValidateOffset();
248 // only select an item if it's in a valid range
249 if (item >= 0 && item < (int)m_items.size())
251 // Select the item requested - we first set the cursor position
252 // which may be different at either end of the list, then the offset
253 int minCursor, maxCursor;
254 GetCursorRange(minCursor, maxCursor);
256 int cursor;
257 if ((int)m_items.size() - 1 - item <= maxCursor - m_fixedCursor)
258 cursor = std::max(m_fixedCursor, maxCursor + item - (int)m_items.size() + 1);
259 else if (item <= m_fixedCursor - minCursor)
260 cursor = std::min(m_fixedCursor, minCursor + item);
261 else
262 cursor = m_fixedCursor;
263 if (cursor != GetCursor())
264 SetContainerMoving(cursor - GetCursor());
265 SetCursor(cursor);
266 ScrollToOffset(item - GetCursor());
267 MarkDirtyRegion();
271 bool CGUIFixedListContainer::HasPreviousPage() const
273 return (GetOffset() > 0);
276 bool CGUIFixedListContainer::HasNextPage() const
278 return (GetOffset() < (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
281 int CGUIFixedListContainer::GetCurrentPage() const
283 int offset = CorrectOffset(GetOffset(), GetCursor());
284 if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page
285 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
286 return offset / m_itemsPerPage + 1;
289 void CGUIFixedListContainer::GetCursorRange(int &minCursor, int &maxCursor) const
291 minCursor = std::max(m_fixedCursor - m_cursorRange, 0);
292 maxCursor = std::min(m_fixedCursor + m_cursorRange, m_itemsPerPage);
294 if (!m_items.size())
296 minCursor = m_fixedCursor;
297 maxCursor = m_fixedCursor;
298 return;
301 while (maxCursor - minCursor > (int)m_items.size() - 1)
303 if (maxCursor - m_fixedCursor > m_fixedCursor - minCursor)
304 maxCursor--;
305 else
306 minCursor++;