2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "GUIPanelContainer.h"
22 #include "GUIListItem.h"
23 #include "GUIInfoManager.h"
28 CGUIPanelContainer::CGUIPanelContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
29 : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
31 ControlType = GUICONTAINER_PANEL;
32 m_type = VIEW_TYPE_ICON;
36 CGUIPanelContainer::~CGUIPanelContainer(void)
40 void CGUIPanelContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
47 if (!m_layout || !m_focusedLayout) return;
49 UpdateScrollOffset(currentTime);
51 int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
53 int cacheBefore, cacheAfter;
54 GetCacheOffsets(cacheBefore, cacheAfter);
56 // Free memory not used on screen at the moment, do this first so there's more memory for the new items.
57 FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + cacheAfter + m_itemsPerPage + 1, 0));
59 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
60 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
61 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
62 pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
63 end += cacheAfter * m_layout->Size(m_orientation);
65 int current = (offset - cacheBefore) * m_itemsPerRow;
67 while (pos < end && m_items.size())
69 if (current >= (int)m_items.size())
73 CGUIListItemPtr item = m_items[current];
74 bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
76 if (m_orientation == VERTICAL)
77 ProcessItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item, focused, currentTime, dirtyregions);
79 ProcessItem(pos, origin.y + col * m_layout->Size(VERTICAL), item, focused, currentTime, dirtyregions);
81 // increment our position
82 if (col < m_itemsPerRow - 1)
86 pos += m_layout->Size(m_orientation);
92 // when we are scrolling up, offset will become lower (integer division, see offset calc)
93 // to have same behaviour when scrolling down, we need to set page control to offset+1
94 UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
96 CGUIControl::Process(currentTime, dirtyregions);
100 void CGUIPanelContainer::Render()
102 if (!m_layout || !m_focusedLayout) return;
104 int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
106 int cacheBefore, cacheAfter;
107 GetCacheOffsets(cacheBefore, cacheAfter);
109 // Free memory not used on screen at the moment, do this first so there's more memory for the new items.
110 FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + cacheAfter + m_itemsPerPage + 1, 0));
112 if (g_graphicsContext.SetClipRegion(m_posX, m_posY, m_width, m_height))
114 CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
115 float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
116 float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
117 pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
118 end += cacheAfter * m_layout->Size(m_orientation);
120 float focusedPos = 0;
122 CGUIListItemPtr focusedItem;
123 int current = (offset - cacheBefore) * m_itemsPerRow;
125 while (pos < end && m_items.size())
127 if (current >= (int)m_items.size())
131 CGUIListItemPtr item = m_items[current];
132 bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
142 if (m_orientation == VERTICAL)
143 RenderItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item.get(), false);
145 RenderItem(pos, origin.y + col * m_layout->Size(VERTICAL), item.get(), false);
148 // increment our position
149 if (col < m_itemsPerRow - 1)
153 pos += m_layout->Size(m_orientation);
158 // and render the focused item last (for overlapping purposes)
161 if (m_orientation == VERTICAL)
162 RenderItem(origin.x + focusedCol * m_layout->Size(HORIZONTAL), focusedPos, focusedItem.get(), true);
164 RenderItem(focusedPos, origin.y + focusedCol * m_layout->Size(VERTICAL), focusedItem.get(), true);
167 g_graphicsContext.RestoreClipRegion();
169 CGUIControl::Render();
172 bool CGUIPanelContainer::OnAction(const CAction &action)
174 switch (action.GetID())
178 if (GetOffset() == 0)
179 { // already on the first page, so move to the first item
183 { // scroll up to the previous page
184 Scroll( -m_itemsPerPage);
189 case ACTION_PAGE_DOWN:
191 if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow >= (int)m_items.size() || (int)m_items.size() < m_itemsPerPage)
192 { // already at the last page, so move to the last item.
193 SetCursor(m_items.size() - GetOffset() * m_itemsPerRow - 1);
196 { // scroll down to the next page
197 Scroll(m_itemsPerPage);
202 // smooth scrolling (for analog controls)
203 case ACTION_SCROLL_UP:
205 m_analogScrollCount += action.GetAmount() * action.GetAmount();
206 bool handled = false;
207 while (m_analogScrollCount > AnalogScrollSpeed())
210 m_analogScrollCount -= AnalogScrollSpeed();
211 if (GetOffset() > 0)// && GetCursor() <= m_itemsPerPage * m_itemsPerRow / 2)
215 else if (GetCursor() > 0)
217 SetCursor(GetCursor() - 1);
223 case ACTION_SCROLL_DOWN:
225 m_analogScrollCount += action.GetAmount() * action.GetAmount();
226 bool handled = false;
227 while (m_analogScrollCount > AnalogScrollSpeed())
230 m_analogScrollCount -= AnalogScrollSpeed();
231 if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow < (int)m_items.size())// && GetCursor() >= m_itemsPerPage * m_itemsPerRow / 2)
235 else if (GetCursor() < m_itemsPerPage * m_itemsPerRow - 1 && GetOffset() * m_itemsPerRow + GetCursor() < (int)m_items.size() - 1)
237 SetCursor(GetCursor() + 1);
244 return CGUIBaseContainer::OnAction(action);
247 bool CGUIPanelContainer::OnMessage(CGUIMessage& message)
249 if (message.GetControlId() == GetID() )
251 if (message.GetMessage() == GUI_MSG_LABEL_RESET)
254 // fall through to base class
257 return CGUIBaseContainer::OnMessage(message);
260 void CGUIPanelContainer::OnLeft()
262 bool wrapAround = m_actionLeft.GetNavigation() == GetID() || !m_actionLeft.HasActionsMeetingCondition();
263 if (m_orientation == VERTICAL && MoveLeft(wrapAround))
265 if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
267 CGUIControl::OnLeft();
270 void CGUIPanelContainer::OnRight()
272 bool wrapAround = m_actionRight.GetNavigation() == GetID() || !m_actionRight.HasActionsMeetingCondition();
273 if (m_orientation == VERTICAL && MoveRight(wrapAround))
275 if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
277 return CGUIControl::OnRight();
280 void CGUIPanelContainer::OnUp()
282 bool wrapAround = m_actionUp.GetNavigation() == GetID() || !m_actionUp.HasActionsMeetingCondition();
283 if (m_orientation == VERTICAL && MoveUp(wrapAround))
285 if (m_orientation == HORIZONTAL && MoveLeft(wrapAround))
290 void CGUIPanelContainer::OnDown()
292 bool wrapAround = m_actionDown.GetNavigation() == GetID() || !m_actionDown.HasActionsMeetingCondition();
293 if (m_orientation == VERTICAL && MoveDown(wrapAround))
295 if (m_orientation == HORIZONTAL && MoveRight(wrapAround))
297 return CGUIControl::OnDown();
300 bool CGUIPanelContainer::MoveDown(bool wrapAround)
302 if (GetCursor() + m_itemsPerRow < m_itemsPerPage * m_itemsPerRow && (GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
303 { // move to last item if necessary
304 if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
305 SetCursor((int)m_items.size() - 1 - GetOffset()*m_itemsPerRow);
307 SetCursor(GetCursor() + m_itemsPerRow);
309 else if ((GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
310 { // we scroll to the next row, and move to last item if necessary
311 if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
312 SetCursor((int)m_items.size() - 1 - (GetOffset() + 1)*m_itemsPerRow);
313 ScrollToOffset(GetOffset() + 1);
316 { // move first item in list
317 SetCursor(GetCursor() % m_itemsPerRow);
319 SetContainerMoving(1);
326 bool CGUIPanelContainer::MoveUp(bool wrapAround)
328 if (GetCursor() >= m_itemsPerRow)
329 SetCursor(GetCursor() - m_itemsPerRow);
330 else if (GetOffset() > 0)
331 ScrollToOffset(GetOffset() - 1);
333 { // move last item in list in this column
334 SetCursor((GetCursor() % m_itemsPerRow) + (m_itemsPerPage - 1) * m_itemsPerRow);
335 int offset = max((int)GetRows() - m_itemsPerPage, 0);
336 // should check here whether cursor is actually allowed here, and reduce accordingly
337 if (offset * m_itemsPerRow + GetCursor() >= (int)m_items.size())
338 SetCursor((int)m_items.size() - offset * m_itemsPerRow - 1);
339 ScrollToOffset(offset);
340 SetContainerMoving(-1);
347 bool CGUIPanelContainer::MoveLeft(bool wrapAround)
349 int col = GetCursor() % m_itemsPerRow;
351 SetCursor(GetCursor() - 1);
354 SetCursor(GetCursor() + m_itemsPerRow - 1);
355 if (GetOffset() * m_itemsPerRow + GetCursor() >= (int)m_items.size())
356 SetCursor((int)m_items.size() - GetOffset() * m_itemsPerRow - 1);
363 bool CGUIPanelContainer::MoveRight(bool wrapAround)
365 int col = GetCursor() % m_itemsPerRow;
366 if (col + 1 < m_itemsPerRow && GetOffset() * m_itemsPerRow + GetCursor() + 1 < (int)m_items.size())
367 SetCursor(GetCursor() + 1);
368 else if (wrapAround) // move first item in row
369 SetCursor(GetCursor() - col);
375 // scrolls the said amount
376 void CGUIPanelContainer::Scroll(int amount)
378 // increase or decrease the offset
379 int offset = GetOffset() + amount;
380 if (offset > ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow)
382 offset = ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow;
384 if (offset < 0) offset = 0;
385 ScrollToOffset(offset);
388 void CGUIPanelContainer::ValidateOffset()
390 if (!m_layout) return;
391 // first thing is we check the range of our offset
392 // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
393 if (GetOffset() > (int)GetRows() - m_itemsPerPage || (!m_scroller.IsScrolling() && m_scroller.GetValue() > ((int)GetRows() - m_itemsPerPage) * m_layout->Size(m_orientation)))
395 SetOffset(std::max(0, (int)GetRows() - m_itemsPerPage));
396 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
398 if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
401 m_scroller.SetValue(0);
405 void CGUIPanelContainer::SetCursor(int cursor)
407 // +1 to ensure we're OK if we have a half item
408 if (cursor > (m_itemsPerPage + 1)*m_itemsPerRow - 1) cursor = (m_itemsPerPage + 1)*m_itemsPerRow - 1;
409 if (cursor < 0) cursor = 0;
411 SetContainerMoving(cursor - GetCursor());
412 CGUIBaseContainer::SetCursor(cursor);
415 void CGUIPanelContainer::CalculateLayout()
419 if (!m_layout || !m_focusedLayout) return;
420 // calculate the number of items to display
421 if (m_orientation == HORIZONTAL)
423 m_itemsPerRow = (int)(m_height / m_layout->Size(VERTICAL));
424 m_itemsPerPage = (int)(m_width / m_layout->Size(HORIZONTAL));
428 m_itemsPerRow = (int)(m_width / m_layout->Size(HORIZONTAL));
429 m_itemsPerPage = (int)(m_height / m_layout->Size(VERTICAL));
431 if (m_itemsPerRow < 1) m_itemsPerRow = 1;
432 if (m_itemsPerPage < 1) m_itemsPerPage = 1;
434 // ensure that the scroll offset is a multiple of our size
435 m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
438 unsigned int CGUIPanelContainer::GetRows() const
440 assert(m_itemsPerRow > 0);
441 return (m_items.size() + m_itemsPerRow - 1) / m_itemsPerRow;
444 float CGUIPanelContainer::AnalogScrollSpeed() const
446 return 10.0f / m_itemsPerPage;
449 int CGUIPanelContainer::CorrectOffset(int offset, int cursor) const
451 return offset * m_itemsPerRow + cursor;
454 int CGUIPanelContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
459 float sizeX = m_orientation == VERTICAL ? m_layout->Size(HORIZONTAL) : m_layout->Size(VERTICAL);
460 float sizeY = m_orientation == VERTICAL ? m_layout->Size(VERTICAL) : m_layout->Size(HORIZONTAL);
462 float posY = m_orientation == VERTICAL ? point.y : point.x;
463 for (int y = 0; y < m_itemsPerPage + 1; y++) // +1 to ensure if we have a half item we can select it
465 float posX = m_orientation == VERTICAL ? point.x : point.y;
466 for (int x = 0; x < m_itemsPerRow; x++)
468 int item = x + y * m_itemsPerRow;
469 if (posX < sizeX && posY < sizeY && item + GetOffset() < (int)m_items.size())
480 bool CGUIPanelContainer::SelectItemFromPoint(const CPoint &point)
482 int cursor = GetCursorFromPoint(point);
489 bool CGUIPanelContainer::GetCondition(int condition, int data) const
490 { // probably only works vertically atm...
491 int row = GetCursor() / m_itemsPerRow;
492 int col = GetCursor() % m_itemsPerRow;
493 if (m_orientation == HORIZONTAL)
498 return (row == data);
499 case CONTAINER_COLUMN:
500 return (col == data);
502 return CGUIBaseContainer::GetCondition(condition, data);
506 void CGUIPanelContainer::SelectItem(int item)
508 // Check that our offset is valid
510 // only select an item if it's in a valid range
511 if (item >= 0 && item < (int)m_items.size())
513 // Select the item requested
514 if (item >= GetOffset() * m_itemsPerRow && item < (GetOffset() + m_itemsPerPage) * m_itemsPerRow)
515 { // the item is on the current page, so don't change it.
516 SetCursor(item - GetOffset() * m_itemsPerRow);
518 else if (item < GetOffset() * m_itemsPerRow)
519 { // item is on a previous page - make it the first item on the page
520 SetCursor(item % m_itemsPerRow);
521 ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
523 else // (item >= GetOffset()+m_itemsPerPage)
524 { // item is on a later page - make it the last row on the page
525 SetCursor(item % m_itemsPerRow + m_itemsPerRow * (m_itemsPerPage - 1));
526 ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
531 bool CGUIPanelContainer::HasPreviousPage() const
533 return (GetOffset() > 0);
536 bool CGUIPanelContainer::HasNextPage() const
538 return (GetOffset() != (int)GetRows() - m_itemsPerPage && (int)GetRows() > m_itemsPerPage);