blob: 822fa7644fec5bb6c57ed6aef0bdc95356dc948a [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
thatae4b12e2015-02-06 00:23:05 +010030const int SCROLLING_SPEED_DECREMENT = 6; // friction
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010031const int SCROLLING_FLOOR = 10; // minimum pixels for scrolling to start or stop
thatae4b12e2015-02-06 00:23:05 +010032const int SCROLLING_MULTIPLIER = 1; // initial speed of kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010033const float SCROLLING_SPEED_LIMIT = 2.5; // maximum number of items to scroll per update
34
35GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
36{
37 xml_attribute<>* attr;
38 xml_node<>* child;
39 int header_separator_color_specified = 0, header_separator_height_specified = 0, header_text_color_specified = 0, header_background_color_specified = 0;
40
41 firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
42 maxIconWidth = maxIconHeight = mHeaderIconHeight = mHeaderIconWidth = 0;
43 mHeaderSeparatorH = mHeaderIsStatic = mHeaderH = actualItemHeight = 0;
thatf6ed8fc2015-02-14 20:23:16 +010044 mBackground = mHeaderIcon = NULL;
45 mFont = NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010046 mBackgroundW = mBackgroundH = 0;
47 mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
48 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;
60 hasFontHighlightColor = false;
61 selectedItem = NO_ITEM;
62
63 // Load header text
64 child = node->first_node("header");
65 if (child)
66 {
thatf6ed8fc2015-02-14 20:23:16 +010067 mHeaderIcon = LoadAttrImage(child, "icon");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010068
69 attr = child->first_attribute("background");
70 if (attr)
71 {
72 std::string color = attr->value();
73 ConvertStrToColor(color, &mHeaderBackgroundColor);
74 header_background_color_specified = -1;
75 }
76 attr = child->first_attribute("textcolor");
77 if (attr)
78 {
79 std::string color = attr->value();
80 ConvertStrToColor(color, &mHeaderFontColor);
81 header_text_color_specified = -1;
82 }
83 attr = child->first_attribute("separatorcolor");
84 if (attr)
85 {
86 std::string color = attr->value();
87 ConvertStrToColor(color, &mHeaderSeparatorColor);
88 header_separator_color_specified = -1;
89 }
90 attr = child->first_attribute("separatorheight");
91 if (attr) {
92 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -060093 mHeaderSeparatorH = scale_theme_y(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010094 header_separator_height_specified = -1;
95 }
96 }
97 child = node->first_node("text");
98 if (child) mHeaderText = child->value();
99
100 memset(&mHighlightColor, 0, sizeof(COLOR));
101 child = node->first_node("highlight");
102 if (child) {
103 attr = child->first_attribute("color");
104 if (attr) {
105 hasHighlightColor = true;
106 std::string color = attr->value();
107 ConvertStrToColor(color, &mHighlightColor);
108 }
109 }
110
111 // Simple way to check for static state
112 mLastHeaderValue = gui_parse_text(mHeaderText);
113 if (mLastHeaderValue != mHeaderText)
114 mHeaderIsStatic = 0;
115 else
116 mHeaderIsStatic = -1;
117
118 child = node->first_node("background");
119 if (child)
120 {
thatf6ed8fc2015-02-14 20:23:16 +0100121 mBackground = LoadAttrImage(child, "resource");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100122 attr = child->first_attribute("color");
123 if (attr)
124 {
125 std::string color = attr->value();
126 ConvertStrToColor(color, &mBackgroundColor);
127 if (!header_background_color_specified)
128 ConvertStrToColor(color, &mHeaderBackgroundColor);
129 }
130 }
131
132 // Load the placement
133 LoadPlacement(node->first_node("placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
134 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
135
136 // Load the font, and possibly override the color
137 child = node->first_node("font");
138 if (child)
139 {
thatf6ed8fc2015-02-14 20:23:16 +0100140 mFont = LoadAttrFont(child, "resource");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100141
142 attr = child->first_attribute("color");
143 if (attr)
144 {
145 std::string color = attr->value();
146 ConvertStrToColor(color, &mFontColor);
147 if (!header_text_color_specified)
148 ConvertStrToColor(color, &mHeaderFontColor);
149 }
150
151 attr = child->first_attribute("spacing");
152 if (attr) {
153 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600154 mItemSpacing = scale_theme_y(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100155 }
156
157 attr = child->first_attribute("highlightcolor");
158 memset(&mFontHighlightColor, 0, sizeof(COLOR));
159 if (attr)
160 {
161 std::string color = attr->value();
162 ConvertStrToColor(color, &mFontHighlightColor);
163 hasFontHighlightColor = true;
164 }
165 }
166
167 // Load the separator if it exists
168 child = node->first_node("separator");
169 if (child)
170 {
171 attr = child->first_attribute("color");
172 if (attr)
173 {
174 std::string color = attr->value();
175 ConvertStrToColor(color, &mSeparatorColor);
176 if (!header_separator_color_specified)
177 ConvertStrToColor(color, &mHeaderSeparatorColor);
178 }
179
180 attr = child->first_attribute("height");
181 if (attr) {
182 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600183 mSeparatorH = scale_theme_y(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100184 if (!header_separator_height_specified)
185 mHeaderSeparatorH = mSeparatorH;
186 }
187 }
188
189 // Fast scroll colors
190 child = node->first_node("fastscroll");
191 if (child)
192 {
193 attr = child->first_attribute("linecolor");
194 if(attr)
195 ConvertStrToColor(attr->value(), &mFastScrollLineColor);
196
197 attr = child->first_attribute("rectcolor");
198 if(attr)
199 ConvertStrToColor(attr->value(), &mFastScrollRectColor);
200
201 attr = child->first_attribute("w");
202 if (attr) {
203 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600204 mFastScrollW = scale_theme_x(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100205 }
206
207 attr = child->first_attribute("linew");
208 if (attr) {
209 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600210 mFastScrollLineW = scale_theme_x(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100211 }
212
213 attr = child->first_attribute("rectw");
214 if (attr) {
215 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600216 mFastScrollRectW = scale_theme_x(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100217 }
218
219 attr = child->first_attribute("recth");
220 if (attr) {
221 string parsevalue = gui_parse_text(attr->value());
Ethan Yonker63e414f2015-02-06 15:44:39 -0600222 mFastScrollRectH = scale_theme_y(atoi(parsevalue.c_str()));
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100223 }
224 }
225
226 // Retrieve the line height
thatf6ed8fc2015-02-14 20:23:16 +0100227 mFontHeight = mFont->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100228 mHeaderH = mFontHeight;
229
230 if (mHeaderIcon && mHeaderIcon->GetResource())
231 {
thatf6ed8fc2015-02-14 20:23:16 +0100232 mHeaderIconWidth = mHeaderIcon->GetWidth();
233 mHeaderIconHeight = mHeaderIcon->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100234 if (mHeaderIconHeight > mHeaderH)
235 mHeaderH = mHeaderIconHeight;
236 if (mHeaderIconWidth > maxIconWidth)
237 maxIconWidth = mHeaderIconWidth;
238 }
239
240 mHeaderH += mItemSpacing + mHeaderSeparatorH;
241 actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;
242 if (mHeaderH < actualItemHeight)
243 mHeaderH = actualItemHeight;
244
245 if (actualItemHeight / 3 > 6)
246 touchDebounce = actualItemHeight / 3;
247
248 if (mBackground && mBackground->GetResource())
249 {
thatf6ed8fc2015-02-14 20:23:16 +0100250 mBackgroundW = mBackground->GetWidth();
251 mBackgroundH = mBackground->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100252 }
253}
254
255GUIScrollList::~GUIScrollList()
256{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100257}
258
259void GUIScrollList::SetMaxIconSize(int w, int h)
260{
261 if (w > maxIconWidth)
262 maxIconWidth = w;
263 if (h > maxIconHeight)
264 maxIconHeight = h;
265 if (maxIconHeight > mFontHeight) {
266 actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
267 if (actualItemHeight > mHeaderH)
268 mHeaderH = actualItemHeight;
269 }
270}
271
272void GUIScrollList::SetVisibleListLocation(size_t list_index)
273{
274 // This will make sure that the item indicated by list_index is visible on the screen
275 size_t lines = GetDisplayItemCount(), listSize = GetItemCount();
276
277 if (list_index <= (unsigned)firstDisplayedItem) {
278 // list_index is above the currently displayed items, put the selected item at the very top
279 firstDisplayedItem = list_index;
280 y_offset = 0;
281 } else if (list_index >= firstDisplayedItem + lines) {
282 // list_index is below the currently displayed items, put the selected item at the very bottom
283 firstDisplayedItem = list_index - lines + 1;
284 if (GetDisplayRemainder() != 0) {
285 // There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
286 firstDisplayedItem--;
287 y_offset = GetDisplayRemainder() - actualItemHeight;
288 } else {
289 // There's no partial row so zero out the offset
290 y_offset = 0;
291 }
292 }
293 scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
294 mUpdate = 1;
295}
296
297int GUIScrollList::Render(void)
298{
299 if(!isConditionTrue())
300 return 0;
301
302 // First step, fill background
303 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255);
304 gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
305
306 // Next, render the background resource (if it exists)
307 if (mBackground && mBackground->GetResource())
308 {
309 int mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
310 int mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
311 gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
312 }
313
314 // This tells us how many lines we can actually render
315 size_t lines = GetDisplayItemCount();
316
317 size_t listSize = GetItemCount();
318 int listW = mRenderW;
319
320 if (listSize <= lines) {
321 hasScroll = false;
322 scrollingSpeed = 0;
323 lines = listSize;
324 y_offset = 0;
325 } else {
326 hasScroll = true;
327 listW -= mFastScrollW; // space for fast scroll
328 lines++;
329 if (lines < listSize)
330 lines++;
331 }
332
333 void* fontResource = NULL;
334 if (mFont) fontResource = mFont->GetResource();
335
336 int yPos = mRenderY + mHeaderH + y_offset;
337 int fontOffsetY = (int)((actualItemHeight - mFontHeight) / 2);
338
339 // render all visible items
340 for (size_t line = 0; line < lines; line++)
341 {
342 size_t itemindex = line + firstDisplayedItem;
343 if (itemindex >= listSize)
344 break;
345
346 // get item data
thatf6ed8fc2015-02-14 20:23:16 +0100347 ImageResource* icon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100348 std::string label;
349 if (GetListItem(itemindex, icon, label))
350 break;
351
352 if (hasHighlightColor && itemindex == selectedItem) {
353 // Highlight the item background of the selected item
354 gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, 255);
355 int HighlightHeight = actualItemHeight;
356 if (yPos + HighlightHeight > mRenderY + mRenderH) {
357 HighlightHeight = mRenderY + mRenderH - yPos;
358 }
359 gr_fill(mRenderX, yPos, mRenderW, HighlightHeight);
360 }
361
362 if (hasFontHighlightColor && itemindex == selectedItem) {
363 // Use the highlight color for the font
364 gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, 255);
365 } else {
366 // Set the color for the font
367 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, 255);
368 }
369
370 if (icon && icon->GetResource()) {
thatf6ed8fc2015-02-14 20:23:16 +0100371 int currentIconHeight = icon->GetHeight();
372 int currentIconWidth = icon->GetWidth();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100373 int currentIconOffsetY = (int)((actualItemHeight - currentIconHeight) / 2);
374 int currentIconOffsetX = (maxIconWidth - currentIconWidth) / 2;
375 int rect_y = 0, image_y = (yPos + currentIconOffsetY);
376 if (image_y + currentIconHeight > mRenderY + mRenderH)
377 rect_y = mRenderY + mRenderH - image_y;
378 else
379 rect_y = currentIconHeight;
380 gr_blit(icon->GetResource(), 0, 0, currentIconWidth, rect_y, mRenderX + currentIconOffsetX, image_y);
381 }
382
383 gr_textExWH(mRenderX + maxIconWidth + 5, yPos + fontOffsetY, label.c_str(), fontResource, mRenderX + listW, mRenderY + mRenderH);
384
385 // Add the separator
386 if (yPos + actualItemHeight < mRenderH + mRenderY) {
387 gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, 255);
388 gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
389 }
390
391 // Move the yPos
392 yPos += actualItemHeight;
393 }
394
395 // Render the Header (last so that it overwrites the top most row for per pixel scrolling)
396 // First step, fill background
397 gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, 255);
398 gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
399
400 // Now, we need the header (icon + text)
401 yPos = mRenderY;
402 {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100403 int mIconOffsetX = 0;
404
405 // render the icon if it exists
thatf6ed8fc2015-02-14 20:23:16 +0100406 ImageResource* headerIcon = mHeaderIcon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100407 if (headerIcon && headerIcon->GetResource())
408 {
409 gr_blit(headerIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
410 mIconOffsetX = maxIconWidth;
411 }
412
413 // render the text
414 gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, 255);
415 gr_textExWH(mRenderX + mIconOffsetX + 5, yPos + (int)((mHeaderH - mFontHeight) / 2), mLastHeaderValue.c_str(), fontResource, mRenderX + mRenderW, mRenderY + mRenderH);
416
417 // Add the separator
418 gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, 255);
419 gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
420 }
421
422 // render fast scroll
423 lines = GetDisplayItemCount();
424 if (hasScroll) {
425 int startX = listW + mRenderX;
426 int fWidth = mRenderW - listW;
427 int fHeight = mRenderH - mHeaderH;
428
429 // line
430 gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, 255);
431 gr_fill(startX + fWidth/2, mRenderY + mHeaderH, mFastScrollLineW, mRenderH - mHeaderH);
432
433 // rect
434 int pct = 0;
435 if (GetDisplayRemainder() != 0) {
436 // Properly handle the percentage if a partial line is present
437 int partial_line_size = actualItemHeight - GetDisplayRemainder();
438 pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-((lines + 1)*actualItemHeight) + partial_line_size);
439 } else {
440 pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-lines*actualItemHeight);
441 }
442 int mFastScrollRectX = startX + (fWidth - mFastScrollRectW)/2;
443 int mFastScrollRectY = mRenderY+mHeaderH + ((fHeight - mFastScrollRectH)*pct)/100;
444
445 gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, 255);
446 gr_fill(mFastScrollRectX, mFastScrollRectY, mFastScrollRectW, mFastScrollRectH);
447 }
448 mUpdate = 0;
449 return 0;
450}
451
452int GUIScrollList::Update(void)
453{
454 if(!isConditionTrue())
455 return 0;
456
457 if (!mHeaderIsStatic) {
458 std::string newValue = gui_parse_text(mHeaderText);
459 if (mLastHeaderValue != newValue) {
460 mLastHeaderValue = newValue;
461 mUpdate = 1;
462 }
463 }
464
465 // Handle kinetic scrolling
466 int maxScrollDistance = actualItemHeight * SCROLLING_SPEED_LIMIT;
467 if (scrollingSpeed == 0) {
468 // Do nothing
469 return 0;
470 } else if (scrollingSpeed > 0) {
471 if (scrollingSpeed < maxScrollDistance)
472 y_offset += scrollingSpeed;
473 else
474 y_offset += maxScrollDistance;
475 scrollingSpeed -= SCROLLING_SPEED_DECREMENT;
476 } else if (scrollingSpeed < 0) {
477 if (abs(scrollingSpeed) < maxScrollDistance)
478 y_offset += scrollingSpeed;
479 else
480 y_offset -= maxScrollDistance;
481 scrollingSpeed += SCROLLING_SPEED_DECREMENT;
482 }
483 if (abs(scrollingSpeed) < SCROLLING_FLOOR)
484 scrollingSpeed = 0;
485 HandleScrolling();
486 mUpdate = 1;
487
488 return 0;
489}
490
491size_t GUIScrollList::HitTestItem(int x, int y)
492{
493 // We only care about y position
494 if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
495 return NO_ITEM;
496
497 int startSelection = (y - mRenderY - mHeaderH);
498
499 // Locate the correct item
500 size_t actualSelection = firstDisplayedItem;
501 int selectY = y_offset;
502 while (selectY + actualItemHeight < startSelection) {
503 selectY += actualItemHeight;
504 actualSelection++;
505 }
506
507 if (actualSelection < GetItemCount())
508 return actualSelection;
509
510 return NO_ITEM;
511}
512
513int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
514{
515 if(!isConditionTrue())
516 return -1;
517
518 switch (state)
519 {
520 case TOUCH_START:
521 if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW)
522 fastScroll = 1; // Initial touch is in the fast scroll region
523 if (scrollingSpeed != 0) {
524 selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
525 scrollingSpeed = 0; // stop scrolling on a new touch
526 } else if (!fastScroll) {
527 // find out which item the user touched
528 selectedItem = HitTestItem(x, y);
529 }
530 if (selectedItem != NO_ITEM)
531 mUpdate = 1;
532 lastY = last2Y = y;
533 break;
534
535 case TOUCH_DRAG:
536 if (fastScroll)
537 {
538 int pct = ((y-mRenderY-mHeaderH)*100)/(mRenderH-mHeaderH);
539 int totalSize = GetItemCount();
540 int lines = GetDisplayItemCount();
541
542 float l = float((totalSize-lines)*pct)/100;
543 if(l + lines >= totalSize)
544 {
545 firstDisplayedItem = totalSize - lines;
546 if (GetDisplayRemainder() != 0) {
547 // There's a partial row displayed, set the scrolling offset so that the last item really is at the very bottom
548 firstDisplayedItem--;
549 y_offset = GetDisplayRemainder() - actualItemHeight;
550 } else {
551 // There's no partial row so zero out the offset
552 y_offset = 0;
553 }
554 }
555 else
556 {
557 if (l < 0)
558 l = 0;
559 firstDisplayedItem = l;
560 y_offset = -(l - int(l))*actualItemHeight;
561 if (GetDisplayRemainder() != 0) {
562 // There's a partial row displayed, make sure y_offset doesn't go past the max
563 if (firstDisplayedItem == totalSize - lines - 1 && y_offset < GetDisplayRemainder() - actualItemHeight)
564 y_offset = GetDisplayRemainder() - actualItemHeight;
565 } else if (firstDisplayedItem == totalSize - lines)
566 y_offset = 0;
567 }
568
569 selectedItem = NO_ITEM;
570 mUpdate = 1;
571 scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
572 break;
573 }
574
575 // Provide some debounce on initial touches
576 if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
577 mUpdate = 1;
578 break;
579 }
580
581 selectedItem = NO_ITEM; // nothing is selected because we dragged too far
582 // Handle scrolling
583 if (hasScroll) {
584 y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
585 last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
586 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
587
588 HandleScrolling();
589 } else
590 y_offset = 0;
591 mUpdate = 1;
592 break;
593
594 case TOUCH_RELEASE:
595 fastScroll = 0;
596 if (selectedItem != NO_ITEM) {
597 // We've selected an item!
598 NotifySelect(selectedItem);
599 mUpdate = 1;
600
601 DataManager::Vibrate("tw_button_vibrate");
602 selectedItem = NO_ITEM;
603 } else {
604 // Start kinetic scrolling
605 scrollingSpeed = lastY - last2Y;
606 if (abs(scrollingSpeed) > SCROLLING_FLOOR)
607 scrollingSpeed *= SCROLLING_MULTIPLIER;
608 else
609 scrollingSpeed = 0;
610 }
611 case TOUCH_REPEAT:
612 case TOUCH_HOLD:
613 break;
614 }
615 return 0;
616}
617
618void GUIScrollList::HandleScrolling()
619{
620 // handle dragging downward, scrolling upward
621 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
622 while(firstDisplayedItem && y_offset > 0) {
623 firstDisplayedItem--;
624 y_offset -= actualItemHeight;
625 }
thatde72b6d2015-02-08 08:55:00 +0100626 if (firstDisplayedItem == 0 && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100627 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 +0100628 scrollingSpeed = 0; // stop kinetic scrolling
629 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100630
631 // handle dragging upward, scrolling downward
632 int totalSize = GetItemCount();
633 int lines = GetDisplayItemCount(); // number of full lines our list can display at once
634 int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
635
636 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
637 while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
638 firstDisplayedItem++;
639 y_offset += actualItemHeight;
640 }
641 // Check if we dragged too far, set the list at the bottom and adjust offset as needed
642 if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
643 firstDisplayedItem = totalSize - lines - 1;
644 y_offset = bottom_offset;
thatde72b6d2015-02-08 08:55:00 +0100645 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100646 } else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
647 firstDisplayedItem = totalSize - lines;
648 y_offset = 0;
thatde72b6d2015-02-08 08:55:00 +0100649 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100650 }
651}
652
653int GUIScrollList::GetDisplayItemCount()
654{
655 return (mRenderH - mHeaderH) / (actualItemHeight);
656}
657
658int GUIScrollList::GetDisplayRemainder()
659{
660 return (mRenderH - mHeaderH) % actualItemHeight;
661}
662
663int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
664{
665 GUIObject::NotifyVarChange(varName, value);
666
667 if(!isConditionTrue())
668 return 0;
669
670 if (!mHeaderIsStatic) {
671 std::string newValue = gui_parse_text(mHeaderText);
672 if (mLastHeaderValue != newValue) {
673 mLastHeaderValue = newValue;
674 firstDisplayedItem = 0;
675 y_offset = 0;
676 scrollingSpeed = 0; // stop kinetic scrolling on variable changes
677 mUpdate = 1;
678 }
679 }
680 return 0;
681}
682
683int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
684{
685 mRenderX = x;
686 mRenderY = y;
687 if (w || h)
688 {
689 mRenderW = w;
690 mRenderH = h;
691 }
692 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
693 mUpdate = 1;
694 return 0;
695}
696
697void GUIScrollList::SetPageFocus(int inFocus)
698{
699 if (inFocus) {
700 NotifyVarChange("", ""); // This forces a check for the header text
701 scrollingSpeed = 0; // stop kinetic scrolling on page changes
702 mUpdate = 1;
703 }
704}