/* * 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 "GUIScrollBarControl.h" #include "GUIMessage.h" #include "input/Key.h" #include "input/mouse/MouseStat.h" #include "utils/StringUtils.h" #define MIN_NIB_SIZE 4.0f GUIScrollBarControl::GUIScrollBarControl(int parentID, int controlID, float posX, float posY, float width, float height, const CTextureInfo& backGroundTexture, const CTextureInfo& barTexture, const CTextureInfo& barTextureFocus, const CTextureInfo& nibTexture, const CTextureInfo& nibTextureFocus, ORIENTATION orientation, bool showOnePage) : CGUIControl(parentID, controlID, posX, posY, width, height), m_guiBackground(CGUITexture::CreateTexture(posX, posY, width, height, backGroundTexture)), m_guiBarNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, barTexture)), m_guiBarFocus(CGUITexture::CreateTexture(posX, posY, width, height, barTextureFocus)), m_guiNibNoFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTexture)), m_guiNibFocus(CGUITexture::CreateTexture(posX, posY, width, height, nibTextureFocus)) { m_guiNibNoFocus->SetAspectRatio(CAspectRatio::AR_CENTER); m_guiNibFocus->SetAspectRatio(CAspectRatio::AR_CENTER); m_numItems = 100; m_offset = 0; m_pageSize = 10; ControlType = GUICONTROL_SCROLLBAR; m_orientation = orientation; m_showOnePage = showOnePage; } GUIScrollBarControl::GUIScrollBarControl(const GUIScrollBarControl& control) : CGUIControl(control), m_guiBackground(control.m_guiBackground->Clone()), m_guiBarNoFocus(control.m_guiBarNoFocus->Clone()), m_guiBarFocus(control.m_guiBarFocus->Clone()), m_guiNibNoFocus(control.m_guiNibNoFocus->Clone()), m_guiNibFocus(control.m_guiNibFocus->Clone()), m_numItems(control.m_numItems), m_pageSize(control.m_pageSize), m_offset(control.m_offset), m_showOnePage(control.m_showOnePage), m_orientation(control.m_orientation) { } void GUIScrollBarControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) { bool changed = false; if (m_bInvalidated) changed |= UpdateBarSize(); changed |= m_guiBackground->Process(currentTime); changed |= m_guiBarNoFocus->Process(currentTime); changed |= m_guiBarFocus->Process(currentTime); changed |= m_guiNibNoFocus->Process(currentTime); changed |= m_guiNibFocus->Process(currentTime); if (changed) MarkDirtyRegion(); CGUIControl::Process(currentTime, dirtyregions); } void GUIScrollBarControl::Render() { m_guiBackground->Render(); if (m_bHasFocus) { m_guiBarFocus->Render(); m_guiNibFocus->Render(); } else { m_guiBarNoFocus->Render(); m_guiNibNoFocus->Render(); } CGUIControl::Render(); } bool GUIScrollBarControl::OnMessage(CGUIMessage& message) { switch (message.GetMessage()) { case GUI_MSG_ITEM_SELECT: SetValue(message.GetParam1()); return true; case GUI_MSG_LABEL_RESET: SetRange(message.GetParam1(), message.GetParam2()); return true; case GUI_MSG_PAGE_UP: Move(-1); return true; case GUI_MSG_PAGE_DOWN: Move(1); return true; } return CGUIControl::OnMessage(message); } bool GUIScrollBarControl::OnAction(const CAction &action) { switch ( action.GetID() ) { case ACTION_MOVE_LEFT: if (m_orientation == HORIZONTAL) { if(Move( -1)) return true; } break; case ACTION_MOVE_RIGHT: if (m_orientation == HORIZONTAL) { if(Move(1)) return true; } break; case ACTION_MOVE_UP: if (m_orientation == VERTICAL) { if(Move(-1)) return true; } break; case ACTION_MOVE_DOWN: if (m_orientation == VERTICAL) { if(Move(1)) return true; } break; } return CGUIControl::OnAction(action); } bool GUIScrollBarControl::Move(int numSteps) { if (numSteps < 0 && m_offset == 0) // we are at the beginning - can't scroll up/left anymore return false; if (numSteps > 0 && m_offset == std::max(m_numItems - m_pageSize, 0)) // we are at the end - we can't scroll down/right anymore return false; m_offset += numSteps * m_pageSize; if (m_offset > m_numItems - m_pageSize) m_offset = m_numItems - m_pageSize; if (m_offset < 0) m_offset = 0; CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetParentID(), GetID(), GUI_MSG_PAGE_CHANGE, m_offset); SendWindowMessage(message); SetInvalid(); return true; } void GUIScrollBarControl::SetRange(int pageSize, int numItems) { if (m_pageSize != pageSize || m_numItems != numItems) { m_pageSize = pageSize; m_numItems = numItems; m_offset = 0; SetInvalid(); } } void GUIScrollBarControl::SetValue(int value) { if (m_offset != value) { m_offset = value; SetInvalid(); } } void GUIScrollBarControl::FreeResources(bool immediately) { CGUIControl::FreeResources(immediately); m_guiBackground->FreeResources(immediately); m_guiBarNoFocus->FreeResources(immediately); m_guiBarFocus->FreeResources(immediately); m_guiNibNoFocus->FreeResources(immediately); m_guiNibFocus->FreeResources(immediately); } void GUIScrollBarControl::DynamicResourceAlloc(bool bOnOff) { CGUIControl::DynamicResourceAlloc(bOnOff); m_guiBackground->DynamicResourceAlloc(bOnOff); m_guiBarNoFocus->DynamicResourceAlloc(bOnOff); m_guiBarFocus->DynamicResourceAlloc(bOnOff); m_guiNibNoFocus->DynamicResourceAlloc(bOnOff); m_guiNibFocus->DynamicResourceAlloc(bOnOff); } void GUIScrollBarControl::AllocResources() { CGUIControl::AllocResources(); m_guiBackground->AllocResources(); m_guiBarNoFocus->AllocResources(); m_guiBarFocus->AllocResources(); m_guiNibNoFocus->AllocResources(); m_guiNibFocus->AllocResources(); } void GUIScrollBarControl::SetInvalid() { CGUIControl::SetInvalid(); m_guiBackground->SetInvalid(); m_guiBarFocus->SetInvalid(); m_guiBarFocus->SetInvalid(); m_guiNibNoFocus->SetInvalid(); m_guiNibFocus->SetInvalid(); } bool GUIScrollBarControl::UpdateBarSize() { bool changed = false; // scale our textures to suit if (m_orientation == VERTICAL) { // calculate the height to display the nib at float percent = (m_numItems == 0) ? 0 : (float)m_pageSize / m_numItems; float nibSize = GetHeight() * percent; if (nibSize < m_guiNibFocus->GetTextureHeight() + 2 * MIN_NIB_SIZE) nibSize = m_guiNibFocus->GetTextureHeight() + 2 * MIN_NIB_SIZE; if (nibSize > GetHeight()) nibSize = GetHeight(); changed |= m_guiBarNoFocus->SetHeight(nibSize); changed |= m_guiBarFocus->SetHeight(nibSize); changed |= m_guiNibNoFocus->SetHeight(nibSize); changed |= m_guiNibFocus->SetHeight(nibSize); // nibSize may be altered by the border size of the nib (and bar). nibSize = std::max(m_guiBarFocus->GetHeight(), m_guiNibFocus->GetHeight()); // and the position percent = (m_numItems == m_pageSize) ? 0 : (float)m_offset / (m_numItems - m_pageSize); float nibPos = (GetHeight() - nibSize) * percent; if (nibPos < 0) nibPos = 0; if (nibPos > GetHeight() - nibSize) nibPos = GetHeight() - nibSize; changed |= m_guiBarNoFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos); changed |= m_guiBarFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos); changed |= m_guiNibNoFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos); changed |= m_guiNibFocus->SetPosition(GetXPosition(), GetYPosition() + nibPos); } else { // calculate the height to display the nib at float percent = (m_numItems == 0) ? 0 : (float)m_pageSize / m_numItems; float nibSize = GetWidth() * percent + 0.5f; if (nibSize < m_guiNibFocus->GetTextureWidth() + 2 * MIN_NIB_SIZE) nibSize = m_guiNibFocus->GetTextureWidth() + 2 * MIN_NIB_SIZE; if (nibSize > GetWidth()) nibSize = GetWidth(); changed |= m_guiBarNoFocus->SetWidth(nibSize); changed |= m_guiBarFocus->SetWidth(nibSize); changed |= m_guiNibNoFocus->SetWidth(nibSize); changed |= m_guiNibFocus->SetWidth(nibSize); // and the position percent = (m_numItems == m_pageSize) ? 0 : (float)m_offset / (m_numItems - m_pageSize); float nibPos = (GetWidth() - nibSize) * percent; if (nibPos < 0) nibPos = 0; if (nibPos > GetWidth() - nibSize) nibPos = GetWidth() - nibSize; changed |= m_guiBarNoFocus->SetPosition(GetXPosition() + nibPos, GetYPosition()); changed |= m_guiBarFocus->SetPosition(GetXPosition() + nibPos, GetYPosition()); changed |= m_guiNibNoFocus->SetPosition(GetXPosition() + nibPos, GetYPosition()); changed |= m_guiNibFocus->SetPosition(GetXPosition() + nibPos, GetYPosition()); } return changed; } void GUIScrollBarControl::SetFromPosition(const CPoint &point) { float fPercent; if (m_orientation == VERTICAL) fPercent = (point.y - m_guiBackground->GetYPosition() - 0.5f * m_guiBarFocus->GetHeight()) / (m_guiBackground->GetHeight() - m_guiBarFocus->GetHeight()); else fPercent = (point.x - m_guiBackground->GetXPosition() - 0.5f * m_guiBarFocus->GetWidth()) / (m_guiBackground->GetWidth() - m_guiBarFocus->GetWidth()); if (fPercent < 0) fPercent = 0; if (fPercent > 1) fPercent = 1; int offset = (int)(floor(fPercent * (m_numItems - m_pageSize) + 0.5f)); if (m_offset != offset) { m_offset = offset; CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetParentID(), GetID(), GUI_MSG_PAGE_CHANGE, m_offset); SendWindowMessage(message); SetInvalid(); } } EVENT_RESULT GUIScrollBarControl::OnMouseEvent(const CPoint &point, const CMouseEvent &event) { if (event.m_id == ACTION_MOUSE_DRAG || event.m_id == ACTION_MOUSE_DRAG_END) { if (static_cast(event.m_state) == HoldAction::DRAG) { // we want exclusive access CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID()); SendWindowMessage(msg); } else if (static_cast(event.m_state) == HoldAction::DRAG_END) { // we're done with exclusive access CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID()); SendWindowMessage(msg); } SetFromPosition(point); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_MOUSE_LEFT_CLICK && m_guiBackground->HitTest(point)) { SetFromPosition(point); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_MOUSE_WHEEL_UP) { Move(-1); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_MOUSE_WHEEL_DOWN) { Move(1); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_NOTIFY) { return (m_orientation == HORIZONTAL) ? EVENT_RESULT_PAN_HORIZONTAL_WITHOUT_INERTIA : EVENT_RESULT_PAN_VERTICAL_WITHOUT_INERTIA; } else if (event.m_id == ACTION_GESTURE_BEGIN) { // grab exclusive access CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, GetID(), GetParentID()); SendWindowMessage(msg); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_PAN) { // do the drag SetFromPosition(point); return EVENT_RESULT_HANDLED; } else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT) { // release exclusive access CGUIMessage msg(GUI_MSG_EXCLUSIVE_MOUSE, 0, GetParentID()); SendWindowMessage(msg); return EVENT_RESULT_HANDLED; } return EVENT_RESULT_UNHANDLED; } std::string GUIScrollBarControl::GetDescription() const { return StringUtils::Format("{}/{}", m_offset, m_numItems); } bool GUIScrollBarControl::UpdateColors(const CGUIListItem* item) { bool changed = CGUIControl::UpdateColors(nullptr); changed |= m_guiBackground->SetDiffuseColor(m_diffuseColor); changed |= m_guiBarNoFocus->SetDiffuseColor(m_diffuseColor); changed |= m_guiBarFocus->SetDiffuseColor(m_diffuseColor); changed |= m_guiNibNoFocus->SetDiffuseColor(m_diffuseColor); changed |= m_guiNibFocus->SetDiffuseColor(m_diffuseColor); return changed; } bool GUIScrollBarControl::IsVisible() const { // page controls can be optionally disabled if the number of pages is 1 if (m_numItems <= m_pageSize && !m_showOnePage) return false; return CGUIControl::IsVisible(); }