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 "GUIWrappingListContainer.h"
10
11 #include "FileItem.h"
12 #include "GUIListItemLayout.h"
13 #include "GUIMessage.h"
14 #include "input/Key.h"
15
CGUIWrappingListContainer(int parentID,int controlID,float posX,float posY,float width,float height,ORIENTATION orientation,const CScroller & scroller,int preloadItems,int fixedPosition)16 CGUIWrappingListContainer::CGUIWrappingListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition)
17 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
18 {
19 SetCursor(fixedPosition);
20 ControlType = GUICONTAINER_WRAPLIST;
21 m_type = VIEW_TYPE_LIST;
22 m_extraItems = 0;
23 }
24
25 CGUIWrappingListContainer::~CGUIWrappingListContainer(void) = default;
26
UpdatePageControl(int offset)27 void CGUIWrappingListContainer::UpdatePageControl(int offset)
28 {
29 if (m_pageControl)
30 { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
31 CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), m_pageControl, GetNumItems() ? CorrectOffset(offset, GetCursor()) % GetNumItems() : 0);
32 SendWindowMessage(msg);
33 }
34 }
35
OnAction(const CAction & action)36 bool CGUIWrappingListContainer::OnAction(const CAction &action)
37 {
38 switch (action.GetID())
39 {
40 case ACTION_PAGE_UP:
41 Scroll(-m_itemsPerPage);
42 return true;
43 case ACTION_PAGE_DOWN:
44 Scroll(m_itemsPerPage);
45 return true;
46 // smooth scrolling (for analog controls)
47 case ACTION_SCROLL_UP:
48 {
49 m_analogScrollCount += action.GetAmount() * action.GetAmount();
50 bool handled = false;
51 while (m_analogScrollCount > 0.4)
52 {
53 handled = true;
54 m_analogScrollCount -= 0.4f;
55 Scroll(-1);
56 }
57 return handled;
58 }
59 break;
60 case ACTION_SCROLL_DOWN:
61 {
62 m_analogScrollCount += action.GetAmount() * action.GetAmount();
63 bool handled = false;
64 while (m_analogScrollCount > 0.4)
65 {
66 handled = true;
67 m_analogScrollCount -= 0.4f;
68 Scroll(1);
69 }
70 return handled;
71 }
72 break;
73 }
74 return CGUIBaseContainer::OnAction(action);
75 }
76
OnMessage(CGUIMessage & message)77 bool CGUIWrappingListContainer::OnMessage(CGUIMessage& message)
78 {
79 if (message.GetControlId() == GetID() )
80 {
81 if (message.GetMessage() == GUI_MSG_PAGE_CHANGE)
82 {
83 if (message.GetSenderId() == m_pageControl && IsVisible())
84 { // offset by our cursor position
85 message.SetParam1(message.GetParam1() - GetCursor());
86 }
87 }
88 }
89 return CGUIBaseContainer::OnMessage(message);
90 }
91
MoveUp(bool wrapAround)92 bool CGUIWrappingListContainer::MoveUp(bool wrapAround)
93 {
94 Scroll(-1);
95 return true;
96 }
97
MoveDown(bool wrapAround)98 bool CGUIWrappingListContainer::MoveDown(bool wrapAround)
99 {
100 Scroll(+1);
101 return true;
102 }
103
104 // scrolls the said amount
Scroll(int amount)105 void CGUIWrappingListContainer::Scroll(int amount)
106 {
107 ScrollToOffset(GetOffset() + amount);
108 }
109
GetOffsetRange(int & minOffset,int & maxOffset) const110 bool CGUIWrappingListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
111 {
112 return false;
113 }
114
ValidateOffset()115 void CGUIWrappingListContainer::ValidateOffset()
116 {
117 // our minimal amount of items - we need to take into account extra items to display wrapped items when scrolling
118 unsigned int minItems = (unsigned int)m_itemsPerPage + ScrollCorrectionRange() + GetCacheCount() / 2;
119 if (minItems <= m_items.size())
120 return;
121
122 // no need to check the range here, but we need to check we have
123 // more items than slots.
124 ResetExtraItems();
125 if (m_items.size())
126 {
127 size_t numItems = m_items.size();
128 while (m_items.size() < minItems)
129 {
130 // add additional copies of items, as we require extras at render time
131 for (unsigned int i = 0; i < numItems; i++)
132 {
133 m_items.push_back(CGUIListItemPtr(m_items[i]->Clone()));
134 m_extraItems++;
135 }
136 }
137 }
138 }
139
CorrectOffset(int offset,int cursor) const140 int CGUIWrappingListContainer::CorrectOffset(int offset, int cursor) const
141 {
142 if (m_items.size())
143 {
144 int correctOffset = (offset + cursor) % (int)m_items.size();
145 if (correctOffset < 0) correctOffset += m_items.size();
146 return correctOffset;
147 }
148 return 0;
149 }
150
GetSelectedItem() const151 int CGUIWrappingListContainer::GetSelectedItem() const
152 {
153 if (m_items.size() > m_extraItems)
154 {
155 int numItems = (int)(m_items.size() - m_extraItems);
156 int correctOffset = (GetOffset() + GetCursor()) % numItems;
157 if (correctOffset < 0) correctOffset += numItems;
158 return correctOffset;
159 }
160 return 0;
161 }
162
SelectItemFromPoint(const CPoint & point)163 bool CGUIWrappingListContainer::SelectItemFromPoint(const CPoint &point)
164 {
165 if (!m_focusedLayout || !m_layout)
166 return false;
167
168 const float mouse_scroll_speed = 0.05f;
169 const float mouse_max_amount = 1.0f; // max speed: 1 item per frame
170 float sizeOfItem = m_layout->Size(m_orientation);
171 // see if the point is either side of our focused item
172 float start = GetCursor() * sizeOfItem;
173 float end = start + m_focusedLayout->Size(m_orientation);
174 float pos = (m_orientation == VERTICAL) ? point.y : point.x;
175 if (pos < start - 0.5f * sizeOfItem)
176 { // scroll backward
177 if (!InsideLayout(m_layout, point))
178 return false;
179 float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
180 m_analogScrollCount += amount * amount * mouse_scroll_speed;
181 if (m_analogScrollCount > 1)
182 {
183 Scroll(-1);
184 m_analogScrollCount-=1.0f;
185 }
186 return true;
187 }
188 else if (pos > end + 0.5f * sizeOfItem)
189 { // scroll forward
190 if (!InsideLayout(m_layout, point))
191 return false;
192
193 float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
194 m_analogScrollCount += amount * amount * mouse_scroll_speed;
195 if (m_analogScrollCount > 1)
196 {
197 Scroll(1);
198 m_analogScrollCount-=1.0f;
199 }
200 return true;
201 }
202 return InsideLayout(m_focusedLayout, point);
203 }
204
SelectItem(int item)205 void CGUIWrappingListContainer::SelectItem(int item)
206 {
207 if (item >= 0 && item < (int)m_items.size())
208 ScrollToOffset(item - GetCursor());
209 }
210
ResetExtraItems()211 void CGUIWrappingListContainer::ResetExtraItems()
212 {
213 // delete any extra items
214 if (m_extraItems)
215 m_items.erase(m_items.begin() + m_items.size() - m_extraItems, m_items.end());
216 m_extraItems = 0;
217 }
218
Reset()219 void CGUIWrappingListContainer::Reset()
220 {
221 ResetExtraItems();
222 CGUIBaseContainer::Reset();
223 }
224
GetCurrentPage() const225 int CGUIWrappingListContainer::GetCurrentPage() const
226 {
227 int offset = CorrectOffset(GetOffset(), GetCursor());
228 if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page
229 return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
230 return offset / m_itemsPerPage + 1;
231 }
232
SetPageControlRange()233 void CGUIWrappingListContainer::SetPageControlRange()
234 {
235 if (m_pageControl)
236 {
237 CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), m_pageControl, m_itemsPerPage, GetNumItems());
238 SendWindowMessage(msg);
239 }
240 }
241