summaryrefslogblamecommitdiffstats
path: root/gui/scrolllist.cpp
blob: 4b772d45d8800cc790cfec863ecb9020c778c669 (plain) (tree)




























                                                                            

                                                                      





                                                                                        


                                                                                                      

                                                            

                                         













                                                                                  


                               

                                                 


                                                            
 
                                                                                                  
 
                                             

                  
                                                               
                                                                 


                             
                                                                                               


                                                             
                                       

                  
                                                        


                                                                                         


                                          
                                            

                  

                                                                 

         
                      
                                             

                  

                                                                         
 



                                                                     


                                   
                                         
                                                                    
 
                                       
                                         
                  
         





                                                                                                
 













                                                                     





                                                      

                                                        




                               









                                                                              
                                                                


































                                                                                                                                           
                                                                                                              

                                                                              


                                                        







                                                                                                                  
                                                                   































                                                                      
                                    





                                                                             

                                                                                                                          

                 
                                                
                                                               
                                                                                                                                          

                                                     
                                                                                                      

                 
                              
                                                  

                                                                  
                                                                                            
                                                                                       

                                                                                                                                        

                 

                                                                                                        

                                    

                                                                                                                  





                                                                                                  
                        




                                                                                                                                              


                                               
                                                        






                                                                                                                                                                                                              

                                                                                                                                           

                                    
                                                                                                                                          










                                                                                                    
                                                                                                                                      













                                                                                                                                                                    
                                                                                                                                      


                                                                                                

                         

















                                                                         
                                               







                                                       


                                                            




                                                            


                                                            



























































































































                                                                                                                                                         
                                                                
















                                                                                                                      
                                                      
                                                                                                                                                                          

                                                             














                                                                                                                                                   
                                                             


                                                                             
                                                             






















































                                                                                         
/*
	Copyright 2013 bigbiff/Dees_Troy TeamWin
	This file is part of TWRP/TeamWin Recovery Project.

	TWRP is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	TWRP is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with TWRP.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <string.h>

extern "C" {
#include "../twcommon.h"
#include "../minuitwrp/minui.h"
}

#include "rapidxml.hpp"
#include "objects.hpp"
#include "../data.hpp"

const float SCROLLING_SPEED_DECREMENT = 0.9; // friction
const int SCROLLING_FLOOR = 2; // minimum pixels for scrolling to stop
const float SCROLLING_SPEED_LIMIT = 2.5; // maximum number of items to scroll per update

GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
{
	xml_attribute<>* attr;
	xml_node<>* child;

	firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
	maxIconWidth = maxIconHeight =  mHeaderIconHeight = mHeaderIconWidth = 0;
	mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
	mHeaderIsStatic = false;
	mBackground = mHeaderIcon = NULL;
	mFont = NULL;
	mBackgroundW = mBackgroundH = 0;
	mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
	lastY = last2Y = fastScroll = 0;
	mUpdate = 0;
	touchDebounce = 6;
	ConvertStrToColor("black", &mBackgroundColor);
	ConvertStrToColor("black", &mHeaderBackgroundColor);
	ConvertStrToColor("black", &mSeparatorColor);
	ConvertStrToColor("black", &mHeaderSeparatorColor);
	ConvertStrToColor("white", &mFontColor);
	ConvertStrToColor("white", &mHeaderFontColor);
	ConvertStrToColor("white", &mFastScrollLineColor);
	ConvertStrToColor("white", &mFastScrollRectColor);
	hasHighlightColor = false;
	selectedItem = NO_ITEM;

	// Load header text
	child = node->first_node("text");
	if (child)  mHeaderText = child->value();
	// Simple way to check for static state
	mLastHeaderValue = gui_parse_text(mHeaderText);
	mHeaderIsStatic = (mLastHeaderValue == mHeaderText);

	mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor);

	child = FindNode(node, "background");
	if (child)
	{
		mBackground = LoadAttrImage(child, "resource");
		mBackgroundColor = LoadAttrColor(child, "color");
	}

	// Load the placement
	LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);

	// Load the font, and possibly override the color
	child = FindNode(node, "font");
	if (child)
	{
		mFont = LoadAttrFont(child, "resource");
		mFontColor = LoadAttrColor(child, "color");
		mFontHighlightColor = LoadAttrColor(child, "highlightcolor", mFontColor);
		mItemSpacing = LoadAttrIntScaleY(child, "spacing");
	}

	// Load the separator if it exists
	child = FindNode(node, "separator");
	if (child)
	{
		mSeparatorColor = LoadAttrColor(child, "color");
		mSeparatorH = LoadAttrIntScaleY(child, "height");
	}

	// Fast scroll
	child = FindNode(node, "fastscroll");
	if (child)
	{
		mFastScrollLineColor = LoadAttrColor(child, "linecolor");
		mFastScrollRectColor = LoadAttrColor(child, "rectcolor");

		mFastScrollW = LoadAttrIntScaleX(child, "w");
		mFastScrollLineW = LoadAttrIntScaleX(child, "linew");
		mFastScrollRectW = LoadAttrIntScaleX(child, "rectw");
		mFastScrollRectH = LoadAttrIntScaleY(child, "recth");
	}

	// Retrieve the line height
	mFontHeight = mFont->GetHeight();
	actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;

	// Load the header if it exists
	child = FindNode(node, "header");
	if (child)
	{
		mHeaderH = mFontHeight;
		mHeaderIcon = LoadAttrImage(child, "icon");
		mHeaderBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
		mHeaderFontColor = LoadAttrColor(child, "textcolor", mFontColor);
		mHeaderSeparatorColor = LoadAttrColor(child, "separatorcolor", mSeparatorColor);
		mHeaderSeparatorH = LoadAttrIntScaleY(child, "separatorheight", mSeparatorH);

		if (mHeaderIcon && mHeaderIcon->GetResource())
		{
			mHeaderIconWidth = mHeaderIcon->GetWidth();
			mHeaderIconHeight = mHeaderIcon->GetHeight();
			if (mHeaderIconHeight > mHeaderH)
				mHeaderH = mHeaderIconHeight;
			if (mHeaderIconWidth > maxIconWidth)
				maxIconWidth = mHeaderIconWidth;
		}

		mHeaderH += mItemSpacing + mHeaderSeparatorH;
		if (mHeaderH < actualItemHeight)
			mHeaderH = actualItemHeight;
	}

	if (actualItemHeight / 3 > 6)
		touchDebounce = actualItemHeight / 3;

	if (mBackground && mBackground->GetResource())
	{
		mBackgroundW = mBackground->GetWidth();
		mBackgroundH = mBackground->GetHeight();
	}
}

GUIScrollList::~GUIScrollList()
{
}

void GUIScrollList::SetMaxIconSize(int w, int h)
{
	if (w > maxIconWidth)
		maxIconWidth = w;
	if (h > maxIconHeight)
		maxIconHeight = h;
	if (maxIconHeight > mFontHeight) {
		actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
		if (mHeaderH > 0 && actualItemHeight > mHeaderH)
			mHeaderH = actualItemHeight;
	}
}

void GUIScrollList::SetVisibleListLocation(size_t list_index)
{
	// This will make sure that the item indicated by list_index is visible on the screen
	size_t lines = GetDisplayItemCount(), listSize = GetItemCount();

	if (list_index <= (unsigned)firstDisplayedItem) {
		// list_index is above the currently displayed items, put the selected item at the very top
		firstDisplayedItem = list_index;
		y_offset = 0;
	} else if (list_index >= firstDisplayedItem + lines) {
		// list_index is below the currently displayed items, put the selected item at the very bottom
		firstDisplayedItem = list_index - lines + 1;
		if (GetDisplayRemainder() != 0) {
			// There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
			firstDisplayedItem--;
			y_offset = GetDisplayRemainder() - actualItemHeight;
		} else {
			// There's no partial row so zero out the offset
			y_offset = 0;
		}
	}
	scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
	mUpdate = 1;
}

int GUIScrollList::Render(void)
{
	if(!isConditionTrue())
		return 0;

	// First step, fill background
	gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
	gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);

	// don't paint outside of the box
	gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);

	// Next, render the background resource (if it exists)
	if (mBackground && mBackground->GetResource())
	{
		int mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
		int mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
		gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
	}

	// This tells us how many full lines we can actually render
	size_t lines = GetDisplayItemCount();

	size_t listSize = GetItemCount();
	int listW = mRenderW;

	if (listSize <= lines) {
		hasScroll = false;
		scrollingSpeed = 0;
		lines = listSize;
		y_offset = 0;
	} else {
		hasScroll = true;
		listW -= mFastScrollW; // space for fast scroll
		lines++;
		if (lines < listSize)
			lines++;
	}

	void* fontResource = NULL;
	if (mFont)  fontResource = mFont->GetResource();

	int yPos = mRenderY + mHeaderH + y_offset;
	int fontOffsetY = (int)((actualItemHeight - mFontHeight) / 2);

	// render all visible items
	for (size_t line = 0; line < lines; line++)
	{
		size_t itemindex = line + firstDisplayedItem;
		if (itemindex >= listSize)
			break;

		// get item data
		ImageResource* icon;
		std::string label;
		if (GetListItem(itemindex, icon, label))
			break;

		if (hasHighlightColor && itemindex == selectedItem) {
			// Highlight the item background of the selected item
			gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
			gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
		}

		if (itemindex == selectedItem) {
			// Use the highlight color for the font
			gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
		} else {
			// Set the color for the font
			gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
		}

		// render icon
		if (icon && icon->GetResource()) {
			int currentIconHeight = icon->GetHeight();
			int currentIconWidth = icon->GetWidth();
			int currentIconOffsetY = (actualItemHeight - currentIconHeight) / 2;
			int currentIconOffsetX = (maxIconWidth - currentIconWidth) / 2;
			int image_y = (yPos + currentIconOffsetY);
			gr_blit(icon->GetResource(), 0, 0, currentIconWidth, currentIconHeight, mRenderX + currentIconOffsetX, image_y);
		}

		// render label text
		gr_textEx(mRenderX + maxIconWidth + 5, yPos + fontOffsetY, label.c_str(), fontResource);

		// Add the separator
		gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
		gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);

		// Move the yPos
		yPos += actualItemHeight;
	}

	// Render the Header (last so that it overwrites the top most row for per pixel scrolling)
	yPos = mRenderY;
	if (mHeaderH > 0) {
		// First step, fill background
		gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
		gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);

		int mIconOffsetX = 0;

		// render the icon if it exists
		ImageResource* headerIcon = mHeaderIcon;
		if (headerIcon && headerIcon->GetResource())
		{
			gr_blit(headerIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
			mIconOffsetX = maxIconWidth;
		}

		// render the text
		gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
		gr_textEx(mRenderX + mIconOffsetX + 5, yPos + (int)((mHeaderH - mFontHeight) / 2), mLastHeaderValue.c_str(), fontResource);

		// Add the separator
		gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
		gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
	}

	// render fast scroll
	lines = GetDisplayItemCount();
	if (hasScroll) {
		int startX = listW + mRenderX;
		int fWidth = mRenderW - listW;
		int fHeight = mRenderH - mHeaderH;

		// line
		gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
		gr_fill(startX + fWidth/2, mRenderY + mHeaderH, mFastScrollLineW, mRenderH - mHeaderH);

		// rect
		int pct = 0;
		if (GetDisplayRemainder() != 0) {
			// Properly handle the percentage if a partial line is present
			int partial_line_size = actualItemHeight - GetDisplayRemainder();
			pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-((lines + 1)*actualItemHeight) + partial_line_size);
		} else {
			pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-lines*actualItemHeight);
		}
		int mFastScrollRectX = startX + (fWidth - mFastScrollRectW)/2;
		int mFastScrollRectY = mRenderY+mHeaderH + ((fHeight - mFastScrollRectH)*pct)/100;

		gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
		gr_fill(mFastScrollRectX, mFastScrollRectY, mFastScrollRectW, mFastScrollRectH);
	}
	mUpdate = 0;
	// reset clipping
	gr_noclip();
	return 0;
}

int GUIScrollList::Update(void)
{
	if(!isConditionTrue())
		return 0;

	if (!mHeaderIsStatic) {
		std::string newValue = gui_parse_text(mHeaderText);
		if (mLastHeaderValue != newValue) {
			mLastHeaderValue = newValue;
			mUpdate = 1;
		}
	}

	// Handle kinetic scrolling
	int maxScrollDistance = actualItemHeight * SCROLLING_SPEED_LIMIT;
	int oldScrollingSpeed = scrollingSpeed;
	if (scrollingSpeed == 0) {
		// Do nothing
		return 0;
	} else if (scrollingSpeed > 0) {
		if (scrollingSpeed < maxScrollDistance)
			y_offset += scrollingSpeed;
		else
			y_offset += maxScrollDistance;
		scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
		if (scrollingSpeed == oldScrollingSpeed)
			--scrollingSpeed;
	} else if (scrollingSpeed < 0) {
		if (abs(scrollingSpeed) < maxScrollDistance)
			y_offset += scrollingSpeed;
		else
			y_offset -= maxScrollDistance;
		scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
		if (scrollingSpeed == oldScrollingSpeed)
			++scrollingSpeed;
	}
	if (abs(scrollingSpeed) < SCROLLING_FLOOR)
		scrollingSpeed = 0;
	HandleScrolling();
	mUpdate = 1;

	return 0;
}

size_t GUIScrollList::HitTestItem(int x, int y)
{
	// We only care about y position
	if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
		return NO_ITEM;

	int startSelection = (y - mRenderY - mHeaderH);

	// Locate the correct item
	size_t actualSelection = firstDisplayedItem;
	int selectY = y_offset;
	while (selectY + actualItemHeight < startSelection) {
		selectY += actualItemHeight;
		actualSelection++;
	}

	if (actualSelection < GetItemCount())
		return actualSelection;

	return NO_ITEM;
}

int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
{
	if(!isConditionTrue())
		return -1;

	switch (state)
	{
	case TOUCH_START:
		if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW)
			fastScroll = 1; // Initial touch is in the fast scroll region
		if (scrollingSpeed != 0) {
			selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
			scrollingSpeed = 0; // stop scrolling on a new touch
		} else if (!fastScroll) {
			// find out which item the user touched
			selectedItem = HitTestItem(x, y);
		}
		if (selectedItem != NO_ITEM)
			mUpdate = 1;
		lastY = last2Y = y;
		break;

	case TOUCH_DRAG:
		if (fastScroll)
		{
			int pct = ((y-mRenderY-mHeaderH)*100)/(mRenderH-mHeaderH);
			int totalSize = GetItemCount();
			int lines = GetDisplayItemCount();

			float l = float((totalSize-lines)*pct)/100;
			if(l + lines >= totalSize)
			{
				firstDisplayedItem = totalSize - lines;
				if (GetDisplayRemainder() != 0) {
					// There's a partial row displayed, set the scrolling offset so that the last item really is at the very bottom
					firstDisplayedItem--;
					y_offset = GetDisplayRemainder() - actualItemHeight;
				} else {
					// There's no partial row so zero out the offset
					y_offset = 0;
				}
			}
			else
			{
				if (l < 0)
					l = 0;
				firstDisplayedItem = l;
				y_offset = -(l - int(l))*actualItemHeight;
				if (GetDisplayRemainder() != 0) {
					// There's a partial row displayed, make sure y_offset doesn't go past the max
					if (firstDisplayedItem == totalSize - lines - 1 && y_offset < GetDisplayRemainder() - actualItemHeight)
						y_offset = GetDisplayRemainder() - actualItemHeight;
				} else if (firstDisplayedItem == totalSize - lines)
					y_offset = 0;
			}

			selectedItem = NO_ITEM;
			mUpdate = 1;
			scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
			break;
		}

		// Provide some debounce on initial touches
		if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
			mUpdate = 1;
			break;
		}

		selectedItem = NO_ITEM; // nothing is selected because we dragged too far
		// Handle scrolling
		if (hasScroll) {
			y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
			last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
			lastY = y; // update last touch to the current touch so we can tell how far and what direction we scroll for the next touch event

			HandleScrolling();
		} else
			y_offset = 0;
		mUpdate = 1;
		break;

	case TOUCH_RELEASE:
		fastScroll = 0;
		if (selectedItem != NO_ITEM) {
			// We've selected an item!
			NotifySelect(selectedItem);
			mUpdate = 1;

			DataManager::Vibrate("tw_button_vibrate");
			selectedItem = NO_ITEM;
		} else {
			// Start kinetic scrolling
			scrollingSpeed = lastY - last2Y;
			if (abs(scrollingSpeed) < touchDebounce)
				scrollingSpeed = 0;
		}
	case TOUCH_REPEAT:
	case TOUCH_HOLD:
		break;
	}
	return 0;
}

void GUIScrollList::HandleScrolling()
{
	// handle dragging downward, scrolling upward
	// the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
	while(firstDisplayedItem && y_offset > 0) {
		firstDisplayedItem--;
		y_offset -= actualItemHeight;
	}
	if (firstDisplayedItem == 0 && y_offset > 0) {
		y_offset = 0; // user kept dragging downward past the top of the list, so always reset the offset to 0 since we can't scroll any further in this direction
		scrollingSpeed = 0; // stop kinetic scrolling
	}

	// handle dragging upward, scrolling downward
	int totalSize = GetItemCount();
	int lines = GetDisplayItemCount(); // number of full lines our list can display at once
	int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling

	// the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
	while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
		firstDisplayedItem++;
		y_offset += actualItemHeight;
	}
	// Check if we dragged too far, set the list at the bottom and adjust offset as needed
	if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
		firstDisplayedItem = totalSize - lines - 1;
		y_offset = bottom_offset;
		scrollingSpeed = 0; // stop kinetic scrolling
	} else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
		firstDisplayedItem = totalSize - lines;
		y_offset = 0;
		scrollingSpeed = 0; // stop kinetic scrolling
	}
}

int GUIScrollList::GetDisplayItemCount()
{
	return (mRenderH - mHeaderH) / (actualItemHeight);
}

int GUIScrollList::GetDisplayRemainder()
{
	return (mRenderH - mHeaderH) % actualItemHeight;
}

int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
{
	GUIObject::NotifyVarChange(varName, value);

	if(!isConditionTrue())
		return 0;

	if (!mHeaderIsStatic) {
		std::string newValue = gui_parse_text(mHeaderText);
		if (mLastHeaderValue != newValue) {
			mLastHeaderValue = newValue;
			firstDisplayedItem = 0;
			y_offset = 0;
			scrollingSpeed = 0; // stop kinetic scrolling on variable changes
			mUpdate = 1;
		}
	}
	return 0;
}

int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
{
	mRenderX = x;
	mRenderY = y;
	if (w || h)
	{
		mRenderW = w;
		mRenderH = h;
	}
	SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
	mUpdate = 1;
	return 0;
}

void GUIScrollList::SetPageFocus(int inFocus)
{
	if (inFocus) {
		NotifyVarChange("", ""); // This forces a check for the header text
		scrollingSpeed = 0; // stop kinetic scrolling on page changes
		mUpdate = 1;
	}
}