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