/*
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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern "C" {
#include "../common.h"
#include "../roots.h"
#include "../minuitwrp/minui.h"
#include "../recovery_ui.h"
}
#include "rapidxml.hpp"
#include "objects.hpp"
#include "../data.hpp"
#include "../twrp-functions.hpp"
#define SCROLLING_SPEED_DECREMENT 6
#define SCROLLING_FLOOR 10
#define SCROLLING_MULTIPLIER 6
GUIListBox::GUIListBox(xml_node<>* node)
{
xml_attribute<>* attr;
xml_node<>* child;
int header_separator_color_specified = 0, header_separator_height_specified = 0, header_text_color_specified = 0, header_background_color_specified = 0;
mStart = mLineSpacing = startY = mFontHeight = mSeparatorH = scrollingY = scrollingSpeed = 0;
mIconWidth = mIconHeight = mSelectedIconHeight = mSelectedIconWidth = mUnselectedIconHeight = mUnselectedIconWidth = mHeaderIconHeight = mHeaderIconWidth = 0;
mHeaderSeparatorH = mLineHeight = mHeaderIsStatic = mHeaderH = actualLineHeight = 0;
mIconSelected = mIconUnselected = mBackground = mFont = mHeaderIcon = NULL;
mBackgroundX = mBackgroundY = mBackgroundW = mBackgroundH = 0;
mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
mFastScrollRectX = mFastScrollRectY = -1;
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;
hasFontHighlightColor = false;
isHighlighted = false;
startSelection = -1;
// Load header text
child = node->first_node("header");
if (child)
{
attr = child->first_attribute("icon");
if (attr)
mHeaderIcon = PageManager::FindResource(attr->value());
attr = child->first_attribute("background");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mHeaderBackgroundColor);
header_background_color_specified = -1;
}
attr = child->first_attribute("textcolor");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mHeaderFontColor);
header_text_color_specified = -1;
}
attr = child->first_attribute("separatorcolor");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mHeaderSeparatorColor);
header_separator_color_specified = -1;
}
attr = child->first_attribute("separatorheight");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mHeaderSeparatorH = atoi(parsevalue.c_str());
header_separator_height_specified = -1;
}
}
child = node->first_node("text");
if (child) mHeaderText = child->value();
memset(&mHighlightColor, 0, sizeof(COLOR));
child = node->first_node("highlight");
if (child) {
attr = child->first_attribute("color");
if (attr) {
hasHighlightColor = true;
std::string color = attr->value();
ConvertStrToColor(color, &mHighlightColor);
}
}
// Simple way to check for static state
mLastValue = gui_parse_text(mHeaderText);
if (mLastValue != mHeaderText)
mHeaderIsStatic = 0;
else
mHeaderIsStatic = -1;
child = node->first_node("icon");
if (child)
{
attr = child->first_attribute("selected");
if (attr)
mIconSelected = PageManager::FindResource(attr->value());
attr = child->first_attribute("unselected");
if (attr)
mIconUnselected = PageManager::FindResource(attr->value());
}
child = node->first_node("background");
if (child)
{
attr = child->first_attribute("resource");
if (attr)
mBackground = PageManager::FindResource(attr->value());
attr = child->first_attribute("color");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mBackgroundColor);
if (!header_background_color_specified)
ConvertStrToColor(color, &mHeaderBackgroundColor);
}
}
// Load the placement
LoadPlacement(node->first_node("placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
// Load the font, and possibly override the color
child = node->first_node("font");
if (child)
{
attr = child->first_attribute("resource");
if (attr)
mFont = PageManager::FindResource(attr->value());
attr = child->first_attribute("color");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mFontColor);
if (!header_text_color_specified)
ConvertStrToColor(color, &mHeaderFontColor);
}
attr = child->first_attribute("spacing");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mLineSpacing = atoi(parsevalue.c_str());
}
attr = child->first_attribute("highlightcolor");
memset(&mFontHighlightColor, 0, sizeof(COLOR));
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mFontHighlightColor);
hasFontHighlightColor = true;
}
}
// Load the separator if it exists
child = node->first_node("separator");
if (child)
{
attr = child->first_attribute("color");
if (attr)
{
std::string color = attr->value();
ConvertStrToColor(color, &mSeparatorColor);
if (!header_separator_color_specified)
ConvertStrToColor(color, &mHeaderSeparatorColor);
}
attr = child->first_attribute("height");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mSeparatorH = atoi(parsevalue.c_str());
if (!header_separator_height_specified)
mHeaderSeparatorH = mSeparatorH;
}
}
// Handle the result variable
child = node->first_node("data");
if (child)
{
attr = child->first_attribute("name");
if (attr)
mVariable = attr->value();
attr = child->first_attribute("default");
if (attr)
DataManager::SetValue(mVariable, attr->value());
}
// Fast scroll colors
child = node->first_node("fastscroll");
if (child)
{
attr = child->first_attribute("linecolor");
if(attr)
ConvertStrToColor(attr->value(), &mFastScrollLineColor);
attr = child->first_attribute("rectcolor");
if(attr)
ConvertStrToColor(attr->value(), &mFastScrollRectColor);
attr = child->first_attribute("w");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mFastScrollW = atoi(parsevalue.c_str());
}
attr = child->first_attribute("linew");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mFastScrollLineW = atoi(parsevalue.c_str());
}
attr = child->first_attribute("rectw");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mFastScrollRectW = atoi(parsevalue.c_str());
}
attr = child->first_attribute("recth");
if (attr) {
string parsevalue = gui_parse_text(attr->value());
mFastScrollRectH = atoi(parsevalue.c_str());
}
}
// Retrieve the line height
gr_getFontDetails(mFont ? mFont->GetResource() : NULL, &mFontHeight, NULL);
mLineHeight = mFontHeight;
mHeaderH = mFontHeight;
if (mIconSelected && mIconSelected->GetResource())
{
mSelectedIconWidth = gr_get_width(mIconSelected->GetResource());
mSelectedIconHeight = gr_get_height(mIconSelected->GetResource());
if (mSelectedIconHeight > (int)mLineHeight)
mLineHeight = mSelectedIconHeight;
mIconWidth = mSelectedIconWidth;
}
if (mIconUnselected && mIconUnselected->GetResource())
{
mUnselectedIconWidth = gr_get_width(mIconUnselected->GetResource());
mUnselectedIconHeight = gr_get_height(mIconUnselected->GetResource());
if (mUnselectedIconHeight > (int)mLineHeight)
mLineHeight = mUnselectedIconHeight;
if (mUnselectedIconWidth > mIconWidth)
mIconWidth = mUnselectedIconWidth;
}
if (mHeaderIcon && mHeaderIcon->GetResource())
{
mHeaderIconWidth = gr_get_width(mHeaderIcon->GetResource());
mHeaderIconHeight = gr_get_height(mHeaderIcon->GetResource());
if (mHeaderIconHeight > mHeaderH)
mHeaderH = mHeaderIconHeight;
if (mHeaderIconWidth > mIconWidth)
mIconWidth = mHeaderIconWidth;
}
mHeaderH += mLineSpacing + mHeaderSeparatorH;
actualLineHeight = mLineHeight + mLineSpacing + mSeparatorH;
if (mHeaderH < actualLineHeight)
mHeaderH = actualLineHeight;
if (actualLineHeight / 3 > 6)
touchDebounce = actualLineHeight / 3;
if (mBackground && mBackground->GetResource())
{
mBackgroundW = gr_get_width(mBackground->GetResource());
mBackgroundH = gr_get_height(mBackground->GetResource());
}
// Get the currently selected value for the list
DataManager::GetValue(mVariable, currentValue);
// Get the data for the list
child = node->first_node("listitem");
if (!child) return;
while (child)
{
ListData data;
attr = child->first_attribute("name");
if (!attr) return;
data.displayName = attr->value();
data.variableValue = child->value();
if (child->value() == currentValue) {
data.selected = 1;
} else {
data.selected = 0;
}
mList.push_back(data);
child = child->next_sibling("listitem");
}
}
GUIListBox::~GUIListBox()
{
}
int GUIListBox::Render(void)
{
// First step, fill background
gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255);
gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
// Next, render the background resource (if it exists)
if (mBackground && mBackground->GetResource())
{
mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
}
// This tells us how many lines we can actually render
int lines = (mRenderH - mHeaderH) / (actualLineHeight);
int line;
int listSize = mList.size();
int listW = mRenderW;
if (listSize < lines) {
lines = listSize;
scrollingY = 0;
mFastScrollRectX = mFastScrollRectY = -1;
} else {
listW -= mFastScrollW; // space for fast scroll
lines++;
if (lines < listSize)
lines++;
}
void* fontResource = NULL;
if (mFont) fontResource = mFont->GetResource();
int yPos = mRenderY + mHeaderH + scrollingY;
int fontOffsetY = (int)((actualLineHeight - mFontHeight) / 2);
int currentIconHeight = 0, currentIconWidth = 0;
int currentIconOffsetY = 0, currentIconOffsetX = 0;
int UnselectedIconOffsetY = (int)((actualLineHeight - mUnselectedIconHeight) / 2), SelectedIconOffsetY = (int)((actualLineHeight - mSelectedIconHeight) / 2);
int UnselectedIconOffsetX = (mIconWidth - mUnselectedIconWidth) / 2, SelectedIconOffsetX = (mIconWidth - mSelectedIconWidth) / 2;
int actualSelection = mStart;
if (isHighlighted) {
int selectY = scrollingY;
// Locate the correct line for highlighting
while (selectY + actualLineHeight < startSelection) {
selectY += actualLineHeight;
actualSelection++;
}
if (hasHighlightColor) {
// Highlight the area
gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, 255);
int HighlightHeight = actualLineHeight;
if (mRenderY + mHeaderH + selectY + actualLineHeight > mRenderH + mRenderY) {
HighlightHeight = actualLineHeight - (mRenderY + mHeaderH + selectY + actualLineHeight - mRenderH - mRenderY);
}
gr_fill(mRenderX, mRenderY + mHeaderH + selectY, mRenderW, HighlightHeight);
}
}
for (line = 0; line < lines; line++)
{
Resource* icon;
std::string label;
if (line + mStart >= listSize)
continue;
label = mList.at(line + mStart).displayName;
if (isHighlighted && hasFontHighlightColor && line + mStart == actualSelection) {
// Use the highlight color for the font
gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, 255);
} else {
// Set the color for the font
gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, 255);
}
if (mList.at(line + mStart).selected != 0)
{
icon = mIconSelected;
currentIconHeight = mSelectedIconHeight;
currentIconWidth = mSelectedIconWidth;
currentIconOffsetY = SelectedIconOffsetY;
currentIconOffsetX = SelectedIconOffsetX;
}
else
{
icon = mIconUnselected;
currentIconHeight = mSelectedIconHeight;
currentIconWidth = mSelectedIconWidth;
currentIconOffsetY = SelectedIconOffsetY;
currentIconOffsetX = SelectedIconOffsetX;
}
if (icon && icon->GetResource())
{
int rect_y = 0, image_y = (yPos + currentIconOffsetY);
if (image_y + currentIconHeight > mRenderY + mRenderH)
rect_y = mRenderY + mRenderH - image_y;
else
rect_y = currentIconHeight;
gr_blit(icon->GetResource(), 0, 0, currentIconWidth, rect_y, mRenderX + currentIconOffsetX, image_y);
}
gr_textExWH(mRenderX + mIconWidth + 5, yPos + fontOffsetY, label.c_str(), fontResource, mRenderX + listW, mRenderY + mRenderH);
// Add the separator
if (yPos + actualLineHeight < mRenderH + mRenderY) {
gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, 255);
gr_fill(mRenderX, yPos + actualLineHeight - mSeparatorH, listW, mSeparatorH);
}
// Move the yPos
yPos += actualLineHeight;
}
// Render the Header (last so that it overwrites the top most row for per pixel scrolling)
// First step, fill background
gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, 255);
gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
// Now, we need the header (icon + text)
yPos = mRenderY;
{
Resource* headerIcon;
int mIconOffsetX = 0;
// render the icon if it exists
headerIcon = mHeaderIcon;
if (headerIcon && headerIcon->GetResource())
{
gr_blit(headerIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - mIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
mIconOffsetX = mIconWidth;
}
// render the text
gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, 255);
gr_textExWH(mRenderX + mIconOffsetX + 5, yPos + (int)((mHeaderH - mFontHeight) / 2), mLastValue.c_str(), fontResource, mRenderX + mRenderW, mRenderY + mRenderH);
// Add the separator
gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, 255);
gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
}
// render fast scroll
lines = (mRenderH - mHeaderH) / (actualLineHeight);
if(mFastScrollW > 0 && listSize > lines)
{
int startX = listW + mRenderX;
int fWidth = mRenderW - listW;
int fHeight = mRenderH - mHeaderH;
// line
gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, 255);
gr_fill(startX + fWidth/2, mRenderY + mHeaderH, mFastScrollLineW, mRenderH - mHeaderH);
// rect
int pct = ((mStart*actualLineHeight - scrollingY)*100)/((listSize)*actualLineHeight-lines*actualLineHeight);
mFastScrollRectX = startX + (fWidth - mFastScrollRectW)/2;
mFastScrollRectY = mRenderY+mHeaderH + ((fHeight - mFastScrollRectH)*pct)/100;
gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, 255);
gr_fill(mFastScrollRectX, mFastScrollRectY, mFastScrollRectW, mFastScrollRectH);
}
mUpdate = 0;
return 0;
}
int GUIListBox::Update(void)
{
if (!mHeaderIsStatic) {
std::string newValue = gui_parse_text(mHeaderText);
if (mLastValue != newValue) {
mLastValue = newValue;
mUpdate = 1;
}
}
if (mUpdate)
{
mUpdate = 0;
if (Render() == 0)
return 2;
}
// Handle kinetic scrolling
if (scrollingSpeed == 0) {
// Do nothing
} else if (scrollingSpeed > 0) {
if (scrollingSpeed < ((int) (actualLineHeight * 2.5))) {
scrollingY += scrollingSpeed;
scrollingSpeed -= SCROLLING_SPEED_DECREMENT;
} else {
scrollingY += ((int) (actualLineHeight * 2.5));
scrollingSpeed -= SCROLLING_SPEED_DECREMENT;
}
while (mStart && scrollingY > 0) {
mStart--;
scrollingY -= actualLineHeight;
}
if (mStart == 0 && scrollingY > 0) {
scrollingY = 0;
scrollingSpeed = 0;
} else if (scrollingSpeed < SCROLLING_FLOOR)
scrollingSpeed = 0;
mUpdate = 1;
} else if (scrollingSpeed < 0) {
int totalSize = mList.size();
int lines = (mRenderH - mHeaderH) / (actualLineHeight);
if (totalSize > lines) {
int bottom_offset = ((int)(mRenderH) - mHeaderH) - (lines * actualLineHeight);
bottom_offset -= actualLineHeight;
if (abs(scrollingSpeed) < ((int) (actualLineHeight * 2.5))) {
scrollingY += scrollingSpeed;
scrollingSpeed += SCROLLING_SPEED_DECREMENT;
} else {
scrollingY -= ((int) (actualLineHeight * 2.5));
scrollingSpeed += SCROLLING_SPEED_DECREMENT;
}
while (mStart + lines + (bottom_offset ? 1 : 0) < totalSize && abs(scrollingY) > actualLineHeight) {
mStart++;
scrollingY += actualLineHeight;
}
if (bottom_offset != 0 && mStart + lines + 1 >= totalSize && scrollingY <= bottom_offset) {
mStart = totalSize - lines - 1;
scrollingY = bottom_offset;
} else if (mStart + lines >= totalSize && scrollingY < 0) {
mStart = totalSize - lines;
scrollingY = 0;
} else if (scrollingSpeed * -1 < SCROLLING_FLOOR)
scrollingSpeed = 0;
mUpdate = 1;
}
}
return 0;
}
int GUIListBox::GetSelection(int x, int y)
{
// We only care about y position
if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH) return -1;
return (y - mRenderY - mHeaderH);
}
int GUIListBox::NotifyTouch(TOUCH_STATE state, int x, int y)
{
static int lastY = 0, last2Y = 0;
int selection = 0;
switch (state)
{
case TOUCH_START:
if (scrollingSpeed != 0)
startSelection = -1;
else
startSelection = GetSelection(x,y);
isHighlighted = (startSelection > -1);
if (isHighlighted)
mUpdate = 1;
startY = lastY = last2Y = y;
scrollingSpeed = 0;
break;
case TOUCH_DRAG:
// Check if we dragged out of the selection window
if (GetSelection(x, y) == -1) {
last2Y = lastY = 0;
if (isHighlighted) {
isHighlighted = false;
mUpdate = 1;
}
break;
}
// Fast scroll
if(mFastScrollRectX != -1 && x >= mRenderX + mRenderW - mFastScrollW)
{
int pct = ((y-mRenderY-mHeaderH)*100)/(mRenderH-mHeaderH);
int totalSize = mList.size();
int lines = (mRenderH - mHeaderH) / (actualLineHeight);
float l = float((totalSize-lines)*pct)/100;
if(l + lines >= totalSize)
{
mStart = totalSize - lines;
scrollingY = 0;
}
else
{
mStart = l;
scrollingY = -(l - int(l))*actualLineHeight;
}
startSelection = -1;
mUpdate = 1;
scrollingSpeed = 0;
isHighlighted = false;
break;
}
// Provide some debounce on initial touches
if (startSelection != -1 && abs(y - startY) < touchDebounce) {
isHighlighted = true;
mUpdate = 1;
break;
}
isHighlighted = false;
last2Y = lastY;
lastY = y;
startSelection = -1;
// Handle scrolling
scrollingY += y - startY;
startY = y;
while(mStart && scrollingY > 0) {
mStart--;
scrollingY -= actualLineHeight;
}
if (mStart == 0 && scrollingY > 0)
scrollingY = 0;
{
int totalSize = mList.size();
int lines = (mRenderH - mHeaderH) / (actualLineHeight);
if (totalSize > lines) {
int bottom_offset = ((int)(mRenderH) - mHeaderH) - (lines * actualLineHeight);
bottom_offset -= actualLineHeight;
while (mStart + lines + (bottom_offset ? 1 : 0) < totalSize && abs(scrollingY) > actualLineHeight) {
mStart++;
scrollingY += actualLineHeight;
}
if (bottom_offset != 0 && mStart + lines + 1 >= totalSize && scrollingY <= bottom_offset) {
mStart = totalSize - lines - 1;
scrollingY = bottom_offset;
} else if (mStart + lines >= totalSize && scrollingY < 0) {
mStart = totalSize - lines;
scrollingY = 0;
}
} else
scrollingY = 0;
}
mUpdate = 1;
break;
case TOUCH_RELEASE:
isHighlighted = false;
if (startSelection >= 0)
{
// We've selected an item!
std::string str;
int listSize = mList.size();
int selectY = scrollingY, actualSelection = mStart;
// Move the selection to the proper place in the array
while (selectY + actualLineHeight < startSelection) {
selectY += actualLineHeight;
actualSelection++;
}
if (actualSelection < listSize && !mVariable.empty())
{
int i;
for (i=0; i SCROLLING_FLOOR)
scrollingSpeed *= SCROLLING_MULTIPLIER;
else
scrollingSpeed = 0;
}
case TOUCH_REPEAT:
case TOUCH_HOLD:
break;
}
return 0;
}
int GUIListBox::NotifyVarChange(std::string varName, std::string value)
{
if (!mHeaderIsStatic) {
std::string newValue = gui_parse_text(mHeaderText);
if (mLastValue != newValue) {
mLastValue = newValue;
mStart = 0;
scrollingY = 0;
scrollingSpeed = 0;
mUpdate = 1;
}
}
if (varName == mVariable)
{
int i, listSize = mList.size(), selected_index = 0;
currentValue = value;
for (i=0; i mStart + lines - 1)
mStart = selected_index;
if (mStart > listSize - lines) {
mStart = listSize - lines;
} else if (selected_index < mStart) {
mStart = selected_index;
}
mUpdate = 1;
return 0;
}
return 0;
}
int GUIListBox::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 GUIListBox::SetPageFocus(int inFocus)
{
if (inFocus)
{
DataManager::GetValue(mVariable, currentValue);
NotifyVarChange(mVariable, currentValue);
mUpdate = 1;
}
}