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 "GUIBaseContainer.h"
12 #include "GUIInfoManager.h"
13 #include "GUIListItemLayout.h"
14 #include "GUIMessage.h"
15 #include "ServiceBroker.h"
16 #include "guilib/guiinfo/GUIInfoLabels.h"
17 #include "guilib/listproviders/IListProvider.h"
18 #include "input/actions/Action.h"
19 #include "input/actions/ActionIDs.h"
20 #include "input/keyboard/KeyIDs.h"
21 #include "input/mouse/MouseEvent.h"
22 #include "settings/Settings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/CharsetConverter.h"
25 #include "utils/MathUtils.h"
26 #include "utils/SortUtils.h"
27 #include "utils/StringUtils.h"
28 #include "utils/TimeUtils.h"
29 #include "utils/XBMCTinyXML.h"
30 #include "utils/log.h"
36 #define HOLD_TIME_START 100
37 #define HOLD_TIME_END 3000
38 #define SCROLLING_GAP 200U
39 #define SCROLLING_THRESHOLD 300U
41 CGUIBaseContainer::CGUIBaseContainer(int parentID
, int controlID
, float posX
, float posY
, float width
, float height
, ORIENTATION orientation
, const CScroller
& scroller
, int preloadItems
)
42 : IGUIContainer(parentID
, controlID
, posX
, posY
, width
, height
)
43 , m_scroller(scroller
)
50 m_orientation
= orientation
;
51 m_analogScrollCount
= 0;
54 m_focusedLayout
= NULL
;
55 m_cacheItems
= preloadItems
;
56 m_scrollItemsPerFrame
= 0.0f
;
57 m_type
= VIEW_TYPE_NONE
;
58 m_autoScrollMoveTime
= 0;
59 m_autoScrollDelayTime
= 0;
60 m_autoScrollIsReversed
= false;
64 CGUIBaseContainer::CGUIBaseContainer(const CGUIBaseContainer
& other
)
65 : IGUIContainer(other
),
66 m_renderOffset(other
.m_renderOffset
),
67 m_analogScrollCount(other
.m_analogScrollCount
),
68 m_lastHoldTime(other
.m_lastHoldTime
),
69 m_orientation(other
.m_orientation
),
70 m_itemsPerPage(other
.m_itemsPerPage
),
71 m_pageControl(other
.m_pageControl
),
72 m_layoutCondition(other
.m_layoutCondition
),
73 m_focusedLayoutCondition(other
.m_focusedLayoutCondition
),
74 m_scroller(other
.m_scroller
),
75 m_listProvider(other
.m_listProvider
? other
.m_listProvider
->Clone() : nullptr),
76 m_wasReset(other
.m_wasReset
),
77 m_letterOffsets(other
.m_letterOffsets
),
78 m_autoScrollCondition(other
.m_autoScrollCondition
),
79 m_autoScrollMoveTime(other
.m_autoScrollMoveTime
),
80 m_autoScrollDelayTime(other
.m_autoScrollDelayTime
),
81 m_autoScrollIsReversed(other
.m_autoScrollIsReversed
),
82 m_lastRenderTime(other
.m_lastRenderTime
),
83 m_cursor(other
.m_cursor
),
84 m_offset(other
.m_offset
),
85 m_cacheItems(other
.m_cacheItems
),
86 m_scrollTimer(other
.m_scrollTimer
),
87 m_lastScrollStartTimer(other
.m_lastScrollStartTimer
),
88 m_pageChangeTimer(other
.m_pageChangeTimer
),
89 m_clickActions(other
.m_clickActions
),
90 m_focusActions(other
.m_focusActions
),
91 m_unfocusActions(other
.m_unfocusActions
),
92 m_matchTimer(other
.m_matchTimer
),
93 m_match(other
.m_match
),
94 m_scrollItemsPerFrame(other
.m_scrollItemsPerFrame
),
95 m_gestureActive(other
.m_gestureActive
),
96 m_waitForScrollEnd(other
.m_waitForScrollEnd
),
97 m_lastScrollValue(other
.m_lastScrollValue
)
99 // Initialize CGUIControl
100 m_bInvalidated
= true;
102 for (const auto& item
: other
.m_items
)
103 m_items
.emplace_back(std::make_shared
<CGUIListItem
>(*item
));
105 for (const auto& layout
: other
.m_layouts
)
106 m_layouts
.emplace_back(layout
, this);
108 for (const auto& focusedLayout
: other
.m_focusedLayouts
)
109 m_focusedLayouts
.emplace_back(focusedLayout
, this);
112 CGUIBaseContainer::~CGUIBaseContainer(void)
114 // release the container from items
115 for (const auto& item
: m_items
)
119 void CGUIBaseContainer::DoProcess(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
)
121 CGUIControl::DoProcess(currentTime
, dirtyregions
);
123 if (m_pageChangeTimer
.IsRunning() && m_pageChangeTimer
.GetElapsedMilliseconds() > 200)
124 m_pageChangeTimer
.Stop();
127 // if not visible, we reset the autoscroll timer
128 if (!IsVisible() && m_autoScrollMoveTime
)
130 ResetAutoScrolling();
134 void CGUIBaseContainer::Process(unsigned int currentTime
, CDirtyRegionList
&dirtyregions
)
136 // update our auto-scrolling as necessary
137 UpdateAutoScrolling(currentTime
);
139 if (!m_waitForScrollEnd
&& !m_gestureActive
)
145 if (!m_layout
|| !m_focusedLayout
) return;
147 UpdateScrollOffset(currentTime
);
149 if (m_scroller
.IsScrolling())
152 int offset
= (int)floorf(m_scroller
.GetValue() / m_layout
->Size(m_orientation
));
154 int cacheBefore
, cacheAfter
;
155 GetCacheOffsets(cacheBefore
, cacheAfter
);
157 // Free memory not used on screen
158 if ((int)m_items
.size() > m_itemsPerPage
+ cacheBefore
+ cacheAfter
)
159 FreeMemory(CorrectOffset(offset
- cacheBefore
, 0), CorrectOffset(offset
+ m_itemsPerPage
+ 1 + cacheAfter
, 0));
161 CPoint origin
= CPoint(m_posX
, m_posY
) + m_renderOffset
;
162 float pos
= (m_orientation
== VERTICAL
) ? origin
.y
: origin
.x
;
163 float end
= (m_orientation
== VERTICAL
) ? m_posY
+ m_height
: m_posX
+ m_width
;
165 // we offset our draw position to take into account scrolling and whether or not our focused
166 // item is offscreen "above" the list.
167 float drawOffset
= (offset
- cacheBefore
) * m_layout
->Size(m_orientation
) - m_scroller
.GetValue();
168 if (GetOffset() + GetCursor() < offset
)
169 drawOffset
+= m_focusedLayout
->Size(m_orientation
) - m_layout
->Size(m_orientation
);
171 end
+= cacheAfter
* m_layout
->Size(m_orientation
);
173 int current
= offset
- cacheBefore
;
174 while (pos
< end
&& m_items
.size())
176 int itemNo
= CorrectOffset(current
, 0);
177 if (itemNo
>= (int)m_items
.size())
179 bool focused
= (current
== GetOffset() + GetCursor());
182 std::shared_ptr
<CGUIListItem
> item
= m_items
[itemNo
];
183 item
->SetCurrentItem(itemNo
+ 1);
186 if (m_orientation
== VERTICAL
)
187 ProcessItem(origin
.x
, pos
, item
, focused
, currentTime
, dirtyregions
);
189 ProcessItem(pos
, origin
.y
, item
, focused
, currentTime
, dirtyregions
);
191 // increment our position
192 pos
+= focused
? m_focusedLayout
->Size(m_orientation
) : m_layout
->Size(m_orientation
);
196 // when we are scrolling up, offset will become lower (integer division, see offset calc)
197 // to have same behaviour when scrolling down, we need to set page control to offset+1
198 UpdatePageControl(offset
+ (m_scroller
.IsScrollingDown() ? 1 : 0));
200 m_lastRenderTime
= currentTime
;
202 CGUIControl::Process(currentTime
, dirtyregions
);
205 void CGUIBaseContainer::ProcessItem(float posX
,
207 std::shared_ptr
<CGUIListItem
>& item
,
209 unsigned int currentTime
,
210 CDirtyRegionList
& dirtyregions
)
212 if (!m_focusedLayout
|| !m_layout
) return;
215 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX
, posY
);
221 if (!item
->GetFocusedLayout())
223 item
->SetFocusedLayout(std::make_unique
<CGUIListItemLayout
>(*m_focusedLayout
, this));
225 if (item
->GetFocusedLayout())
227 if (item
!= m_lastItem
|| !HasFocus())
229 item
->GetFocusedLayout()->SetFocusedItem(0);
231 if (item
!= m_lastItem
&& HasFocus())
233 item
->GetFocusedLayout()->ResetAnimation(ANIM_TYPE_UNFOCUS
);
234 unsigned int subItem
= 1;
235 if (m_lastItem
&& m_lastItem
->GetFocusedLayout())
236 subItem
= m_lastItem
->GetFocusedLayout()->GetFocusedItem();
237 item
->GetFocusedLayout()->SetFocusedItem(subItem
? subItem
: 1);
239 item
->GetFocusedLayout()->Process(item
.get(), m_parentID
, currentTime
, dirtyregions
);
245 if (item
->GetFocusedLayout())
246 item
->GetFocusedLayout()->SetFocusedItem(0); // focus is not set
247 if (!item
->GetLayout())
249 auto layout
= std::make_unique
<CGUIListItemLayout
>(*m_layout
, this);
250 item
->SetLayout(std::move(layout
));
252 if (item
->GetFocusedLayout() && item
->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS
))
253 item
->GetFocusedLayout()->Process(item
.get(), m_parentID
, currentTime
, dirtyregions
);
254 if (item
->GetLayout())
255 item
->GetLayout()->Process(item
.get(), m_parentID
, currentTime
, dirtyregions
);
258 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
261 void CGUIBaseContainer::Render()
263 if (!m_layout
|| !m_focusedLayout
) return;
265 int offset
= (int)floorf(m_scroller
.GetValue() / m_layout
->Size(m_orientation
));
267 int cacheBefore
, cacheAfter
;
268 GetCacheOffsets(cacheBefore
, cacheAfter
);
270 if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX
, m_posY
, m_width
, m_height
))
272 CPoint origin
= CPoint(m_posX
, m_posY
) + m_renderOffset
;
273 float pos
= (m_orientation
== VERTICAL
) ? origin
.y
: origin
.x
;
274 float end
= (m_orientation
== VERTICAL
) ? m_posY
+ m_height
: m_posX
+ m_width
;
276 // we offset our draw position to take into account scrolling and whether or not our focused
277 // item is offscreen "above" the list.
278 float drawOffset
= (offset
- cacheBefore
) * m_layout
->Size(m_orientation
) - m_scroller
.GetValue();
279 if (GetOffset() + GetCursor() < offset
)
280 drawOffset
+= m_focusedLayout
->Size(m_orientation
) - m_layout
->Size(m_orientation
);
282 end
+= cacheAfter
* m_layout
->Size(m_orientation
);
284 float focusedPos
= 0;
285 std::shared_ptr
<CGUIListItem
> focusedItem
;
286 int current
= offset
- cacheBefore
;
287 while (pos
< end
&& m_items
.size())
289 int itemNo
= CorrectOffset(current
, 0);
290 if (itemNo
>= (int)m_items
.size())
292 bool focused
= (current
== GetOffset() + GetCursor());
295 std::shared_ptr
<CGUIListItem
> item
= m_items
[itemNo
];
304 if (m_orientation
== VERTICAL
)
305 RenderItem(origin
.x
, pos
, item
.get(), false);
307 RenderItem(pos
, origin
.y
, item
.get(), false);
310 // increment our position
311 pos
+= focused
? m_focusedLayout
->Size(m_orientation
) : m_layout
->Size(m_orientation
);
314 // render focused item last so it can overlap other items
317 if (m_orientation
== VERTICAL
)
318 RenderItem(origin
.x
, focusedPos
, focusedItem
.get(), true);
320 RenderItem(focusedPos
, origin
.y
, focusedItem
.get(), true);
323 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
326 CGUIControl::Render();
330 void CGUIBaseContainer::RenderItem(float posX
, float posY
, CGUIListItem
*item
, bool focused
)
332 if (!m_focusedLayout
|| !m_layout
) return;
335 CServiceBroker::GetWinSystem()->GetGfxContext().SetOrigin(posX
, posY
);
339 if (item
->GetFocusedLayout())
340 item
->GetFocusedLayout()->Render(item
, m_parentID
);
344 if (item
->GetFocusedLayout() && item
->GetFocusedLayout()->IsAnimating(ANIM_TYPE_UNFOCUS
))
345 item
->GetFocusedLayout()->Render(item
, m_parentID
);
346 else if (item
->GetLayout())
347 item
->GetLayout()->Render(item
, m_parentID
);
349 CServiceBroker::GetWinSystem()->GetGfxContext().RestoreOrigin();
352 bool CGUIBaseContainer::OnAction(const CAction
&action
)
354 if (action
.GetID() == KEY_UNICODE
)
357 g_charsetConverter
.wToUTF8({action
.GetUnicode()}, letter
);
358 OnJumpLetter(letter
);
361 // stop the timer on any other action
364 switch (action
.GetID())
366 case ACTION_MOVE_LEFT
:
367 case ACTION_MOVE_RIGHT
:
368 case ACTION_MOVE_DOWN
:
370 case ACTION_NAV_BACK
:
371 case ACTION_PREVIOUS_MENU
:
373 if (!HasFocus()) return false;
375 if (action
.GetHoldTime() > HOLD_TIME_START
&&
376 ((m_orientation
== VERTICAL
&& (action
.GetID() == ACTION_MOVE_UP
|| action
.GetID() == ACTION_MOVE_DOWN
)) ||
377 (m_orientation
== HORIZONTAL
&& (action
.GetID() == ACTION_MOVE_LEFT
|| action
.GetID() == ACTION_MOVE_RIGHT
))))
378 { // action is held down - repeat a number of times
379 float speed
= std::min(1.0f
, (float)(action
.GetHoldTime() - HOLD_TIME_START
) / (HOLD_TIME_END
- HOLD_TIME_START
));
380 unsigned int frameDuration
= std::min(CTimeUtils::GetFrameTime() - m_lastHoldTime
, 50u); // max 20fps
382 // maximal scroll rate is at least 30 items per second, and at most (item_rows/7) items per second
383 // i.e. timed to take 7 seconds to traverse the list at full speed.
384 // minimal scroll rate is at least 10 items per second
385 float maxSpeed
= std::max(frameDuration
* 0.001f
* 30, frameDuration
* 0.001f
* GetRows() / 7);
386 float minSpeed
= frameDuration
* 0.001f
* 10;
387 m_scrollItemsPerFrame
+= std::max(minSpeed
, speed
*maxSpeed
); // accelerate to max speed
388 m_lastHoldTime
= CTimeUtils::GetFrameTime();
390 if(m_scrollItemsPerFrame
< 1.0f
)//not enough hold time accumulated for one step
393 while (m_scrollItemsPerFrame
>= 1)
395 if (action
.GetID() == ACTION_MOVE_LEFT
|| action
.GetID() == ACTION_MOVE_UP
)
399 m_scrollItemsPerFrame
--;
405 //if HOLD_TIME_START is reached we need
406 //a sane initial value for calculating m_scrollItemsPerPage
407 m_lastHoldTime
= CTimeUtils::GetFrameTime();
408 m_scrollItemsPerFrame
= 0.0f
;
409 return CGUIControl::OnAction(action
);
412 case ACTION_CONTEXT_MENU
:
416 case ACTION_SHOW_INFO
:
419 const int selected
= GetSelectedItem();
420 if (selected
>= 0 && selected
< static_cast<int>(m_items
.size()))
422 if (m_listProvider
->OnInfo(m_items
[selected
]))
428 else if (action
.GetID())
429 return OnClick(action
.GetID());
433 case ACTION_PLAYER_PLAY
:
436 const int selected
= GetSelectedItem();
437 if (selected
>= 0 && selected
< static_cast<int>(m_items
.size()))
439 if (m_listProvider
->OnPlay(m_items
[selected
]))
445 case ACTION_FIRST_PAGE
:
449 case ACTION_LAST_PAGE
:
451 SelectItem(m_items
.size() - 1);
454 case ACTION_NEXT_LETTER
:
457 case ACTION_PREV_LETTER
:
460 case ACTION_JUMP_SMS2
:
461 case ACTION_JUMP_SMS3
:
462 case ACTION_JUMP_SMS4
:
463 case ACTION_JUMP_SMS5
:
464 case ACTION_JUMP_SMS6
:
465 case ACTION_JUMP_SMS7
:
466 case ACTION_JUMP_SMS8
:
467 case ACTION_JUMP_SMS9
:
468 OnJumpSMS(action
.GetID() - ACTION_JUMP_SMS2
+ 2);
474 return action
.GetID() && OnClick(action
.GetID());
477 bool CGUIBaseContainer::OnMessage(CGUIMessage
& message
)
479 if (message
.GetControlId() == GetID() )
483 if (message
.GetMessage() == GUI_MSG_LABEL_BIND
&& message
.GetPointer())
486 CFileItemList
*items
= static_cast<CFileItemList
*>(message
.GetPointer());
487 for (int i
= 0; i
< items
->Size(); i
++)
488 m_items
.push_back(items
->Get(i
));
489 UpdateLayout(true); // true to refresh all items
490 UpdateScrollByLetter();
491 SelectItem(message
.GetParam1());
494 else if (message
.GetMessage() == GUI_MSG_LABEL_RESET
)
497 SetPageControlRange();
501 if (message
.GetMessage() == GUI_MSG_ITEM_SELECT
)
503 SelectItem(message
.GetParam1());
506 else if (message
.GetMessage() == GUI_MSG_SETFOCUS
)
508 if (message
.GetParam1()) // subfocus item is specified, so set the offset appropriately
510 int offset
= GetOffset();
511 if (message
.GetParam2() && message
.GetParam2() == 1)
513 int item
= std::min(offset
+ message
.GetParam1() - 1, (int)m_items
.size() - 1);
517 else if (message
.GetMessage() == GUI_MSG_ITEM_SELECTED
)
519 message
.SetParam1(GetSelectedItem());
522 else if (message
.GetMessage() == GUI_MSG_PAGE_CHANGE
)
524 if (message
.GetSenderId() == m_pageControl
&& IsVisible())
525 { // update our page if we're visible - not much point otherwise
526 if (message
.GetParam1() != GetOffset())
527 m_pageChangeTimer
.StartZero();
528 ScrollToOffset(message
.GetParam1());
532 else if (message
.GetMessage() == GUI_MSG_REFRESH_LIST
)
533 { // update our list contents
534 for (unsigned int i
= 0; i
< m_items
.size(); ++i
)
535 m_items
[i
]->SetInvalid();
537 else if (message
.GetMessage() == GUI_MSG_REFRESH_THUMBS
)
540 m_listProvider
->FreeResources(true);
542 else if (message
.GetMessage() == GUI_MSG_MOVE_OFFSET
)
544 int count
= message
.GetParam1();
558 return CGUIControl::OnMessage(message
);
561 void CGUIBaseContainer::OnUp()
563 CGUIAction action
= GetAction(ACTION_MOVE_UP
);
564 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
565 if (m_orientation
== VERTICAL
&& MoveUp(wrapAround
))
567 // with horizontal lists it doesn't make much sense to have multiselect labels
571 void CGUIBaseContainer::OnDown()
573 CGUIAction action
= GetAction(ACTION_MOVE_DOWN
);
574 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
575 if (m_orientation
== VERTICAL
&& MoveDown(wrapAround
))
577 // with horizontal lists it doesn't make much sense to have multiselect labels
578 CGUIControl::OnDown();
581 void CGUIBaseContainer::OnLeft()
583 CGUIAction action
= GetAction(ACTION_MOVE_LEFT
);
584 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
585 if (m_orientation
== HORIZONTAL
&& MoveUp(wrapAround
))
587 else if (m_orientation
== VERTICAL
)
589 CGUIListItemLayout
*focusedLayout
= GetFocusedLayout();
590 if (focusedLayout
&& focusedLayout
->MoveLeft())
593 CGUIControl::OnLeft();
596 void CGUIBaseContainer::OnRight()
598 CGUIAction action
= GetAction(ACTION_MOVE_RIGHT
);
599 bool wrapAround
= action
.GetNavigation() == GetID() || !action
.HasActionsMeetingCondition();
600 if (m_orientation
== HORIZONTAL
&& MoveDown(wrapAround
))
602 else if (m_orientation
== VERTICAL
)
604 CGUIListItemLayout
*focusedLayout
= GetFocusedLayout();
605 if (focusedLayout
&& focusedLayout
->MoveRight())
608 CGUIControl::OnRight();
611 void CGUIBaseContainer::OnNextLetter()
613 int offset
= CorrectOffset(GetOffset(), GetCursor());
614 for (unsigned int i
= 0; i
< m_letterOffsets
.size(); i
++)
616 if (m_letterOffsets
[i
].first
> offset
)
618 SelectItem(m_letterOffsets
[i
].first
);
624 void CGUIBaseContainer::OnPrevLetter()
626 int offset
= CorrectOffset(GetOffset(), GetCursor());
627 if (!m_letterOffsets
.size())
629 for (int i
= (int)m_letterOffsets
.size() - 1; i
>= 0; i
--)
631 if (m_letterOffsets
[i
].first
< offset
)
633 SelectItem(m_letterOffsets
[i
].first
);
639 void CGUIBaseContainer::OnJumpLetter(const std::string
& letter
, bool skip
/*=false*/)
641 if (m_matchTimer
.GetElapsedMilliseconds() < letter_match_timeout
)
646 m_matchTimer
.StartZero();
648 // we can't jump through letters if we have none
649 if (0 == m_letterOffsets
.size())
652 // find the current letter we're focused on
653 unsigned int offset
= CorrectOffset(GetOffset(), GetCursor());
654 unsigned int i
= (offset
+ ((skip
) ? 1 : 0)) % m_items
.size();
657 std::shared_ptr
<CGUIListItem
> item
= m_items
[i
];
658 std::string label
= item
->GetLabel();
659 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING
))
660 label
= SortUtils::RemoveArticles(label
);
661 if (0 == StringUtils::CompareNoCase(label
, m_match
, m_match
.size()))
666 i
= (i
+1) % m_items
.size();
667 } while (i
!= offset
);
669 // no match found - repeat with a single letter
671 g_charsetConverter
.utf8ToW(m_match
, wmatch
);
672 if (wmatch
.length() > 1)
675 OnJumpLetter(letter
, true);
679 void CGUIBaseContainer::OnJumpSMS(int letter
)
681 static const char letterMap
[8][6] = { "ABC2", "DEF3", "GHI4", "JKL5", "MNO6", "PQRS7", "TUV8", "WXYZ9" };
683 // only 2..9 supported
684 if (letter
< 2 || letter
> 9 || !m_letterOffsets
.size())
687 const std::string letters
= letterMap
[letter
- 2];
688 // find where we currently are
689 int offset
= CorrectOffset(GetOffset(), GetCursor());
690 unsigned int currentLetter
= 0;
691 while (currentLetter
+ 1 < m_letterOffsets
.size() && m_letterOffsets
[currentLetter
+ 1].first
<= offset
)
694 // now switch to the next letter
695 std::string current
= m_letterOffsets
[currentLetter
].second
;
696 size_t startPos
= (letters
.find(current
) + 1) % letters
.size();
697 // now jump to letters[startPos], or another one in the same range if possible
698 size_t pos
= startPos
;
701 // check if we can jump to this letter
702 for (size_t i
= 0; i
< m_letterOffsets
.size(); i
++)
704 if (m_letterOffsets
[i
].second
== letters
.substr(pos
, 1))
706 SelectItem(m_letterOffsets
[i
].first
);
710 pos
= (pos
+ 1) % letters
.size();
716 bool CGUIBaseContainer::MoveUp(bool wrapAround
)
721 bool CGUIBaseContainer::MoveDown(bool wrapAround
)
726 // scrolls the said amount
727 void CGUIBaseContainer::Scroll(int amount
)
729 ResetAutoScrolling();
730 ScrollToOffset(GetOffset() + amount
);
733 int CGUIBaseContainer::GetSelectedItem() const
735 return CorrectOffset(GetOffset(), GetCursor());
738 std::shared_ptr
<CGUIListItem
> CGUIBaseContainer::GetListItem(int offset
, unsigned int flag
) const
740 if (!m_items
.size() || !m_layout
)
741 return std::shared_ptr
<CGUIListItem
>();
742 int item
= GetSelectedItem() + offset
;
743 if (flag
& INFOFLAG_LISTITEM_POSITION
) // use offset from the first item displayed, taking into account scrolling
744 item
= CorrectOffset((int)(m_scroller
.GetValue() / m_layout
->Size(m_orientation
)), offset
);
746 if (flag
& INFOFLAG_LISTITEM_ABSOLUTE
) // use offset from the first item
747 item
= CorrectOffset(0, offset
);
749 if (flag
& INFOFLAG_LISTITEM_WRAP
)
751 item
%= ((int)m_items
.size());
752 if (item
< 0) item
+= m_items
.size();
753 return m_items
[item
];
757 if (item
>= 0 && item
< (int)m_items
.size())
758 return m_items
[item
];
760 return std::shared_ptr
<CGUIListItem
>();
763 CGUIListItemLayout
*CGUIBaseContainer::GetFocusedLayout() const
765 std::shared_ptr
<CGUIListItem
> item
= GetListItem(0);
766 if (item
.get()) return item
->GetFocusedLayout();
770 bool CGUIBaseContainer::OnMouseOver(const CPoint
&point
)
772 // select the item under the pointer
773 if (!m_waitForScrollEnd
)
774 SelectItemFromPoint(point
- CPoint(m_posX
, m_posY
));
775 return CGUIControl::OnMouseOver(point
);
778 EVENT_RESULT
CGUIBaseContainer::OnMouseEvent(const CPoint
& point
, const MOUSE::CMouseEvent
& event
)
780 if (event
.m_id
== ACTION_MOUSE_LEFT_CLICK
||
781 event
.m_id
== ACTION_MOUSE_DOUBLE_CLICK
||
782 event
.m_id
== ACTION_MOUSE_RIGHT_CLICK
)
785 m_waitForScrollEnd
= false;
786 int select
= GetSelectedItem();
787 if (SelectItemFromPoint(point
- CPoint(m_posX
, m_posY
)))
789 if (event
.m_id
!= ACTION_MOUSE_RIGHT_CLICK
|| select
== GetSelectedItem())
791 return EVENT_RESULT_HANDLED
;
794 else if (event
.m_id
== ACTION_MOUSE_WHEEL_UP
)
797 return EVENT_RESULT_HANDLED
;
799 else if (event
.m_id
== ACTION_MOUSE_WHEEL_DOWN
)
802 return EVENT_RESULT_HANDLED
;
804 else if (event
.m_id
== ACTION_GESTURE_NOTIFY
)
806 m_waitForScrollEnd
= true;
807 m_lastScrollValue
= m_scroller
.GetValue();
808 return (m_orientation
== HORIZONTAL
) ? EVENT_RESULT_PAN_HORIZONTAL
: EVENT_RESULT_PAN_VERTICAL
;
810 else if (event
.m_id
== ACTION_GESTURE_BEGIN
)
811 { // grab exclusive access
812 m_gestureActive
= true;
813 CGUIMessage
msg(GUI_MSG_EXCLUSIVE_MOUSE
, GetID(), GetParentID());
814 SendWindowMessage(msg
);
815 return EVENT_RESULT_HANDLED
;
817 else if (event
.m_id
== ACTION_GESTURE_PAN
)
818 { // do the drag and validate our offset (corrects for end of scroll)
819 m_scroller
.SetValue(m_scroller
.GetValue() - ((m_orientation
== HORIZONTAL
) ? event
.m_offsetX
: event
.m_offsetY
));
820 float size
= (m_layout
) ? m_layout
->Size(m_orientation
) : 10.0f
;
821 int offset
= MathUtils::round_int(static_cast<double>(m_scroller
.GetValue() / size
));
822 m_lastScrollStartTimer
.Stop();
823 m_scrollTimer
.Start();
824 const int absCursor
= CorrectOffset(GetOffset(), GetCursor());
827 // Notify Application if Inertial scrolling reaches lists end
828 if (m_waitForScrollEnd
)
830 if (fabs(m_scroller
.GetValue() - m_lastScrollValue
) < 0.001f
)
832 m_waitForScrollEnd
= false;
833 return EVENT_RESULT_UNHANDLED
;
836 m_lastScrollValue
= m_scroller
.GetValue();
840 CGUIBaseContainer::SetCursor(absCursor
- CorrectOffset(GetOffset(), 0));
842 return EVENT_RESULT_HANDLED
;
844 else if (event
.m_id
== ACTION_GESTURE_END
|| event
.m_id
== ACTION_GESTURE_ABORT
)
845 { // release exclusive access
846 CGUIMessage
msg(GUI_MSG_EXCLUSIVE_MOUSE
, 0, GetParentID());
847 SendWindowMessage(msg
);
848 m_scrollTimer
.Stop();
849 // and compute the nearest offset from this and scroll there
850 float size
= (m_layout
) ? m_layout
->Size(m_orientation
) : 10.0f
;
851 float offset
= m_scroller
.GetValue() / size
;
852 int toOffset
= MathUtils::round_int(static_cast<double>(offset
));
853 if (toOffset
< offset
)
854 SetOffset(toOffset
+1);
856 SetOffset(toOffset
-1);
857 ScrollToOffset(toOffset
);
859 SetCursor(GetCursor());
861 m_waitForScrollEnd
= false;
862 m_gestureActive
= false;
863 return EVENT_RESULT_HANDLED
;
865 return EVENT_RESULT_UNHANDLED
;
868 bool CGUIBaseContainer::OnClick(int actionID
)
871 if (actionID
== ACTION_SELECT_ITEM
|| actionID
== ACTION_MOUSE_LEFT_CLICK
)
875 int selected
= GetSelectedItem();
876 if (selected
>= 0 && selected
< static_cast<int>(m_items
.size()))
878 if (m_clickActions
.HasActionsMeetingCondition())
879 m_clickActions
.ExecuteActions(0, GetParentID(), m_items
[selected
]);
881 m_listProvider
->OnClick(m_items
[selected
]);
885 // grab the currently focused subitem (if applicable)
886 CGUIListItemLayout
*focusedLayout
= GetFocusedLayout();
888 subItem
= focusedLayout
->GetFocusedItem();
890 else if (actionID
== ACTION_MOUSE_RIGHT_CLICK
)
895 // Don't know what to do, so send to our parent window.
896 CGUIMessage
msg(GUI_MSG_CLICKED
, GetID(), GetParentID(), actionID
, subItem
);
897 return SendWindowMessage(msg
);
900 bool CGUIBaseContainer::OnContextMenu()
904 int selected
= GetSelectedItem();
905 if (selected
>= 0 && selected
< static_cast<int>(m_items
.size()))
907 m_listProvider
->OnContextMenu(m_items
[selected
]);
914 std::string
CGUIBaseContainer::GetDescription() const
916 std::string strLabel
;
917 int item
= GetSelectedItem();
918 if (item
>= 0 && item
< (int)m_items
.size())
920 std::shared_ptr
<CGUIListItem
> pItem
= m_items
[item
];
921 if (pItem
->m_bIsFolder
)
922 strLabel
= StringUtils::Format("[{}]", pItem
->GetLabel());
924 strLabel
= pItem
->GetLabel();
929 void CGUIBaseContainer::SetFocus(bool bOnOff
)
931 if (bOnOff
!= HasFocus())
936 CGUIControl::SetFocus(bOnOff
);
939 void CGUIBaseContainer::SaveStates(std::vector
<CControlState
> &states
)
941 if (!m_listProvider
|| !m_listProvider
->AlwaysFocusDefaultItem())
942 states
.emplace_back(GetID(), GetSelectedItem());
945 void CGUIBaseContainer::SetPageControl(int id
)
950 bool CGUIBaseContainer::GetOffsetRange(int &minOffset
, int &maxOffset
) const
953 maxOffset
= GetRows() - m_itemsPerPage
;
957 void CGUIBaseContainer::ValidateOffset()
961 void CGUIBaseContainer::AllocResources()
963 CGUIControl::AllocResources();
967 UpdateListProvider(true);
971 void CGUIBaseContainer::FreeResources(bool immediately
)
973 CGUIControl::FreeResources(immediately
);
979 m_listProvider
->Reset();
985 void CGUIBaseContainer::UpdateLayout(bool updateAllItems
)
988 { // free memory of items
989 for (iItems it
= m_items
.begin(); it
!= m_items
.end(); ++it
)
992 // and recalculate the layout
994 SetPageControlRange();
998 void CGUIBaseContainer::SetPageControlRange()
1002 CGUIMessage
msg(GUI_MSG_LABEL_RESET
, GetID(), m_pageControl
, m_itemsPerPage
, GetRows());
1003 SendWindowMessage(msg
);
1007 void CGUIBaseContainer::UpdatePageControl(int offset
)
1010 { // tell our pagecontrol (scrollbar or whatever) to update (offset it by our cursor position)
1011 CGUIMessage
msg(GUI_MSG_ITEM_SELECT
, GetID(), m_pageControl
, offset
);
1012 SendWindowMessage(msg
);
1016 void CGUIBaseContainer::UpdateVisibility(const CGUIListItem
*item
)
1018 CGUIControl::UpdateVisibility(item
);
1020 if (!IsVisible() && !CGUIControl::CanFocus())
1021 return; // no need to update the content if we're not visible and we can't focus
1023 // update layouts in case of condition changed
1024 if ((m_layout
&& m_layout
->CheckCondition() != m_layoutCondition
) ||
1025 (m_focusedLayout
&& m_focusedLayout
->CheckCondition() != m_focusedLayoutCondition
))
1028 m_layoutCondition
= m_layout
->CheckCondition();
1029 if (m_focusedLayout
)
1030 m_focusedLayoutCondition
= m_focusedLayout
->CheckCondition();
1032 int itemIndex
= GetSelectedItem();
1033 UpdateLayout(true); // true to refresh all items
1034 SelectItem(itemIndex
);
1037 UpdateListProvider();
1040 void CGUIBaseContainer::UpdateListProvider(bool forceRefresh
/* = false */)
1044 if (m_listProvider
->Update(forceRefresh
))
1046 // save the current item
1047 int currentItem
= GetSelectedItem();
1048 CGUIListItem
*current
= (currentItem
>= 0 && currentItem
< (int)m_items
.size()) ? m_items
[currentItem
].get() : NULL
;
1049 const std::string
prevSelectedPath((current
&& current
->IsFileItem()) ? static_cast<CFileItem
*>(current
)->GetPath() : "");
1052 m_listProvider
->Fetch(m_items
);
1053 SetPageControlRange();
1054 // update the newly selected item
1057 // first, try to re-identify selected item by comparing item pointers, though it is not guaranteed that item instances got not recreated on update.
1058 for (int i
= 0; i
< (int)m_items
.size(); i
++)
1060 if (m_items
[i
].get() == current
)
1063 if (i
!= currentItem
)
1070 if (!found
&& !prevSelectedPath
.empty())
1072 // as fallback, try to re-identify selected item by comparing item paths.
1073 for (int i
= 0; i
< static_cast<int>(m_items
.size()); i
++)
1075 const std::shared_ptr
<CGUIListItem
> c(m_items
[i
]);
1076 if (c
->IsFileItem())
1078 const std::string
&selectedPath
= static_cast<CFileItem
*>(c
.get())->GetPath();
1079 if (selectedPath
== prevSelectedPath
)
1082 if (i
!= currentItem
)
1091 if (!found
&& currentItem
>= (int)m_items
.size())
1092 SelectItem(m_items
.size()-1);
1095 // always update the scroll by letter, as the list provider may have altered labels
1096 // while not actually changing the list items.
1097 UpdateScrollByLetter();
1101 void CGUIBaseContainer::CalculateLayout()
1103 CGUIListItemLayout
*oldFocusedLayout
= m_focusedLayout
;
1104 CGUIListItemLayout
*oldLayout
= m_layout
;
1105 GetCurrentLayouts();
1107 // calculate the number of items to display
1108 if (!m_focusedLayout
|| !m_layout
)
1111 if (oldLayout
== m_layout
&& oldFocusedLayout
== m_focusedLayout
)
1112 return; // nothing has changed, so don't update stuff
1114 m_itemsPerPage
= std::max((int)((Size() - m_focusedLayout
->Size(m_orientation
)) / m_layout
->Size(m_orientation
)) + 1, 1);
1116 // ensure that the scroll offset is a multiple of our size
1117 m_scroller
.SetValue(GetOffset() * m_layout
->Size(m_orientation
));
1120 void CGUIBaseContainer::UpdateScrollByLetter()
1122 m_letterOffsets
.clear();
1124 // for scrolling by letter we have an offset table into our vector.
1125 std::string currentMatch
;
1126 for (unsigned int i
= 0; i
< m_items
.size(); i
++)
1128 std::shared_ptr
<CGUIListItem
> item
= m_items
[i
];
1129 // The letter offset jumping is only for ASCII characters at present, and
1130 // our checks are all done in uppercase
1131 std::string nextLetter
;
1132 std::wstring character
= item
->GetSortLabel().substr(0, 1);
1133 StringUtils::ToUpper(character
);
1134 g_charsetConverter
.wToUTF8(character
, nextLetter
);
1135 if (currentMatch
!= nextLetter
)
1137 currentMatch
= nextLetter
;
1138 m_letterOffsets
.emplace_back(static_cast<int>(i
), currentMatch
);
1143 unsigned int CGUIBaseContainer::GetRows() const
1145 return m_items
.size();
1148 inline float CGUIBaseContainer::Size() const
1150 return (m_orientation
== HORIZONTAL
) ? m_width
: m_height
;
1153 int CGUIBaseContainer::ScrollCorrectionRange() const
1155 int range
= m_itemsPerPage
/ 4;
1156 if (range
<= 0) range
= 1;
1160 void CGUIBaseContainer::ScrollToOffset(int offset
)
1162 int minOffset
, maxOffset
;
1163 if(GetOffsetRange(minOffset
, maxOffset
))
1164 offset
= std::max(minOffset
, std::min(offset
, maxOffset
));
1165 float size
= (m_layout
) ? m_layout
->Size(m_orientation
) : 10.0f
;
1166 int range
= ScrollCorrectionRange();
1167 if (offset
* size
< m_scroller
.GetValue() && m_scroller
.GetValue() - offset
* size
> size
* range
)
1168 { // scrolling up, and we're jumping more than 0.5 of a screen
1169 m_scroller
.SetValue((offset
+ range
) * size
);
1171 if (offset
* size
> m_scroller
.GetValue() && offset
* size
- m_scroller
.GetValue() > size
* range
)
1172 { // scrolling down, and we're jumping more than 0.5 of a screen
1173 m_scroller
.SetValue((offset
- range
) * size
);
1175 m_scroller
.ScrollTo(offset
* size
);
1176 m_lastScrollStartTimer
.StartZero();
1179 SetContainerMoving(offset
- GetOffset());
1180 if (m_scroller
.IsScrolling())
1181 m_scrollTimer
.Start();
1183 m_scrollTimer
.Stop();
1187 m_scrollTimer
.Stop();
1188 m_scroller
.Update(~0U);
1193 void CGUIBaseContainer::SetAutoScrolling(const TiXmlNode
*node
)
1196 const TiXmlElement
*scroll
= node
->FirstChildElement("autoscroll");
1199 scroll
->Attribute("time", &m_autoScrollMoveTime
);
1200 if (scroll
->Attribute("reverse"))
1201 m_autoScrollIsReversed
= true;
1202 if (scroll
->FirstChild())
1203 m_autoScrollCondition
= CServiceBroker::GetGUI()->GetInfoManager().Register(scroll
->FirstChild()->ValueStr(), GetParentID());
1207 void CGUIBaseContainer::ResetAutoScrolling()
1209 m_autoScrollDelayTime
= 0;
1212 void CGUIBaseContainer::UpdateAutoScrolling(unsigned int currentTime
)
1214 if (m_autoScrollCondition
&& m_autoScrollCondition
->Get(INFO::DEFAULT_CONTEXT
))
1216 if (m_lastRenderTime
)
1217 m_autoScrollDelayTime
+= currentTime
- m_lastRenderTime
;
1218 if (m_autoScrollDelayTime
> (unsigned int)m_autoScrollMoveTime
&& !m_scroller
.IsScrolling())
1219 { // delay is finished - start moving
1220 m_autoScrollDelayTime
= 0;
1221 // Move up or down whether reversed moving is true or false
1222 m_autoScrollIsReversed
? MoveUp(true) : MoveDown(true);
1226 ResetAutoScrolling();
1229 void CGUIBaseContainer::SetContainerMoving(int direction
)
1232 CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetGUIControlsInfoProvider().SetContainerMoving(GetID(), direction
> 0, m_scroller
.IsScrolling());
1235 void CGUIBaseContainer::UpdateScrollOffset(unsigned int currentTime
)
1237 if (m_scroller
.Update(currentTime
))
1239 else if (m_lastScrollStartTimer
.IsRunning() && m_lastScrollStartTimer
.GetElapsedMilliseconds() >= SCROLLING_GAP
)
1241 m_scrollTimer
.Stop();
1242 m_lastScrollStartTimer
.Stop();
1243 SetCursor(GetCursor());
1247 int CGUIBaseContainer::CorrectOffset(int offset
, int cursor
) const
1249 return offset
+ cursor
;
1252 void CGUIBaseContainer::Reset()
1257 ResetAutoScrolling();
1260 void CGUIBaseContainer::LoadLayout(TiXmlElement
*layout
)
1262 TiXmlElement
*itemElement
= layout
->FirstChildElement("itemlayout");
1264 { // we have a new item layout
1265 m_layouts
.emplace_back();
1266 m_layouts
.back().LoadLayout(itemElement
, GetParentID(), false, m_width
, m_height
);
1267 itemElement
= itemElement
->NextSiblingElement("itemlayout");
1268 m_layouts
.back().SetParentControl(this);
1270 itemElement
= layout
->FirstChildElement("focusedlayout");
1272 { // we have a new item layout
1273 m_focusedLayouts
.emplace_back();
1274 m_focusedLayouts
.back().LoadLayout(itemElement
, GetParentID(), true, m_width
, m_height
);
1275 itemElement
= itemElement
->NextSiblingElement("focusedlayout");
1276 m_focusedLayouts
.back().SetParentControl(this);
1280 void CGUIBaseContainer::LoadListProvider(TiXmlElement
*content
, int defaultItem
, bool defaultAlways
)
1282 m_listProvider
= IListProvider::Create(content
, GetParentID());
1284 m_listProvider
->SetDefaultItem(defaultItem
, defaultAlways
);
1287 void CGUIBaseContainer::SetListProvider(std::unique_ptr
<IListProvider
> provider
)
1289 m_listProvider
= std::move(provider
);
1290 UpdateListProvider(true);
1293 void CGUIBaseContainer::SetRenderOffset(const CPoint
&offset
)
1295 m_renderOffset
= offset
;
1298 void CGUIBaseContainer::FreeMemory(int keepStart
, int keepEnd
)
1300 if (keepStart
< keepEnd
)
1301 { // remove before keepStart and after keepEnd
1302 for (int i
= 0; i
< keepStart
&& i
< (int)m_items
.size(); ++i
)
1303 m_items
[i
]->FreeMemory();
1304 for (int i
= std::max(keepEnd
+ 1, 0); i
< (int)m_items
.size(); ++i
)
1305 m_items
[i
]->FreeMemory();
1309 for (int i
= std::max(keepEnd
+ 1, 0); i
< keepStart
&& i
< (int)m_items
.size(); ++i
)
1310 m_items
[i
]->FreeMemory();
1314 bool CGUIBaseContainer::InsideLayout(const CGUIListItemLayout
*layout
, const CPoint
&point
) const
1316 if (!layout
) return false;
1317 if ((m_orientation
== VERTICAL
&& (layout
->Size(HORIZONTAL
) > 1) && point
.x
> layout
->Size(HORIZONTAL
)) ||
1318 (m_orientation
== HORIZONTAL
&& (layout
->Size(VERTICAL
) > 1)&& point
.y
> layout
->Size(VERTICAL
)))
1324 void CGUIBaseContainer::DumpTextureUse()
1326 CLog::Log(LOGDEBUG
, "{} for container {}", __FUNCTION__
, GetID());
1327 for (unsigned int i
= 0; i
< m_items
.size(); ++i
)
1329 std::shared_ptr
<CGUIListItem
> item
= m_items
[i
];
1330 if (item
->GetFocusedLayout()) item
->GetFocusedLayout()->DumpTextureUse();
1331 if (item
->GetLayout()) item
->GetLayout()->DumpTextureUse();
1336 bool CGUIBaseContainer::GetCondition(int condition
, int data
) const
1341 return (m_orientation
== VERTICAL
) ? (GetCursor() == data
) : true;
1342 case CONTAINER_COLUMN
:
1343 return (m_orientation
== HORIZONTAL
) ? (GetCursor() == data
) : true;
1344 case CONTAINER_POSITION
:
1345 return (GetCursor() == data
);
1346 case CONTAINER_HAS_NEXT
:
1347 return (HasNextPage());
1348 case CONTAINER_HAS_PREVIOUS
:
1349 return (HasPreviousPage());
1350 case CONTAINER_HAS_PARENT_ITEM
:
1351 return (m_items
.size() && m_items
[0]->IsFileItem() && (std::static_pointer_cast
<CFileItem
>(m_items
[0]))->IsParentFolder());
1352 case CONTAINER_SUBITEM
:
1354 CGUIListItemLayout
*layout
= GetFocusedLayout();
1355 return layout
? (layout
->GetFocusedItem() == (unsigned int)data
) : false;
1357 case CONTAINER_SCROLLING
:
1358 return ((m_scrollTimer
.IsRunning() && m_scrollTimer
.GetElapsedMilliseconds() > std::max(m_scroller
.GetDuration(), SCROLLING_THRESHOLD
)) || m_pageChangeTimer
.IsRunning());
1359 case CONTAINER_ISUPDATING
:
1360 return (m_listProvider
) ? m_listProvider
->IsUpdating() : false;
1366 void CGUIBaseContainer::GetCurrentLayouts()
1369 for (auto &layout
: m_layouts
)
1371 if (layout
.CheckCondition())
1377 if (!m_layout
&& !m_layouts
.empty())
1378 m_layout
= &m_layouts
.front(); // failsafe
1380 m_focusedLayout
= NULL
;
1381 for (auto &layout
: m_focusedLayouts
)
1383 if (layout
.CheckCondition())
1385 m_focusedLayout
= &layout
;
1389 if (!m_focusedLayout
&& !m_focusedLayouts
.empty())
1390 m_focusedLayout
= &m_focusedLayouts
.front(); // failsafe
1393 bool CGUIBaseContainer::HasNextPage() const
1398 bool CGUIBaseContainer::HasPreviousPage() const
1403 std::string
CGUIBaseContainer::GetLabel(int info
) const
1408 case CONTAINER_NUM_PAGES
:
1409 label
= std::to_string((GetRows() + m_itemsPerPage
- 1) / m_itemsPerPage
);
1411 case CONTAINER_CURRENT_PAGE
:
1412 label
= std::to_string(GetCurrentPage());
1414 case CONTAINER_POSITION
:
1415 label
= std::to_string(GetCursor());
1417 case CONTAINER_CURRENT_ITEM
:
1419 if (m_items
.size() && m_items
[0]->IsFileItem() && (std::static_pointer_cast
<CFileItem
>(m_items
[0]))->IsParentFolder())
1420 label
= std::to_string(GetSelectedItem());
1422 label
= std::to_string(GetSelectedItem() + 1);
1425 case CONTAINER_NUM_ALL_ITEMS
:
1426 case CONTAINER_NUM_ITEMS
:
1428 unsigned int numItems
= GetNumItems();
1429 if (info
== CONTAINER_NUM_ITEMS
&& numItems
&& m_items
[0]->IsFileItem() && (std::static_pointer_cast
<CFileItem
>(m_items
[0]))->IsParentFolder())
1430 label
= std::to_string(numItems
- 1);
1432 label
= std::to_string(numItems
);
1435 case CONTAINER_NUM_NONFOLDER_ITEMS
:
1438 for (const auto& item
: m_items
)
1440 if (!item
->m_bIsFolder
)
1443 label
= std::to_string(numItems
);
1452 int CGUIBaseContainer::GetCurrentPage() const
1454 if (GetOffset() + m_itemsPerPage
>= (int)GetRows()) // last page
1455 return (GetRows() + m_itemsPerPage
- 1) / m_itemsPerPage
;
1456 return GetOffset() / m_itemsPerPage
+ 1;
1459 void CGUIBaseContainer::GetCacheOffsets(int &cacheBefore
, int &cacheAfter
) const
1461 if (m_scroller
.IsScrollingDown())
1464 cacheAfter
= m_cacheItems
;
1466 else if (m_scroller
.IsScrollingUp())
1468 cacheBefore
= m_cacheItems
;
1473 cacheBefore
= m_cacheItems
/ 2;
1474 cacheAfter
= m_cacheItems
/ 2;
1478 void CGUIBaseContainer::SetCursor(int cursor
)
1480 if (m_cursor
!= cursor
)
1485 void CGUIBaseContainer::SetOffset(int offset
)
1487 if (m_offset
!= offset
)
1492 bool CGUIBaseContainer::CanFocus() const
1494 if (CGUIControl::CanFocus())
1497 We allow focus if we have items available or if we have a list provider
1498 that's in the process of updating.
1500 return !m_items
.empty() || (m_listProvider
&& m_listProvider
->IsUpdating());
1505 void CGUIBaseContainer::OnFocus()
1507 if (m_listProvider
&& m_listProvider
->AlwaysFocusDefaultItem())
1508 SelectItem(m_listProvider
->GetDefaultItem());
1510 if (m_focusActions
.HasAnyActions())
1511 m_focusActions
.ExecuteActions(GetID(), GetParentID());
1513 CGUIControl::OnFocus();
1516 void CGUIBaseContainer::OnUnFocus()
1518 if (m_unfocusActions
.HasAnyActions())
1519 m_unfocusActions
.ExecuteActions(GetID(), GetParentID());
1521 CGUIControl::OnUnFocus();