summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUIPanelContainer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/guilib/GUIPanelContainer.cpp567
1 files changed, 567 insertions, 0 deletions
diff --git a/xbmc/guilib/GUIPanelContainer.cpp b/xbmc/guilib/GUIPanelContainer.cpp
new file mode 100644
index 0000000..0195d57
--- /dev/null
+++ b/xbmc/guilib/GUIPanelContainer.cpp
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIPanelContainer.h"
+
+#include "FileItem.h"
+#include "GUIListItemLayout.h"
+#include "GUIMessage.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+#include "utils/StringUtils.h"
+
+#include <cassert>
+
+CGUIPanelContainer::CGUIPanelContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems)
+ : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
+{
+ ControlType = GUICONTAINER_PANEL;
+ m_type = VIEW_TYPE_ICON;
+ m_itemsPerRow = 1;
+}
+
+CGUIPanelContainer::~CGUIPanelContainer(void) = default;
+
+void CGUIPanelContainer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ ValidateOffset();
+
+ if (m_bInvalidated)
+ UpdateLayout();
+
+ if (!m_layout || !m_focusedLayout)
+ return;
+
+ UpdateScrollOffset(currentTime);
+
+ int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ // Free memory not used on screen
+ if ((int)m_items.size() > m_itemsPerPage + cacheBefore + cacheAfter)
+ FreeMemory(CorrectOffset(offset - cacheBefore, 0), CorrectOffset(offset + m_itemsPerPage + 1 + cacheAfter, 0));
+
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+ pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ int current = (offset - cacheBefore) * m_itemsPerRow;
+ int col = 0;
+ while (pos < end && m_items.size())
+ {
+ if (current >= (int)m_items.size())
+ break;
+ if (current >= 0)
+ {
+ CGUIListItemPtr item = m_items[current];
+ item->SetCurrentItem(current + 1);
+ bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
+
+ if (m_orientation == VERTICAL)
+ ProcessItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item, focused, currentTime, dirtyregions);
+ else
+ ProcessItem(pos, origin.y + col * m_layout->Size(VERTICAL), item, focused, currentTime, dirtyregions);
+ }
+ // increment our position
+ if (col < m_itemsPerRow - 1)
+ col++;
+ else
+ {
+ pos += m_layout->Size(m_orientation);
+ col = 0;
+ }
+ current++;
+ }
+
+ // when we are scrolling up, offset will become lower (integer division, see offset calc)
+ // to have same behaviour when scrolling down, we need to set page control to offset+1
+ UpdatePageControl(offset + (m_scroller.IsScrollingDown() ? 1 : 0));
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+
+void CGUIPanelContainer::Render()
+{
+ if (!m_layout || !m_focusedLayout)
+ return;
+
+ int offset = (int)(m_scroller.GetValue() / m_layout->Size(m_orientation));
+
+ int cacheBefore, cacheAfter;
+ GetCacheOffsets(cacheBefore, cacheAfter);
+
+ if (CServiceBroker::GetWinSystem()->GetGfxContext().SetClipRegion(m_posX, m_posY, m_width, m_height))
+ {
+ CPoint origin = CPoint(m_posX, m_posY) + m_renderOffset;
+ float pos = (m_orientation == VERTICAL) ? origin.y : origin.x;
+ float end = (m_orientation == VERTICAL) ? m_posY + m_height : m_posX + m_width;
+ pos += (offset - cacheBefore) * m_layout->Size(m_orientation) - m_scroller.GetValue();
+ end += cacheAfter * m_layout->Size(m_orientation);
+
+ float focusedPos = 0;
+ int focusedCol = 0;
+ CGUIListItemPtr focusedItem;
+ int current = (offset - cacheBefore) * m_itemsPerRow;
+ int col = 0;
+ while (pos < end && m_items.size())
+ {
+ if (current >= (int)m_items.size())
+ break;
+ if (current >= 0)
+ {
+ CGUIListItemPtr item = m_items[current];
+ bool focused = (current == GetOffset() * m_itemsPerRow + GetCursor()) && m_bHasFocus;
+ // render our item
+ if (focused)
+ {
+ focusedPos = pos;
+ focusedCol = col;
+ focusedItem = item;
+ }
+ else
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x + col * m_layout->Size(HORIZONTAL), pos, item.get(), false);
+ else
+ RenderItem(pos, origin.y + col * m_layout->Size(VERTICAL), item.get(), false);
+ }
+ }
+ // increment our position
+ if (col < m_itemsPerRow - 1)
+ col++;
+ else
+ {
+ pos += m_layout->Size(m_orientation);
+ col = 0;
+ }
+ current++;
+ }
+ // and render the focused item last (for overlapping purposes)
+ if (focusedItem)
+ {
+ if (m_orientation == VERTICAL)
+ RenderItem(origin.x + focusedCol * m_layout->Size(HORIZONTAL), focusedPos, focusedItem.get(), true);
+ else
+ RenderItem(focusedPos, origin.y + focusedCol * m_layout->Size(VERTICAL), focusedItem.get(), true);
+ }
+
+ CServiceBroker::GetWinSystem()->GetGfxContext().RestoreClipRegion();
+ }
+ CGUIControl::Render();
+}
+
+bool CGUIPanelContainer::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_PAGE_UP:
+ {
+ if (GetOffset() == 0)
+ { // already on the first page, so move to the first item
+ SetCursor(0);
+ }
+ else
+ { // scroll up to the previous page
+ Scroll( -m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ case ACTION_PAGE_DOWN:
+ {
+ if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow >= (int)m_items.size() || (int)m_items.size() < m_itemsPerPage)
+ { // already at the last page, so move to the last item.
+ SetCursor(m_items.size() - GetOffset() * m_itemsPerRow - 1);
+ }
+ else
+ { // scroll down to the next page
+ Scroll(m_itemsPerPage);
+ }
+ return true;
+ }
+ break;
+ // smooth scrolling (for analog controls)
+ case ACTION_SCROLL_UP:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > AnalogScrollSpeed())
+ {
+ handled = true;
+ m_analogScrollCount -= AnalogScrollSpeed();
+ if (GetOffset() > 0)// && GetCursor() <= m_itemsPerPage * m_itemsPerRow / 2)
+ {
+ Scroll(-1);
+ }
+ else if (GetCursor() > 0)
+ {
+ SetCursor(GetCursor() - 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ case ACTION_SCROLL_DOWN:
+ {
+ m_analogScrollCount += action.GetAmount() * action.GetAmount();
+ bool handled = false;
+ while (m_analogScrollCount > AnalogScrollSpeed())
+ {
+ handled = true;
+ m_analogScrollCount -= AnalogScrollSpeed();
+ if ((GetOffset() + m_itemsPerPage) * m_itemsPerRow < (int)m_items.size())// && GetCursor() >= m_itemsPerPage * m_itemsPerRow / 2)
+ {
+ Scroll(1);
+ }
+ else if (GetCursor() < m_itemsPerPage * m_itemsPerRow - 1 && GetOffset() * m_itemsPerRow + GetCursor() < (int)m_items.size() - 1)
+ {
+ SetCursor(GetCursor() + 1);
+ }
+ }
+ return handled;
+ }
+ break;
+ }
+ return CGUIBaseContainer::OnAction(action);
+}
+
+bool CGUIPanelContainer::OnMessage(CGUIMessage& message)
+{
+ if (message.GetControlId() == GetID() )
+ {
+ if (message.GetMessage() == GUI_MSG_LABEL_RESET)
+ {
+ SetCursor(0);
+ // fall through to base class
+ }
+ }
+ return CGUIBaseContainer::OnMessage(message);
+}
+
+void CGUIPanelContainer::OnLeft()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_LEFT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveLeft(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveUp(wrapAround))
+ return;
+ CGUIControl::OnLeft();
+}
+
+void CGUIPanelContainer::OnRight()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_RIGHT);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveRight(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveDown(wrapAround))
+ return;
+ return CGUIControl::OnRight();
+}
+
+void CGUIPanelContainer::OnUp()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_UP);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveUp(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveLeft(wrapAround))
+ return;
+ CGUIControl::OnUp();
+}
+
+void CGUIPanelContainer::OnDown()
+{
+ CGUIAction action = GetAction(ACTION_MOVE_DOWN);
+ bool wrapAround = action.GetNavigation() == GetID() || !action.HasActionsMeetingCondition();
+ if (m_orientation == VERTICAL && MoveDown(wrapAround))
+ return;
+ if (m_orientation == HORIZONTAL && MoveRight(wrapAround))
+ return;
+ return CGUIControl::OnDown();
+}
+
+bool CGUIPanelContainer::MoveDown(bool wrapAround)
+{
+ if (GetCursor() + m_itemsPerRow < m_itemsPerPage * m_itemsPerRow && (GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
+ { // move to last item if necessary
+ if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - 1 - GetOffset()*m_itemsPerRow);
+ else
+ SetCursor(GetCursor() + m_itemsPerRow);
+ }
+ else if ((GetOffset() + 1 + GetCursor() / m_itemsPerRow) * m_itemsPerRow < (int)m_items.size())
+ { // we scroll to the next row, and move to last item if necessary
+ if ((GetOffset() + 1)*m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - 1 - (GetOffset() + 1)*m_itemsPerRow);
+ ScrollToOffset(GetOffset() + 1);
+ }
+ else if (wrapAround)
+ { // move first item in list
+ SetCursor(GetCursor() % m_itemsPerRow);
+ ScrollToOffset(0);
+ SetContainerMoving(1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveUp(bool wrapAround)
+{
+ if (GetCursor() >= m_itemsPerRow)
+ SetCursor(GetCursor() - m_itemsPerRow);
+ else if (GetOffset() > 0)
+ ScrollToOffset(GetOffset() - 1);
+ else if (wrapAround)
+ { // move last item in list in this column
+ SetCursor((GetCursor() % m_itemsPerRow) + (m_itemsPerPage - 1) * m_itemsPerRow);
+ int offset = std::max((int)GetRows() - m_itemsPerPage, 0);
+ // should check here whether cursor is actually allowed here, and reduce accordingly
+ if (offset * m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - offset * m_itemsPerRow - 1);
+ ScrollToOffset(offset);
+ SetContainerMoving(-1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveLeft(bool wrapAround)
+{
+ int col = GetCursor() % m_itemsPerRow;
+ if (col > 0)
+ SetCursor(GetCursor() - 1);
+ else if (wrapAround)
+ { // wrap around
+ SetCursor(GetCursor() + m_itemsPerRow - 1);
+ if (GetOffset() * m_itemsPerRow + GetCursor() >= (int)m_items.size())
+ SetCursor((int)m_items.size() - GetOffset() * m_itemsPerRow - 1);
+ }
+ else
+ return false;
+ return true;
+}
+
+bool CGUIPanelContainer::MoveRight(bool wrapAround)
+{
+ int col = GetCursor() % m_itemsPerRow;
+ if (col + 1 < m_itemsPerRow && GetOffset() * m_itemsPerRow + GetCursor() + 1 < (int)m_items.size())
+ SetCursor(GetCursor() + 1);
+ else if (wrapAround) // move first item in row
+ SetCursor(GetCursor() - col);
+ else
+ return false;
+ return true;
+}
+
+// scrolls the said amount
+void CGUIPanelContainer::Scroll(int amount)
+{
+ // increase or decrease the offset
+ int offset = GetOffset() + amount;
+ if (offset > ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow)
+ {
+ offset = ((int)GetRows() - m_itemsPerPage) * m_itemsPerRow;
+ }
+ if (offset < 0) offset = 0;
+ ScrollToOffset(offset);
+}
+
+void CGUIPanelContainer::ValidateOffset()
+{
+ if (!m_layout) return;
+ // first thing is we check the range of our offset
+ // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
+ if (GetOffset() > (int)GetRows() - m_itemsPerPage || (!m_scroller.IsScrolling() && m_scroller.GetValue() > ((int)GetRows() - m_itemsPerPage) * m_layout->Size(m_orientation)))
+ {
+ SetOffset(std::max(0, (int)GetRows() - m_itemsPerPage));
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+ }
+ if (GetOffset() < 0 || (!m_scroller.IsScrolling() && m_scroller.GetValue() < 0))
+ {
+ SetOffset(0);
+ m_scroller.SetValue(0);
+ }
+}
+
+void CGUIPanelContainer::SetCursor(int cursor)
+{
+ if (cursor > m_itemsPerPage * m_itemsPerRow - 1)
+ cursor = m_itemsPerPage * m_itemsPerRow - 1;
+ if (cursor < 0) cursor = 0;
+ if (!m_wasReset)
+ SetContainerMoving(cursor - GetCursor());
+ CGUIBaseContainer::SetCursor(cursor);
+}
+
+void CGUIPanelContainer::CalculateLayout()
+{
+ GetCurrentLayouts();
+
+ if (!m_layout || !m_focusedLayout) return;
+ // calculate the number of items to display
+ if (m_orientation == HORIZONTAL)
+ {
+ m_itemsPerRow = (int)(m_height / m_layout->Size(VERTICAL));
+ m_itemsPerPage = (int)(m_width / m_layout->Size(HORIZONTAL));
+ }
+ else
+ {
+ m_itemsPerRow = (int)(m_width / m_layout->Size(HORIZONTAL));
+ m_itemsPerPage = (int)(m_height / m_layout->Size(VERTICAL));
+ }
+ if (m_itemsPerRow < 1) m_itemsPerRow = 1;
+ if (m_itemsPerPage < 1) m_itemsPerPage = 1;
+
+ // ensure that the scroll offset is a multiple of our size
+ m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
+}
+
+unsigned int CGUIPanelContainer::GetRows() const
+{
+ assert(m_itemsPerRow > 0);
+ return (m_items.size() + m_itemsPerRow - 1) / m_itemsPerRow;
+}
+
+float CGUIPanelContainer::AnalogScrollSpeed() const
+{
+ return 10.0f / m_itemsPerPage;
+}
+
+int CGUIPanelContainer::CorrectOffset(int offset, int cursor) const
+{
+ return offset * m_itemsPerRow + cursor;
+}
+
+int CGUIPanelContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
+{
+ if (!m_layout)
+ return -1;
+
+ float sizeX = m_orientation == VERTICAL ? m_layout->Size(HORIZONTAL) : m_layout->Size(VERTICAL);
+ float sizeY = m_orientation == VERTICAL ? m_layout->Size(VERTICAL) : m_layout->Size(HORIZONTAL);
+
+ float posY = m_orientation == VERTICAL ? point.y : point.x;
+ for (int y = 0; y < m_itemsPerPage + 1; y++) // +1 to ensure if we have a half item we can select it
+ {
+ float posX = m_orientation == VERTICAL ? point.x : point.y;
+ for (int x = 0; x < m_itemsPerRow; x++)
+ {
+ int item = x + y * m_itemsPerRow;
+ if (posX < sizeX && posY < sizeY && item + GetOffset() < (int)m_items.size())
+ { // found
+ return item;
+ }
+ posX -= sizeX;
+ }
+ posY -= sizeY;
+ }
+ return -1;
+}
+
+bool CGUIPanelContainer::SelectItemFromPoint(const CPoint &point)
+{
+ int cursor = GetCursorFromPoint(point);
+ if (cursor < 0)
+ return false;
+ SetCursor(cursor);
+ return true;
+}
+
+int CGUIPanelContainer::GetCurrentRow() const
+{
+ return m_itemsPerRow > 0 ? GetCursor() / m_itemsPerRow : 0;
+}
+
+int CGUIPanelContainer::GetCurrentColumn() const
+{
+ return GetCursor() % m_itemsPerRow;
+}
+
+bool CGUIPanelContainer::GetCondition(int condition, int data) const
+{
+ int row = GetCurrentRow();
+ int col = GetCurrentColumn();
+
+ if (m_orientation == HORIZONTAL)
+ std::swap(row, col);
+
+ switch (condition)
+ {
+ case CONTAINER_ROW:
+ return (row == data);
+ case CONTAINER_COLUMN:
+ return (col == data);
+ default:
+ return CGUIBaseContainer::GetCondition(condition, data);
+ }
+}
+
+std::string CGUIPanelContainer::GetLabel(int info) const
+{
+ int row = GetCurrentRow();
+ int col = GetCurrentColumn();
+
+ if (m_orientation == HORIZONTAL)
+ std::swap(row, col);
+
+ switch (info)
+ {
+ case CONTAINER_ROW:
+ return std::to_string(row);
+ case CONTAINER_COLUMN:
+ return std::to_string(col);
+ default:
+ return CGUIBaseContainer::GetLabel(info);
+ }
+ return StringUtils::Empty;
+}
+
+void CGUIPanelContainer::SelectItem(int item)
+{
+ // Check that our offset is valid
+ ValidateOffset();
+ // only select an item if it's in a valid range
+ if (item >= 0 && item < (int)m_items.size())
+ {
+ // Select the item requested
+ if (item >= GetOffset() * m_itemsPerRow && item < (GetOffset() + m_itemsPerPage) * m_itemsPerRow)
+ { // the item is on the current page, so don't change it.
+ SetCursor(item - GetOffset() * m_itemsPerRow);
+ }
+ else if (item < GetOffset() * m_itemsPerRow)
+ { // item is on a previous page - make it the first item on the page
+ SetCursor(item % m_itemsPerRow);
+ ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
+ }
+ else // (item >= GetOffset()+m_itemsPerPage)
+ { // item is on a later page - make it the last row on the page
+ SetCursor(item % m_itemsPerRow + m_itemsPerRow * (m_itemsPerPage - 1));
+ ScrollToOffset((item - GetCursor()) / m_itemsPerRow);
+ }
+ }
+}
+
+bool CGUIPanelContainer::HasPreviousPage() const
+{
+ return (GetOffset() > 0);
+}
+
+bool CGUIPanelContainer::HasNextPage() const
+{
+ return (GetOffset() != (int)GetRows() - m_itemsPerPage && (int)GetRows() > m_itemsPerPage);
+}
+