/* * 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 "GUIFixedListContainer.h" #include "GUIListItemLayout.h" #include "input/Key.h" CGUIFixedListContainer::CGUIFixedListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition, int cursorRange) : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems) { ControlType = GUICONTAINER_FIXEDLIST; m_type = VIEW_TYPE_LIST; m_fixedCursor = fixedPosition; m_cursorRange = std::max(0, cursorRange); SetCursor(m_fixedCursor); } CGUIFixedListContainer::~CGUIFixedListContainer(void) = default; bool CGUIFixedListContainer::OnAction(const CAction &action) { switch (action.GetID()) { case ACTION_PAGE_UP: { Scroll(-m_itemsPerPage); return true; } break; case ACTION_PAGE_DOWN: { 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 > 0.4f) { handled = true; m_analogScrollCount -= 0.4f; Scroll(-1); } return handled; } break; case ACTION_SCROLL_DOWN: { m_analogScrollCount += action.GetAmount() * action.GetAmount(); bool handled = false; while (m_analogScrollCount > 0.4f) { handled = true; m_analogScrollCount -= 0.4f; Scroll(1); } return handled; } break; } return CGUIBaseContainer::OnAction(action); } bool CGUIFixedListContainer::MoveUp(bool wrapAround) { int item = GetSelectedItem(); if (item > 0) SelectItem(item - 1); else if (wrapAround) { SelectItem((int)m_items.size() - 1); SetContainerMoving(-1); } else return false; return true; } bool CGUIFixedListContainer::MoveDown(bool wrapAround) { int item = GetSelectedItem(); if (item < (int)m_items.size() - 1) SelectItem(item + 1); else if (wrapAround) { // move first item in list SelectItem(0); SetContainerMoving(1); } else return false; return true; } void CGUIFixedListContainer::Scroll(int amount) { // increase or decrease the offset within [-minCursor, m_items.size() - maxCursor] int minCursor, maxCursor; GetCursorRange(minCursor, maxCursor); const int nextCursor = GetCursor() + amount; int offset = GetOffset() + amount; if (offset < -minCursor) { offset = -minCursor; SetCursor(nextCursor < minCursor ? minCursor : nextCursor); } if (offset > (int)m_items.size() - 1 - maxCursor) { offset = m_items.size() - 1 - maxCursor; SetCursor(nextCursor > maxCursor ? maxCursor : nextCursor); } ScrollToOffset(offset); } bool CGUIFixedListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const { GetCursorRange(minOffset, maxOffset); minOffset = -minOffset; maxOffset = m_items.size() - maxOffset - 1; return true; } void CGUIFixedListContainer::ValidateOffset() { if (!m_layout) return; // ensure our fixed cursor position is valid if (m_fixedCursor >= m_itemsPerPage) m_fixedCursor = m_itemsPerPage - 1; if (m_fixedCursor < 0) m_fixedCursor = 0; // compute our minimum and maximum cursor positions int minCursor, maxCursor; GetCursorRange(minCursor, maxCursor); // assure our cursor is between these limits SetCursor(std::max(GetCursor(), minCursor)); SetCursor(std::min(GetCursor(), maxCursor)); int minOffset, maxOffset; GetOffsetRange(minOffset, maxOffset); // and finally ensure our offset is valid // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation))) { SetOffset(std::max(-minCursor, maxOffset)); m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation)); } if (GetOffset() < minOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() < minOffset * m_layout->Size(m_orientation))) { SetOffset(minOffset); m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation)); } } int CGUIFixedListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const { if (!m_focusedLayout || !m_layout) return -1; int minCursor, maxCursor; GetCursorRange(minCursor, maxCursor); // see if the point is either side of our focus range float start = (minCursor + 0.2f) * m_layout->Size(m_orientation); float end = (maxCursor - 0.2f) * m_layout->Size(m_orientation) + m_focusedLayout->Size(m_orientation); float pos = (m_orientation == VERTICAL) ? point.y : point.x; if (pos >= start && pos <= end) { // select the appropriate item pos -= minCursor * m_layout->Size(m_orientation); for (int row = minCursor; row <= maxCursor; row++) { const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout; if (pos < layout->Size(m_orientation)) { if (!InsideLayout(layout, point)) return -1; return row; } pos -= layout->Size(m_orientation); } } return -1; } bool CGUIFixedListContainer::SelectItemFromPoint(const CPoint &point) { if (!m_focusedLayout || !m_layout) return false; MarkDirtyRegion(); const float mouse_scroll_speed = 0.25f; const float mouse_max_amount = 1.5f; float sizeOfItem = m_layout->Size(m_orientation); int minCursor, maxCursor; GetCursorRange(minCursor, maxCursor); // see if the point is either side of our focus range float start = (minCursor + 0.2f) * sizeOfItem; float end = (maxCursor - 0.2f) * sizeOfItem + m_focusedLayout->Size(m_orientation); float pos = (m_orientation == VERTICAL) ? point.y : point.x; if (pos < start && GetOffset() > -minCursor) { // scroll backward if (!InsideLayout(m_layout, point)) return false; float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount); m_analogScrollCount += amount * amount * mouse_scroll_speed; if (m_analogScrollCount > 1) { ScrollToOffset(GetOffset() - 1); m_analogScrollCount = 0; } return true; } else if (pos > end && GetOffset() + maxCursor < (int)m_items.size() - 1) { if (!InsideLayout(m_layout, point)) return false; // scroll forward float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount); m_analogScrollCount += amount * amount * mouse_scroll_speed; if (m_analogScrollCount > 1) { ScrollToOffset(GetOffset() + 1); m_analogScrollCount = 0; } return true; } else { // select the appropriate item int cursor = GetCursorFromPoint(point); if (cursor < 0) return false; // calling SelectItem() here will focus the item and scroll, which isn't really what we're after SetCursor(cursor); return true; } } void CGUIFixedListContainer::SelectItem(int item) { // Check that GetOffset() 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 - we first set the cursor position // which may be different at either end of the list, then the offset int minCursor, maxCursor; GetCursorRange(minCursor, maxCursor); int cursor; if ((int)m_items.size() - 1 - item <= maxCursor - m_fixedCursor) cursor = std::max(m_fixedCursor, maxCursor + item - (int)m_items.size() + 1); else if (item <= m_fixedCursor - minCursor) cursor = std::min(m_fixedCursor, minCursor + item); else cursor = m_fixedCursor; if (cursor != GetCursor()) SetContainerMoving(cursor - GetCursor()); SetCursor(cursor); ScrollToOffset(item - GetCursor()); MarkDirtyRegion(); } } bool CGUIFixedListContainer::HasPreviousPage() const { return (GetOffset() > 0); } bool CGUIFixedListContainer::HasNextPage() const { return (GetOffset() < (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage); } int CGUIFixedListContainer::GetCurrentPage() const { int offset = CorrectOffset(GetOffset(), GetCursor()); if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows()) // last page return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage; return offset / m_itemsPerPage + 1; } void CGUIFixedListContainer::GetCursorRange(int &minCursor, int &maxCursor) const { minCursor = std::max(m_fixedCursor - m_cursorRange, 0); maxCursor = std::min(m_fixedCursor + m_cursorRange, m_itemsPerPage); if (!m_items.size()) { minCursor = m_fixedCursor; maxCursor = m_fixedCursor; return; } while (maxCursor - minCursor > (int)m_items.size() - 1) { if (maxCursor - m_fixedCursor > m_fixedCursor - minCursor) maxCursor--; else minCursor++; } }