blob: 70a54e5ac52c7de6d48c82fc85f7ac6fccfd0252 [file] [log] [blame]
Ethan Yonker0a3a98f2015-02-05 00:48:28 +01001/*
2 Copyright 2013 bigbiff/Dees_Troy TeamWin
3 This file is part of TWRP/TeamWin Recovery Project.
4
5 TWRP is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 TWRP is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with TWRP. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include <string.h>
20
21extern "C" {
22#include "../twcommon.h"
23#include "../minuitwrp/minui.h"
24}
25
26#include "rapidxml.hpp"
27#include "objects.hpp"
28#include "../data.hpp"
29
that10ec0172015-02-15 23:52:28 +010030const float SCROLLING_SPEED_DECREMENT = 0.9; // friction
31const int SCROLLING_FLOOR = 2; // minimum pixels for scrolling to stop
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010032const float SCROLLING_SPEED_LIMIT = 2.5; // maximum number of items to scroll per update
33
34GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
35{
36 xml_attribute<>* attr;
37 xml_node<>* child;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010038
39 firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
40 maxIconWidth = maxIconHeight = mHeaderIconHeight = mHeaderIconWidth = 0;
that9876ac32015-02-15 21:40:59 +010041 mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
42 mHeaderIsStatic = false;
thatf6ed8fc2015-02-14 20:23:16 +010043 mBackground = mHeaderIcon = NULL;
44 mFont = NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010045 mBackgroundW = mBackgroundH = 0;
46 mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
thata9998212015-02-19 22:51:24 +010047 mFastScrollRectCurrentY = mFastScrollRectCurrentH = mFastScrollRectTouchY = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010048 lastY = last2Y = fastScroll = 0;
49 mUpdate = 0;
50 touchDebounce = 6;
51 ConvertStrToColor("black", &mBackgroundColor);
52 ConvertStrToColor("black", &mHeaderBackgroundColor);
53 ConvertStrToColor("black", &mSeparatorColor);
54 ConvertStrToColor("black", &mHeaderSeparatorColor);
55 ConvertStrToColor("white", &mFontColor);
56 ConvertStrToColor("white", &mHeaderFontColor);
57 ConvertStrToColor("white", &mFastScrollLineColor);
58 ConvertStrToColor("white", &mFastScrollRectColor);
59 hasHighlightColor = false;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010060 selectedItem = NO_ITEM;
61
62 // Load header text
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010063 child = node->first_node("text");
64 if (child) mHeaderText = child->value();
that9876ac32015-02-15 21:40:59 +010065 // Simple way to check for static state
66 mLastHeaderValue = gui_parse_text(mHeaderText);
67 mHeaderIsStatic = (mLastHeaderValue == mHeaderText);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010068
Ethan Yonker21ff02a2015-02-18 14:35:00 -060069 mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010070
Ethan Yonker21ff02a2015-02-18 14:35:00 -060071 child = FindNode(node, "background");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010072 if (child)
73 {
thatf6ed8fc2015-02-14 20:23:16 +010074 mBackground = LoadAttrImage(child, "resource");
that9876ac32015-02-15 21:40:59 +010075 mBackgroundColor = LoadAttrColor(child, "color");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010076 }
77
78 // Load the placement
Ethan Yonker21ff02a2015-02-18 14:35:00 -060079 LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010080 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
81
82 // Load the font, and possibly override the color
Ethan Yonker21ff02a2015-02-18 14:35:00 -060083 child = FindNode(node, "font");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010084 if (child)
85 {
thatf6ed8fc2015-02-14 20:23:16 +010086 mFont = LoadAttrFont(child, "resource");
that9876ac32015-02-15 21:40:59 +010087 mFontColor = LoadAttrColor(child, "color");
88 mFontHighlightColor = LoadAttrColor(child, "highlightcolor", mFontColor);
89 mItemSpacing = LoadAttrIntScaleY(child, "spacing");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010090 }
91
92 // Load the separator if it exists
Ethan Yonker21ff02a2015-02-18 14:35:00 -060093 child = FindNode(node, "separator");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010094 if (child)
95 {
that9876ac32015-02-15 21:40:59 +010096 mSeparatorColor = LoadAttrColor(child, "color");
97 mSeparatorH = LoadAttrIntScaleY(child, "height");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010098 }
99
that9876ac32015-02-15 21:40:59 +0100100 // Fast scroll
Ethan Yonker21ff02a2015-02-18 14:35:00 -0600101 child = FindNode(node, "fastscroll");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100102 if (child)
103 {
that9876ac32015-02-15 21:40:59 +0100104 mFastScrollLineColor = LoadAttrColor(child, "linecolor");
105 mFastScrollRectColor = LoadAttrColor(child, "rectcolor");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100106
that9876ac32015-02-15 21:40:59 +0100107 mFastScrollW = LoadAttrIntScaleX(child, "w");
108 mFastScrollLineW = LoadAttrIntScaleX(child, "linew");
109 mFastScrollRectW = LoadAttrIntScaleX(child, "rectw");
110 mFastScrollRectH = LoadAttrIntScaleY(child, "recth");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100111 }
112
113 // Retrieve the line height
thatf6ed8fc2015-02-14 20:23:16 +0100114 mFontHeight = mFont->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100115 actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100116
117 // Load the header if it exists
Ethan Yonker21ff02a2015-02-18 14:35:00 -0600118 child = FindNode(node, "header");
that9876ac32015-02-15 21:40:59 +0100119 if (child)
120 {
121 mHeaderH = mFontHeight;
122 mHeaderIcon = LoadAttrImage(child, "icon");
123 mHeaderBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
124 mHeaderFontColor = LoadAttrColor(child, "textcolor", mFontColor);
125 mHeaderSeparatorColor = LoadAttrColor(child, "separatorcolor", mSeparatorColor);
126 mHeaderSeparatorH = LoadAttrIntScaleY(child, "separatorheight", mSeparatorH);
127
128 if (mHeaderIcon && mHeaderIcon->GetResource())
129 {
130 mHeaderIconWidth = mHeaderIcon->GetWidth();
131 mHeaderIconHeight = mHeaderIcon->GetHeight();
132 if (mHeaderIconHeight > mHeaderH)
133 mHeaderH = mHeaderIconHeight;
134 if (mHeaderIconWidth > maxIconWidth)
135 maxIconWidth = mHeaderIconWidth;
136 }
137
138 mHeaderH += mItemSpacing + mHeaderSeparatorH;
139 if (mHeaderH < actualItemHeight)
140 mHeaderH = actualItemHeight;
141 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100142
143 if (actualItemHeight / 3 > 6)
144 touchDebounce = actualItemHeight / 3;
145
146 if (mBackground && mBackground->GetResource())
147 {
thatf6ed8fc2015-02-14 20:23:16 +0100148 mBackgroundW = mBackground->GetWidth();
149 mBackgroundH = mBackground->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100150 }
151}
152
153GUIScrollList::~GUIScrollList()
154{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100155}
156
157void GUIScrollList::SetMaxIconSize(int w, int h)
158{
159 if (w > maxIconWidth)
160 maxIconWidth = w;
161 if (h > maxIconHeight)
162 maxIconHeight = h;
163 if (maxIconHeight > mFontHeight) {
164 actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100165 if (mHeaderH > 0 && actualItemHeight > mHeaderH)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100166 mHeaderH = actualItemHeight;
167 }
168}
169
170void GUIScrollList::SetVisibleListLocation(size_t list_index)
171{
172 // This will make sure that the item indicated by list_index is visible on the screen
173 size_t lines = GetDisplayItemCount(), listSize = GetItemCount();
174
175 if (list_index <= (unsigned)firstDisplayedItem) {
176 // list_index is above the currently displayed items, put the selected item at the very top
177 firstDisplayedItem = list_index;
178 y_offset = 0;
179 } else if (list_index >= firstDisplayedItem + lines) {
180 // list_index is below the currently displayed items, put the selected item at the very bottom
181 firstDisplayedItem = list_index - lines + 1;
182 if (GetDisplayRemainder() != 0) {
183 // There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
184 firstDisplayedItem--;
185 y_offset = GetDisplayRemainder() - actualItemHeight;
186 } else {
187 // There's no partial row so zero out the offset
188 y_offset = 0;
189 }
190 }
191 scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
192 mUpdate = 1;
193}
194
195int GUIScrollList::Render(void)
196{
197 if(!isConditionTrue())
198 return 0;
199
200 // First step, fill background
that9876ac32015-02-15 21:40:59 +0100201 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100202 gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
203
that9876ac32015-02-15 21:40:59 +0100204 // don't paint outside of the box
205 gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
206
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100207 // Next, render the background resource (if it exists)
208 if (mBackground && mBackground->GetResource())
209 {
210 int mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
211 int mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
212 gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
213 }
214
that9876ac32015-02-15 21:40:59 +0100215 // This tells us how many full lines we can actually render
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100216 size_t lines = GetDisplayItemCount();
217
218 size_t listSize = GetItemCount();
219 int listW = mRenderW;
220
221 if (listSize <= lines) {
222 hasScroll = false;
223 scrollingSpeed = 0;
224 lines = listSize;
225 y_offset = 0;
226 } else {
227 hasScroll = true;
228 listW -= mFastScrollW; // space for fast scroll
229 lines++;
230 if (lines < listSize)
231 lines++;
232 }
233
234 void* fontResource = NULL;
235 if (mFont) fontResource = mFont->GetResource();
236
237 int yPos = mRenderY + mHeaderH + y_offset;
238 int fontOffsetY = (int)((actualItemHeight - mFontHeight) / 2);
239
240 // render all visible items
241 for (size_t line = 0; line < lines; line++)
242 {
243 size_t itemindex = line + firstDisplayedItem;
244 if (itemindex >= listSize)
245 break;
246
247 // get item data
thatf6ed8fc2015-02-14 20:23:16 +0100248 ImageResource* icon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100249 std::string label;
250 if (GetListItem(itemindex, icon, label))
251 break;
252
253 if (hasHighlightColor && itemindex == selectedItem) {
254 // Highlight the item background of the selected item
that9876ac32015-02-15 21:40:59 +0100255 gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
256 gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100257 }
258
that9876ac32015-02-15 21:40:59 +0100259 if (itemindex == selectedItem) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100260 // Use the highlight color for the font
that9876ac32015-02-15 21:40:59 +0100261 gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100262 } else {
263 // Set the color for the font
that9876ac32015-02-15 21:40:59 +0100264 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100265 }
266
that9876ac32015-02-15 21:40:59 +0100267 // render icon
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100268 if (icon && icon->GetResource()) {
thatf6ed8fc2015-02-14 20:23:16 +0100269 int currentIconHeight = icon->GetHeight();
270 int currentIconWidth = icon->GetWidth();
that9876ac32015-02-15 21:40:59 +0100271 int currentIconOffsetY = (actualItemHeight - currentIconHeight) / 2;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100272 int currentIconOffsetX = (maxIconWidth - currentIconWidth) / 2;
that9876ac32015-02-15 21:40:59 +0100273 int image_y = (yPos + currentIconOffsetY);
274 gr_blit(icon->GetResource(), 0, 0, currentIconWidth, currentIconHeight, mRenderX + currentIconOffsetX, image_y);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100275 }
276
that9876ac32015-02-15 21:40:59 +0100277 // render label text
278 gr_textEx(mRenderX + maxIconWidth + 5, yPos + fontOffsetY, label.c_str(), fontResource);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100279
280 // Add the separator
that9876ac32015-02-15 21:40:59 +0100281 gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
282 gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100283
284 // Move the yPos
285 yPos += actualItemHeight;
286 }
287
thata9998212015-02-19 22:51:24 +0100288 // Render the Header
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100289 yPos = mRenderY;
that9876ac32015-02-15 21:40:59 +0100290 if (mHeaderH > 0) {
291 // First step, fill background
292 gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
293 gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
294
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100295 int mIconOffsetX = 0;
296
297 // render the icon if it exists
thatf6ed8fc2015-02-14 20:23:16 +0100298 ImageResource* headerIcon = mHeaderIcon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100299 if (headerIcon && headerIcon->GetResource())
300 {
301 gr_blit(headerIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
302 mIconOffsetX = maxIconWidth;
303 }
304
305 // render the text
that9876ac32015-02-15 21:40:59 +0100306 gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
307 gr_textEx(mRenderX + mIconOffsetX + 5, yPos + (int)((mHeaderH - mFontHeight) / 2), mLastHeaderValue.c_str(), fontResource);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100308
309 // Add the separator
that9876ac32015-02-15 21:40:59 +0100310 gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100311 gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
312 }
313
that9876ac32015-02-15 21:40:59 +0100314 // reset clipping
315 gr_noclip();
thata9998212015-02-19 22:51:24 +0100316
317 // render fast scroll
318 if (hasScroll) {
319 int fWidth = mRenderW - listW;
320 int fHeight = mRenderH - mHeaderH;
321 int centerX = listW + mRenderX + fWidth / 2;
322
323 // first determine the total list height and where we are in the list
324 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
325 int topPos = firstDisplayedItem * actualItemHeight - y_offset;
326
327 // now scale it proportionally to the scrollbar height
328 int boxH = fHeight * fHeight / totalHeight; // proportional height of the displayed portion
329 boxH = std::max(boxH, mFastScrollRectH); // but keep a minimum height
330 int boxY = (fHeight - boxH) * topPos / (totalHeight - fHeight); // pixels relative to top of list
331 int boxW = mFastScrollRectW;
332
333 int x = centerX - boxW / 2;
334 int y = mRenderY + mHeaderH + boxY;
335
336 // line above and below box (needs to be split because box can be transparent)
337 gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
338 gr_fill(centerX - mFastScrollLineW / 2, mRenderY + mHeaderH, mFastScrollLineW, boxY);
339 gr_fill(centerX - mFastScrollLineW / 2, y + boxH, mFastScrollLineW, fHeight - boxY - boxH);
340
341 // box
342 gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
343 gr_fill(x, y, boxW, boxH);
344
345 mFastScrollRectCurrentY = boxY;
346 mFastScrollRectCurrentH = boxH;
347 }
348 mUpdate = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100349 return 0;
350}
351
352int GUIScrollList::Update(void)
353{
354 if(!isConditionTrue())
355 return 0;
356
357 if (!mHeaderIsStatic) {
358 std::string newValue = gui_parse_text(mHeaderText);
359 if (mLastHeaderValue != newValue) {
360 mLastHeaderValue = newValue;
361 mUpdate = 1;
362 }
363 }
364
365 // Handle kinetic scrolling
366 int maxScrollDistance = actualItemHeight * SCROLLING_SPEED_LIMIT;
that10ec0172015-02-15 23:52:28 +0100367 int oldScrollingSpeed = scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100368 if (scrollingSpeed == 0) {
369 // Do nothing
370 return 0;
371 } else if (scrollingSpeed > 0) {
372 if (scrollingSpeed < maxScrollDistance)
373 y_offset += scrollingSpeed;
374 else
375 y_offset += maxScrollDistance;
that10ec0172015-02-15 23:52:28 +0100376 scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
377 if (scrollingSpeed == oldScrollingSpeed)
378 --scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100379 } else if (scrollingSpeed < 0) {
380 if (abs(scrollingSpeed) < maxScrollDistance)
381 y_offset += scrollingSpeed;
382 else
383 y_offset -= maxScrollDistance;
that10ec0172015-02-15 23:52:28 +0100384 scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
385 if (scrollingSpeed == oldScrollingSpeed)
386 ++scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100387 }
388 if (abs(scrollingSpeed) < SCROLLING_FLOOR)
389 scrollingSpeed = 0;
390 HandleScrolling();
391 mUpdate = 1;
392
393 return 0;
394}
395
396size_t GUIScrollList::HitTestItem(int x, int y)
397{
398 // We only care about y position
399 if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
400 return NO_ITEM;
401
402 int startSelection = (y - mRenderY - mHeaderH);
403
404 // Locate the correct item
405 size_t actualSelection = firstDisplayedItem;
406 int selectY = y_offset;
407 while (selectY + actualItemHeight < startSelection) {
408 selectY += actualItemHeight;
409 actualSelection++;
410 }
411
412 if (actualSelection < GetItemCount())
413 return actualSelection;
414
415 return NO_ITEM;
416}
417
418int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
419{
420 if(!isConditionTrue())
421 return -1;
422
423 switch (state)
424 {
425 case TOUCH_START:
thata9998212015-02-19 22:51:24 +0100426 if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100427 fastScroll = 1; // Initial touch is in the fast scroll region
thata9998212015-02-19 22:51:24 +0100428 int fastScrollBoxTop = mFastScrollRectCurrentY + mRenderY + mHeaderH;
429 int fastScrollBoxBottom = fastScrollBoxTop + mFastScrollRectCurrentH;
430 if (y >= fastScrollBoxTop && y < fastScrollBoxBottom)
431 // user grabbed the fastscroll bar
432 // try to keep the initially touched part of the scrollbar under the finger
433 mFastScrollRectTouchY = y - fastScrollBoxTop;
434 else
435 // user tapped outside the fastscroll bar
436 // center fastscroll rect on the initial touch position
437 mFastScrollRectTouchY = mFastScrollRectCurrentH / 2;
438 }
439
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100440 if (scrollingSpeed != 0) {
441 selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
442 scrollingSpeed = 0; // stop scrolling on a new touch
443 } else if (!fastScroll) {
444 // find out which item the user touched
445 selectedItem = HitTestItem(x, y);
446 }
447 if (selectedItem != NO_ITEM)
448 mUpdate = 1;
449 lastY = last2Y = y;
450 break;
451
452 case TOUCH_DRAG:
453 if (fastScroll)
454 {
thata9998212015-02-19 22:51:24 +0100455 int relY = y - mRenderY - mHeaderH; // touch position relative to window
456 int windowH = mRenderH - mHeaderH;
457 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100458
thata9998212015-02-19 22:51:24 +0100459 // calculate new top position of the fastscroll bar relative to window
460 int newY = relY - mFastScrollRectTouchY;
461 // keep it fully inside the list
462 newY = std::min(std::max(newY, 0), windowH - mFastScrollRectCurrentH);
463
464 // now compute the new scroll position for the list
465 int newTopPos = newY * (totalHeight - windowH) / (windowH - mFastScrollRectCurrentH); // new top pixel of list
466 newTopPos = std::min(newTopPos, totalHeight - windowH); // account for rounding errors
467 firstDisplayedItem = newTopPos / actualItemHeight;
468 y_offset = - newTopPos % actualItemHeight;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100469
470 selectedItem = NO_ITEM;
471 mUpdate = 1;
472 scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
473 break;
474 }
475
476 // Provide some debounce on initial touches
477 if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
478 mUpdate = 1;
479 break;
480 }
481
482 selectedItem = NO_ITEM; // nothing is selected because we dragged too far
483 // Handle scrolling
484 if (hasScroll) {
485 y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
486 last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
487 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
488
489 HandleScrolling();
490 } else
491 y_offset = 0;
492 mUpdate = 1;
493 break;
494
495 case TOUCH_RELEASE:
thata9998212015-02-19 22:51:24 +0100496 if (fastScroll)
497 mUpdate = 1; // get rid of touch effects on the fastscroll bar
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100498 fastScroll = 0;
499 if (selectedItem != NO_ITEM) {
500 // We've selected an item!
501 NotifySelect(selectedItem);
502 mUpdate = 1;
503
504 DataManager::Vibrate("tw_button_vibrate");
505 selectedItem = NO_ITEM;
506 } else {
507 // Start kinetic scrolling
508 scrollingSpeed = lastY - last2Y;
that10ec0172015-02-15 23:52:28 +0100509 if (abs(scrollingSpeed) < touchDebounce)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100510 scrollingSpeed = 0;
511 }
512 case TOUCH_REPEAT:
513 case TOUCH_HOLD:
514 break;
515 }
516 return 0;
517}
518
519void GUIScrollList::HandleScrolling()
520{
521 // handle dragging downward, scrolling upward
522 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
523 while(firstDisplayedItem && y_offset > 0) {
524 firstDisplayedItem--;
525 y_offset -= actualItemHeight;
526 }
thatde72b6d2015-02-08 08:55:00 +0100527 if (firstDisplayedItem == 0 && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100528 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
thatde72b6d2015-02-08 08:55:00 +0100529 scrollingSpeed = 0; // stop kinetic scrolling
530 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100531
532 // handle dragging upward, scrolling downward
533 int totalSize = GetItemCount();
534 int lines = GetDisplayItemCount(); // number of full lines our list can display at once
535 int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
536
537 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
538 while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
539 firstDisplayedItem++;
540 y_offset += actualItemHeight;
541 }
542 // Check if we dragged too far, set the list at the bottom and adjust offset as needed
543 if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
544 firstDisplayedItem = totalSize - lines - 1;
545 y_offset = bottom_offset;
thatde72b6d2015-02-08 08:55:00 +0100546 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100547 } else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
548 firstDisplayedItem = totalSize - lines;
549 y_offset = 0;
thatde72b6d2015-02-08 08:55:00 +0100550 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100551 }
552}
553
554int GUIScrollList::GetDisplayItemCount()
555{
556 return (mRenderH - mHeaderH) / (actualItemHeight);
557}
558
559int GUIScrollList::GetDisplayRemainder()
560{
561 return (mRenderH - mHeaderH) % actualItemHeight;
562}
563
564int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
565{
566 GUIObject::NotifyVarChange(varName, value);
567
568 if(!isConditionTrue())
569 return 0;
570
571 if (!mHeaderIsStatic) {
572 std::string newValue = gui_parse_text(mHeaderText);
573 if (mLastHeaderValue != newValue) {
574 mLastHeaderValue = newValue;
575 firstDisplayedItem = 0;
576 y_offset = 0;
577 scrollingSpeed = 0; // stop kinetic scrolling on variable changes
578 mUpdate = 1;
579 }
580 }
581 return 0;
582}
583
584int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
585{
586 mRenderX = x;
587 mRenderY = y;
588 if (w || h)
589 {
590 mRenderW = w;
591 mRenderH = h;
592 }
593 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
594 mUpdate = 1;
595 return 0;
596}
597
598void GUIScrollList::SetPageFocus(int inFocus)
599{
600 if (inFocus) {
601 NotifyVarChange("", ""); // This forces a check for the header text
602 scrollingSpeed = 0; // stop kinetic scrolling on page changes
603 mUpdate = 1;
604 }
605}