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.
9 #include "GUIPanelContainer.h"
12 #include "GUIListItemLayout.h"
13 #include "GUIMessage.h"
14 #include "guilib/guiinfo/GUIInfoLabels.h"
15 #include "input/actions/Action.h"
16 #include "input/actions/ActionIDs.h"
17 #include "utils/StringUtils.h"
21 CGUIPanelContainer::CGUIPanelContainer(int parentID
, int controlID
, float posX
, float posY
, float width
, float height
, ORIENTATION orientation
, const CScroller
& scroller
, int preloadItems
)
22 : CGUIBaseContainer(parentID
, controlID
, posX
, posY
, width
, height
, orientation
, scroller
, preloadItems
)
24 ControlType
= GUICONTAINER_PANEL
;
25 m_type
= VIEW_TYPE_ICON
;
29 CGUIPanelContainer::~CGUIPanelContainer(void) = default;
31 void CGUIPanelContainer::Process(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
)
38 if (!m_layout
|| !m_focusedLayout
)
41 UpdateScrollOffset(currentTime
);
43 int offset
= (int)(m_scroller
.GetValue() / m_layout
->Size(m_orientation
));
45 int cacheBefore
, cacheAfter
;
46 GetCacheOffsets(cacheBefore
, cacheAfter
);
48 // Free memory not used on screen
49 if ((int)m_items
.size() > m_itemsPerPage
+ cacheBefore
+ cacheAfter
)
50 FreeMemory(CorrectOffset(offset
- cacheBefore
, 0), CorrectOffset(offset
+ m_itemsPerPage
+ 1 + cacheAfter
, 0));
52 CPoint origin
= CPoint(m_posX
, m_posY
) + m_renderOffset
;
53 float pos
= (m_orientation
== VERTICAL
) ? origin
.y
: origin
.x
;
54 float end
= (m_orientation
== VERTICAL
) ? m_posY
+ m_height
: m_posX
+ m_width
;
55 pos
+= (offset
- cacheBefore
) * m_layout
->Size(m_orientation
) - m_scroller
.GetValue();
56 end
+= cacheAfter
* m_layout
->Size(m_orientation
);
58 int current
= (offset
- cacheBefore
) * m_itemsPerRow
;
60 while (pos
< end
&& m_items
.size())
62 if (current
>= (int)m_items
.size())
66 std::shared_ptr
<CGUIListItem
> item
= m_items
[current
];
67 item
->SetCurrentItem(current
+ 1);
68 bool focused
= (current
== GetOffset() * m_itemsPerRow
+ GetCursor()) && m_bHasFocus
;
70 if (m_orientation
== VERTICAL
)
71 ProcessItem(origin
.x
+ col
* m_layout
->Size(HORIZONTAL
), pos
, item
, focused
, currentTime
, dirtyregions
);
73 ProcessItem(pos
, origin
.y
+ col
* m_layout
->Size(VERTICAL
), item
, focused
, currentTime
, dirtyregions
);
75 // increment our position
76 if (col
< m_itemsPerRow
- 1)
80 pos
+= m_layout
->Size(m_orientation
);
86 // when we are scrolling up, offset will become lower (integer division, see offset calc)
87 // to have same behaviour when scrolling down, we need to set page control to offset+1
88 UpdatePageControl(offset
+ (m_scroller
.IsScrollingDown() ? 1 : 0));
90 CGUIControl::Process(currentTime
, dirtyregions
);
94 void CGUIPanelContainer::Render()
96 if (!m_layout
|| !m_focusedLayout
)
99 int offset
= (int)(m_scroller
.GetValue() / m_layout
->Size(m_orientation
));
101 int cacheBefore
, cacheAfter
;
102 GetCacheOffsets(cacheBefore
, cacheAfter
);
104 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX
, m_posY
, m_width
, m_height
))
106 CPoint origin
= CPoint(m_posX
, m_posY
) + m_renderOffset
;
107 float pos
= (m_orientation
== VERTICAL
) ? origin
.y
: origin
.x
;
108 float end
= (m_orientation
== VERTICAL
) ? m_posY
+ m_height
: m_posX
+ m_width
;
109 pos
+= (offset
- cacheBefore
) * m_layout
->Size(m_orientation
) - m_scroller
.GetValue();
110 end
+= cacheAfter
* m_layout
->Size(m_orientation
);
112 float focusedPos
= 0;
114 std::shared_ptr
<CGUIListItem
> focusedItem
;
115 int current
= (offset
- cacheBefore
) * m_itemsPerRow
;
117 std::vector
<RENDERITEM
> renderitems
;
118 while (pos
< end
&& m_items
.size())
120 if (current
>= (int)m_items
.size())
124 std::shared_ptr
<CGUIListItem
> item
= m_items
[current
];
125 bool focused
= (current
== GetOffset() * m_itemsPerRow
+ GetCursor()) && m_bHasFocus
;
135 if (m_orientation
== VERTICAL
)
136 renderitems
.emplace_back(
137 RENDERITEM
{origin
.x
+ col
* m_layout
->Size(HORIZONTAL
), pos
, item
, false});
139 renderitems
.emplace_back(
140 RENDERITEM
{pos
, origin
.y
+ col
* m_layout
->Size(VERTICAL
), item
, false});
143 // increment our position
144 if (col
< m_itemsPerRow
- 1)
148 pos
+= m_layout
->Size(m_orientation
);
153 // and render the focused item last (for overlapping purposes)
156 if (m_orientation
== VERTICAL
)
157 renderitems
.emplace_back(RENDERITEM
{origin
.x
+ focusedCol
* m_layout
->Size(HORIZONTAL
),
158 focusedPos
, focusedItem
, true});
160 renderitems
.emplace_back(RENDERITEM
{
161 focusedPos
, origin
.y
+ focusedCol
* m_layout
->Size(VERTICAL
), focusedItem
, true});
164 if (CServiceBroker::GetWinSystem()->GetGfxContext().GetRenderOrder() ==
165 RENDER_ORDER_FRONT_TO_BACK
)
167 for (auto it
= std::crbegin(renderitems
); it
!= std::crend(renderitems
); it
++)
169 RenderItem(it
->posX
, it
->posY
, it
->item
.get(), it
->focused
);
174 for (const auto& renderitem
: renderitems
)
176 RenderItem(renderitem
.posX
, renderitem
.posY
, renderitem
.item
.get(), renderitem
.focused
);
180 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
182 CGUIControl::Render();
185 bool CGUIPanelContainer::OnAction(const CAction
&action
)
187 switch (action
.GetID())
191 if (GetOffset() == 0)
192 { // already on the first page, so move to the first item
196 { // scroll up to the previous page
197 Scroll( -m_itemsPerPage
);
202 case ACTION_PAGE_DOWN
:
204 if ((GetOffset() + m_itemsPerPage
) * m_itemsPerRow
>= (int)m_items
.size() || (int)m_items
.size() < m_itemsPerPage
)
205 { // already at the last page, so move to the last item.
206 SetCursor(m_items
.size() - GetOffset() * m_itemsPerRow
- 1);
209 { // scroll down to the next page
210 Scroll(m_itemsPerPage
);
215 // smooth scrolling (for analog controls)
216 case ACTION_SCROLL_UP
:
218 m_analogScrollCount
+= action
.GetAmount() * action
.GetAmount();
219 bool handled
= false;
220 while (m_analogScrollCount
> AnalogScrollSpeed())
223 m_analogScrollCount
-= AnalogScrollSpeed();
224 if (GetOffset() > 0)// && GetCursor() <= m_itemsPerPage * m_itemsPerRow / 2)
228 else if (GetCursor() > 0)
230 SetCursor(GetCursor() - 1);
236 case ACTION_SCROLL_DOWN
:
238 m_analogScrollCount
+= action
.GetAmount() * action
.GetAmount();
239 bool handled
= false;
240 while (m_analogScrollCount
> AnalogScrollSpeed())
243 m_analogScrollCount
-= AnalogScrollSpeed();
244 if ((GetOffset() + m_itemsPerPage
) * m_itemsPerRow
< (int)m_items
.size())// && GetCursor() >= m_itemsPerPage * m_itemsPerRow / 2)
248 else if (GetCursor() < m_itemsPerPage
* m_itemsPerRow
- 1 && GetOffset() * m_itemsPerRow
+ GetCursor() < (int)m_items
.size() - 1)
250 SetCursor(GetCursor() + 1);
257 return CGUIBaseContainer::OnAction(action
);
260 bool CGUIPanelContainer::OnMessage(CGUIMessage
& message
)
262 if (message
.GetControlId() == GetID() )
264 if (message
.GetMessage() == GUI_MSG_LABEL_RESET
)
267 // fall through to base class
270 return CGUIBaseContainer::OnMessage(message
);
273 void CGUIPanelContainer::OnLeft()
275 CGUIAction action
= GetAction(ACTION_MOVE_LEFT
);
276 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
277 if (m_orientation
== VERTICAL
&& MoveLeft(wrapAround
))
279 if (m_orientation
== HORIZONTAL
&& MoveUp(wrapAround
))
281 CGUIControl::OnLeft();
284 void CGUIPanelContainer::OnRight()
286 CGUIAction action
= GetAction(ACTION_MOVE_RIGHT
);
287 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
288 if (m_orientation
== VERTICAL
&& MoveRight(wrapAround
))
290 if (m_orientation
== HORIZONTAL
&& MoveDown(wrapAround
))
292 return CGUIControl::OnRight();
295 void CGUIPanelContainer::OnUp()
297 CGUIAction action
= GetAction(ACTION_MOVE_UP
);
298 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
299 if (m_orientation
== VERTICAL
&& MoveUp(wrapAround
))
301 if (m_orientation
== HORIZONTAL
&& MoveLeft(wrapAround
))
306 void CGUIPanelContainer::OnDown()
308 CGUIAction action
= GetAction(ACTION_MOVE_DOWN
);
309 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
310 if (m_orientation
== VERTICAL
&& MoveDown(wrapAround
))
312 if (m_orientation
== HORIZONTAL
&& MoveRight(wrapAround
))
314 return CGUIControl::OnDown();
317 bool CGUIPanelContainer::MoveDown(bool wrapAround
)
319 if (GetCursor() + m_itemsPerRow
< m_itemsPerPage
* m_itemsPerRow
&& (GetOffset() + 1 + GetCursor() / m_itemsPerRow
) * m_itemsPerRow
< (int)m_items
.size())
320 { // move to last item if necessary
321 if ((GetOffset() + 1)*m_itemsPerRow
+ GetCursor() >= (int)m_items
.size())
322 SetCursor((int)m_items
.size() - 1 - GetOffset()*m_itemsPerRow
);
324 SetCursor(GetCursor() + m_itemsPerRow
);
326 else if ((GetOffset() + 1 + GetCursor() / m_itemsPerRow
) * m_itemsPerRow
< (int)m_items
.size())
327 { // we scroll to the next row, and move to last item if necessary
328 if ((GetOffset() + 1)*m_itemsPerRow
+ GetCursor() >= (int)m_items
.size())
329 SetCursor((int)m_items
.size() - 1 - (GetOffset() + 1)*m_itemsPerRow
);
330 ScrollToOffset(GetOffset() + 1);
333 { // move first item in list
334 SetCursor(GetCursor() % m_itemsPerRow
);
336 SetContainerMoving(1);
343 bool CGUIPanelContainer::MoveUp(bool wrapAround
)
345 if (GetCursor() >= m_itemsPerRow
)
346 SetCursor(GetCursor() - m_itemsPerRow
);
347 else if (GetOffset() > 0)
348 ScrollToOffset(GetOffset() - 1);
350 { // move last item in list in this column
351 SetCursor((GetCursor() % m_itemsPerRow
) + (m_itemsPerPage
- 1) * m_itemsPerRow
);
352 int offset
= std::max((int)GetRows() - m_itemsPerPage
, 0);
353 // should check here whether cursor is actually allowed here, and reduce accordingly
354 if (offset
* m_itemsPerRow
+ GetCursor() >= (int)m_items
.size())
355 SetCursor((int)m_items
.size() - offset
* m_itemsPerRow
- 1);
356 ScrollToOffset(offset
);
357 SetContainerMoving(-1);
364 bool CGUIPanelContainer::MoveLeft(bool wrapAround
)
366 int col
= GetCursor() % m_itemsPerRow
;
368 SetCursor(GetCursor() - 1);
371 SetCursor(GetCursor() + m_itemsPerRow
- 1);
372 if (GetOffset() * m_itemsPerRow
+ GetCursor() >= (int)m_items
.size())
373 SetCursor((int)m_items
.size() - GetOffset() * m_itemsPerRow
- 1);
380 bool CGUIPanelContainer::MoveRight(bool wrapAround
)
382 int col
= GetCursor() % m_itemsPerRow
;
383 if (col
+ 1 < m_itemsPerRow
&& GetOffset() * m_itemsPerRow
+ GetCursor() + 1 < (int)m_items
.size())
384 SetCursor(GetCursor() + 1);
385 else if (wrapAround
) // move first item in row
386 SetCursor(GetCursor() - col
);
392 // scrolls the said amount
393 void CGUIPanelContainer::Scroll(int amount
)
395 // increase or decrease the offset
396 int offset
= GetOffset() + amount
;
397 if (offset
> ((int)GetRows() - m_itemsPerPage
) * m_itemsPerRow
)
399 offset
= ((int)GetRows() - m_itemsPerPage
) * m_itemsPerRow
;
401 if (offset
< 0) offset
= 0;
402 ScrollToOffset(offset
);
405 void CGUIPanelContainer::ValidateOffset()
407 if (!m_layout
) return;
408 // first thing is we check the range of our offset
409 // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
410 if (GetOffset() > (int)GetRows() - m_itemsPerPage
|| (!m_scroller
.IsScrolling() && m_scroller
.GetValue() > ((int)GetRows() - m_itemsPerPage
) * m_layout
->Size(m_orientation
)))
412 SetOffset(std::max(0, (int)GetRows() - m_itemsPerPage
));
413 m_scroller
.SetValue(GetOffset() * m_layout
->Size(m_orientation
));
415 if (GetOffset() < 0 || (!m_scroller
.IsScrolling() && m_scroller
.GetValue() < 0))
418 m_scroller
.SetValue(0);
422 void CGUIPanelContainer::SetCursor(int cursor
)
424 // exceeds the number of items the panel can hold
425 if (cursor
> m_itemsPerPage
* m_itemsPerRow
- 1)
426 cursor
= m_itemsPerPage
* m_itemsPerRow
- 1;
428 // exceeds the number of items being displayed
429 const int itemsOn
= m_items
.size() - 1 - GetOffset() * m_itemsPerRow
;
430 if (cursor
> itemsOn
)
437 SetContainerMoving(cursor
- GetCursor());
438 CGUIBaseContainer::SetCursor(cursor
);
441 void CGUIPanelContainer::CalculateLayout()
445 if (!m_layout
|| !m_focusedLayout
) return;
446 // calculate the number of items to display
447 if (m_orientation
== HORIZONTAL
)
449 m_itemsPerRow
= (int)(m_height
/ m_layout
->Size(VERTICAL
));
450 m_itemsPerPage
= (int)(m_width
/ m_layout
->Size(HORIZONTAL
));
454 m_itemsPerRow
= (int)(m_width
/ m_layout
->Size(HORIZONTAL
));
455 m_itemsPerPage
= (int)(m_height
/ m_layout
->Size(VERTICAL
));
457 if (m_itemsPerRow
< 1) m_itemsPerRow
= 1;
458 if (m_itemsPerPage
< 1) m_itemsPerPage
= 1;
460 // ensure that the scroll offset is a multiple of our size
461 m_scroller
.SetValue(GetOffset() * m_layout
->Size(m_orientation
));
464 unsigned int CGUIPanelContainer::GetRows() const
466 assert(m_itemsPerRow
> 0);
467 return (m_items
.size() + m_itemsPerRow
- 1) / m_itemsPerRow
;
470 float CGUIPanelContainer::AnalogScrollSpeed() const
472 return 10.0f
/ m_itemsPerPage
;
475 int CGUIPanelContainer::CorrectOffset(int offset
, int cursor
) const
477 return offset
* m_itemsPerRow
+ cursor
;
480 int CGUIPanelContainer::GetCursorFromPoint(const CPoint
&point
, CPoint
*itemPoint
) const
485 float sizeX
= m_orientation
== VERTICAL
? m_layout
->Size(HORIZONTAL
) : m_layout
->Size(VERTICAL
);
486 float sizeY
= m_orientation
== VERTICAL
? m_layout
->Size(VERTICAL
) : m_layout
->Size(HORIZONTAL
);
488 float posY
= m_orientation
== VERTICAL
? point
.y
: point
.x
;
489 for (int y
= 0; y
< m_itemsPerPage
+ 1; y
++) // +1 to ensure if we have a half item we can select it
491 float posX
= m_orientation
== VERTICAL
? point
.x
: point
.y
;
492 for (int x
= 0; x
< m_itemsPerRow
; x
++)
494 int item
= x
+ y
* m_itemsPerRow
;
495 if (posX
< sizeX
&& posY
< sizeY
&& item
+ GetOffset() < (int)m_items
.size())
506 bool CGUIPanelContainer::SelectItemFromPoint(const CPoint
&point
)
508 int cursor
= GetCursorFromPoint(point
);
515 int CGUIPanelContainer::GetCurrentRow() const
517 return m_itemsPerRow
> 0 ? GetCursor() / m_itemsPerRow
: 0;
520 int CGUIPanelContainer::GetCurrentColumn() const
522 return GetCursor() % m_itemsPerRow
;
525 bool CGUIPanelContainer::GetCondition(int condition
, int data
) const
527 int row
= GetCurrentRow();
528 int col
= GetCurrentColumn();
530 if (m_orientation
== HORIZONTAL
)
536 return (row
== data
);
537 case CONTAINER_COLUMN
:
538 return (col
== data
);
540 return CGUIBaseContainer::GetCondition(condition
, data
);
544 std::string
CGUIPanelContainer::GetLabel(int info
) const
546 int row
= GetCurrentRow();
547 int col
= GetCurrentColumn();
549 if (m_orientation
== HORIZONTAL
)
555 return std::to_string(row
);
556 case CONTAINER_COLUMN
:
557 return std::to_string(col
);
559 return CGUIBaseContainer::GetLabel(info
);
561 return StringUtils::Empty
;
564 void CGUIPanelContainer::SelectItem(int item
)
566 // Check that our offset is valid
568 // only select an item if it's in a valid range
569 if (item
>= 0 && item
< (int)m_items
.size())
571 // Select the item requested
572 if (item
>= GetOffset() * m_itemsPerRow
&& item
< (GetOffset() + m_itemsPerPage
) * m_itemsPerRow
)
573 { // the item is on the current page, so don't change it.
574 SetCursor(item
- GetOffset() * m_itemsPerRow
);
576 else if (item
< GetOffset() * m_itemsPerRow
)
577 { // item is on a previous page - make it the first item on the page
578 SetCursor(item
% m_itemsPerRow
);
579 ScrollToOffset((item
- GetCursor()) / m_itemsPerRow
);
581 else // (item >= GetOffset()+m_itemsPerPage)
582 { // item is on a later page - make it the last row on the page
583 SetCursor(item
% m_itemsPerRow
+ m_itemsPerRow
* (m_itemsPerPage
- 1));
584 ScrollToOffset((item
- GetCursor()) / m_itemsPerRow
);
589 bool CGUIPanelContainer::HasPreviousPage() const
591 return (GetOffset() > 0);
594 bool CGUIPanelContainer::HasNextPage() const
596 return (GetOffset() != (int)GetRows() - m_itemsPerPage
&& (int)GetRows() > m_itemsPerPage
);
599 void CGUIPanelContainer::ScrollToOffset(int offset
)
601 CGUIBaseContainer::ScrollToOffset(offset
);
602 SetCursor(GetCursor());