blob: 05141b1cf67e17b72b4f8b239563b6880811f681 [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;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010039
40 firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
41 maxIconWidth = maxIconHeight = mHeaderIconHeight = mHeaderIconWidth = 0;
that9876ac32015-02-15 21:40:59 +010042 mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
43 mHeaderIsStatic = false;
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;
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
69 memset(&mHighlightColor, 0, sizeof(COLOR));
70 child = node->first_node("highlight");
71 if (child) {
72 attr = child->first_attribute("color");
73 if (attr) {
74 hasHighlightColor = true;
75 std::string color = attr->value();
76 ConvertStrToColor(color, &mHighlightColor);
77 }
78 }
79
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010080 child = node->first_node("background");
81 if (child)
82 {
thatf6ed8fc2015-02-14 20:23:16 +010083 mBackground = LoadAttrImage(child, "resource");
that9876ac32015-02-15 21:40:59 +010084 mBackgroundColor = LoadAttrColor(child, "color");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010085 }
86
87 // Load the placement
88 LoadPlacement(node->first_node("placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
89 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
90
91 // Load the font, and possibly override the color
92 child = node->first_node("font");
93 if (child)
94 {
thatf6ed8fc2015-02-14 20:23:16 +010095 mFont = LoadAttrFont(child, "resource");
that9876ac32015-02-15 21:40:59 +010096 mFontColor = LoadAttrColor(child, "color");
97 mFontHighlightColor = LoadAttrColor(child, "highlightcolor", mFontColor);
98 mItemSpacing = LoadAttrIntScaleY(child, "spacing");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010099 }
100
101 // Load the separator if it exists
102 child = node->first_node("separator");
103 if (child)
104 {
that9876ac32015-02-15 21:40:59 +0100105 mSeparatorColor = LoadAttrColor(child, "color");
106 mSeparatorH = LoadAttrIntScaleY(child, "height");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100107 }
108
that9876ac32015-02-15 21:40:59 +0100109 // Fast scroll
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100110 child = node->first_node("fastscroll");
111 if (child)
112 {
that9876ac32015-02-15 21:40:59 +0100113 mFastScrollLineColor = LoadAttrColor(child, "linecolor");
114 mFastScrollRectColor = LoadAttrColor(child, "rectcolor");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100115
that9876ac32015-02-15 21:40:59 +0100116 mFastScrollW = LoadAttrIntScaleX(child, "w");
117 mFastScrollLineW = LoadAttrIntScaleX(child, "linew");
118 mFastScrollRectW = LoadAttrIntScaleX(child, "rectw");
119 mFastScrollRectH = LoadAttrIntScaleY(child, "recth");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100120 }
121
122 // Retrieve the line height
thatf6ed8fc2015-02-14 20:23:16 +0100123 mFontHeight = mFont->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100124 actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100125
126 // Load the header if it exists
127 child = node->first_node("header");
128 if (child)
129 {
130 mHeaderH = mFontHeight;
131 mHeaderIcon = LoadAttrImage(child, "icon");
132 mHeaderBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
133 mHeaderFontColor = LoadAttrColor(child, "textcolor", mFontColor);
134 mHeaderSeparatorColor = LoadAttrColor(child, "separatorcolor", mSeparatorColor);
135 mHeaderSeparatorH = LoadAttrIntScaleY(child, "separatorheight", mSeparatorH);
136
137 if (mHeaderIcon && mHeaderIcon->GetResource())
138 {
139 mHeaderIconWidth = mHeaderIcon->GetWidth();
140 mHeaderIconHeight = mHeaderIcon->GetHeight();
141 if (mHeaderIconHeight > mHeaderH)
142 mHeaderH = mHeaderIconHeight;
143 if (mHeaderIconWidth > maxIconWidth)
144 maxIconWidth = mHeaderIconWidth;
145 }
146
147 mHeaderH += mItemSpacing + mHeaderSeparatorH;
148 if (mHeaderH < actualItemHeight)
149 mHeaderH = actualItemHeight;
150 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100151
152 if (actualItemHeight / 3 > 6)
153 touchDebounce = actualItemHeight / 3;
154
155 if (mBackground && mBackground->GetResource())
156 {
thatf6ed8fc2015-02-14 20:23:16 +0100157 mBackgroundW = mBackground->GetWidth();
158 mBackgroundH = mBackground->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100159 }
160}
161
162GUIScrollList::~GUIScrollList()
163{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100164}
165
166void GUIScrollList::SetMaxIconSize(int w, int h)
167{
168 if (w > maxIconWidth)
169 maxIconWidth = w;
170 if (h > maxIconHeight)
171 maxIconHeight = h;
172 if (maxIconHeight > mFontHeight) {
173 actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100174 if (mHeaderH > 0 && actualItemHeight > mHeaderH)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100175 mHeaderH = actualItemHeight;
176 }
177}
178
179void GUIScrollList::SetVisibleListLocation(size_t list_index)
180{
181 // This will make sure that the item indicated by list_index is visible on the screen
182 size_t lines = GetDisplayItemCount(), listSize = GetItemCount();
183
184 if (list_index <= (unsigned)firstDisplayedItem) {
185 // list_index is above the currently displayed items, put the selected item at the very top
186 firstDisplayedItem = list_index;
187 y_offset = 0;
188 } else if (list_index >= firstDisplayedItem + lines) {
189 // list_index is below the currently displayed items, put the selected item at the very bottom
190 firstDisplayedItem = list_index - lines + 1;
191 if (GetDisplayRemainder() != 0) {
192 // There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
193 firstDisplayedItem--;
194 y_offset = GetDisplayRemainder() - actualItemHeight;
195 } else {
196 // There's no partial row so zero out the offset
197 y_offset = 0;
198 }
199 }
200 scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
201 mUpdate = 1;
202}
203
204int GUIScrollList::Render(void)
205{
206 if(!isConditionTrue())
207 return 0;
208
209 // First step, fill background
that9876ac32015-02-15 21:40:59 +0100210 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100211 gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
212
that9876ac32015-02-15 21:40:59 +0100213 // don't paint outside of the box
214 gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
215
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100216 // Next, render the background resource (if it exists)
217 if (mBackground && mBackground->GetResource())
218 {
219 int mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
220 int mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
221 gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
222 }
223
that9876ac32015-02-15 21:40:59 +0100224 // This tells us how many full lines we can actually render
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100225 size_t lines = GetDisplayItemCount();
226
227 size_t listSize = GetItemCount();
228 int listW = mRenderW;
229
230 if (listSize <= lines) {
231 hasScroll = false;
232 scrollingSpeed = 0;
233 lines = listSize;
234 y_offset = 0;
235 } else {
236 hasScroll = true;
237 listW -= mFastScrollW; // space for fast scroll
238 lines++;
239 if (lines < listSize)
240 lines++;
241 }
242
243 void* fontResource = NULL;
244 if (mFont) fontResource = mFont->GetResource();
245
246 int yPos = mRenderY + mHeaderH + y_offset;
247 int fontOffsetY = (int)((actualItemHeight - mFontHeight) / 2);
248
249 // render all visible items
250 for (size_t line = 0; line < lines; line++)
251 {
252 size_t itemindex = line + firstDisplayedItem;
253 if (itemindex >= listSize)
254 break;
255
256 // get item data
thatf6ed8fc2015-02-14 20:23:16 +0100257 ImageResource* icon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100258 std::string label;
259 if (GetListItem(itemindex, icon, label))
260 break;
261
262 if (hasHighlightColor && itemindex == selectedItem) {
263 // Highlight the item background of the selected item
that9876ac32015-02-15 21:40:59 +0100264 gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
265 gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100266 }
267
that9876ac32015-02-15 21:40:59 +0100268 if (itemindex == selectedItem) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100269 // Use the highlight color for the font
that9876ac32015-02-15 21:40:59 +0100270 gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100271 } else {
272 // Set the color for the font
that9876ac32015-02-15 21:40:59 +0100273 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100274 }
275
that9876ac32015-02-15 21:40:59 +0100276 // render icon
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100277 if (icon && icon->GetResource()) {
thatf6ed8fc2015-02-14 20:23:16 +0100278 int currentIconHeight = icon->GetHeight();
279 int currentIconWidth = icon->GetWidth();
that9876ac32015-02-15 21:40:59 +0100280 int currentIconOffsetY = (actualItemHeight - currentIconHeight) / 2;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100281 int currentIconOffsetX = (maxIconWidth - currentIconWidth) / 2;
that9876ac32015-02-15 21:40:59 +0100282 int image_y = (yPos + currentIconOffsetY);
283 gr_blit(icon->GetResource(), 0, 0, currentIconWidth, currentIconHeight, mRenderX + currentIconOffsetX, image_y);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100284 }
285
that9876ac32015-02-15 21:40:59 +0100286 // render label text
287 gr_textEx(mRenderX + maxIconWidth + 5, yPos + fontOffsetY, label.c_str(), fontResource);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100288
289 // Add the separator
that9876ac32015-02-15 21:40:59 +0100290 gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
291 gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100292
293 // Move the yPos
294 yPos += actualItemHeight;
295 }
296
297 // Render the Header (last so that it overwrites the top most row for per pixel scrolling)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100298 yPos = mRenderY;
that9876ac32015-02-15 21:40:59 +0100299 if (mHeaderH > 0) {
300 // First step, fill background
301 gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
302 gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
303
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100304 int mIconOffsetX = 0;
305
306 // render the icon if it exists
thatf6ed8fc2015-02-14 20:23:16 +0100307 ImageResource* headerIcon = mHeaderIcon;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100308 if (headerIcon && headerIcon->GetResource())
309 {
310 gr_blit(headerIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
311 mIconOffsetX = maxIconWidth;
312 }
313
314 // render the text
that9876ac32015-02-15 21:40:59 +0100315 gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
316 gr_textEx(mRenderX + mIconOffsetX + 5, yPos + (int)((mHeaderH - mFontHeight) / 2), mLastHeaderValue.c_str(), fontResource);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100317
318 // Add the separator
that9876ac32015-02-15 21:40:59 +0100319 gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100320 gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
321 }
322
323 // render fast scroll
324 lines = GetDisplayItemCount();
325 if (hasScroll) {
326 int startX = listW + mRenderX;
327 int fWidth = mRenderW - listW;
328 int fHeight = mRenderH - mHeaderH;
329
330 // line
that9876ac32015-02-15 21:40:59 +0100331 gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100332 gr_fill(startX + fWidth/2, mRenderY + mHeaderH, mFastScrollLineW, mRenderH - mHeaderH);
333
334 // rect
335 int pct = 0;
336 if (GetDisplayRemainder() != 0) {
337 // Properly handle the percentage if a partial line is present
338 int partial_line_size = actualItemHeight - GetDisplayRemainder();
339 pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-((lines + 1)*actualItemHeight) + partial_line_size);
340 } else {
341 pct = ((firstDisplayedItem*actualItemHeight - y_offset)*100)/(listSize*actualItemHeight-lines*actualItemHeight);
342 }
343 int mFastScrollRectX = startX + (fWidth - mFastScrollRectW)/2;
344 int mFastScrollRectY = mRenderY+mHeaderH + ((fHeight - mFastScrollRectH)*pct)/100;
345
that9876ac32015-02-15 21:40:59 +0100346 gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100347 gr_fill(mFastScrollRectX, mFastScrollRectY, mFastScrollRectW, mFastScrollRectH);
348 }
349 mUpdate = 0;
that9876ac32015-02-15 21:40:59 +0100350 // reset clipping
351 gr_noclip();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100352 return 0;
353}
354
355int GUIScrollList::Update(void)
356{
357 if(!isConditionTrue())
358 return 0;
359
360 if (!mHeaderIsStatic) {
361 std::string newValue = gui_parse_text(mHeaderText);
362 if (mLastHeaderValue != newValue) {
363 mLastHeaderValue = newValue;
364 mUpdate = 1;
365 }
366 }
367
368 // Handle kinetic scrolling
369 int maxScrollDistance = actualItemHeight * SCROLLING_SPEED_LIMIT;
370 if (scrollingSpeed == 0) {
371 // Do nothing
372 return 0;
373 } else if (scrollingSpeed > 0) {
374 if (scrollingSpeed < maxScrollDistance)
375 y_offset += scrollingSpeed;
376 else
377 y_offset += maxScrollDistance;
378 scrollingSpeed -= SCROLLING_SPEED_DECREMENT;
379 } else if (scrollingSpeed < 0) {
380 if (abs(scrollingSpeed) < maxScrollDistance)
381 y_offset += scrollingSpeed;
382 else
383 y_offset -= maxScrollDistance;
384 scrollingSpeed += SCROLLING_SPEED_DECREMENT;
385 }
386 if (abs(scrollingSpeed) < SCROLLING_FLOOR)
387 scrollingSpeed = 0;
388 HandleScrolling();
389 mUpdate = 1;
390
391 return 0;
392}
393
394size_t GUIScrollList::HitTestItem(int x, int y)
395{
396 // We only care about y position
397 if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
398 return NO_ITEM;
399
400 int startSelection = (y - mRenderY - mHeaderH);
401
402 // Locate the correct item
403 size_t actualSelection = firstDisplayedItem;
404 int selectY = y_offset;
405 while (selectY + actualItemHeight < startSelection) {
406 selectY += actualItemHeight;
407 actualSelection++;
408 }
409
410 if (actualSelection < GetItemCount())
411 return actualSelection;
412
413 return NO_ITEM;
414}
415
416int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
417{
418 if(!isConditionTrue())
419 return -1;
420
421 switch (state)
422 {
423 case TOUCH_START:
424 if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW)
425 fastScroll = 1; // Initial touch is in the fast scroll region
426 if (scrollingSpeed != 0) {
427 selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
428 scrollingSpeed = 0; // stop scrolling on a new touch
429 } else if (!fastScroll) {
430 // find out which item the user touched
431 selectedItem = HitTestItem(x, y);
432 }
433 if (selectedItem != NO_ITEM)
434 mUpdate = 1;
435 lastY = last2Y = y;
436 break;
437
438 case TOUCH_DRAG:
439 if (fastScroll)
440 {
441 int pct = ((y-mRenderY-mHeaderH)*100)/(mRenderH-mHeaderH);
442 int totalSize = GetItemCount();
443 int lines = GetDisplayItemCount();
444
445 float l = float((totalSize-lines)*pct)/100;
446 if(l + lines >= totalSize)
447 {
448 firstDisplayedItem = totalSize - lines;
449 if (GetDisplayRemainder() != 0) {
450 // There's a partial row displayed, set the scrolling offset so that the last item really is at the very bottom
451 firstDisplayedItem--;
452 y_offset = GetDisplayRemainder() - actualItemHeight;
453 } else {
454 // There's no partial row so zero out the offset
455 y_offset = 0;
456 }
457 }
458 else
459 {
460 if (l < 0)
461 l = 0;
462 firstDisplayedItem = l;
463 y_offset = -(l - int(l))*actualItemHeight;
464 if (GetDisplayRemainder() != 0) {
465 // There's a partial row displayed, make sure y_offset doesn't go past the max
466 if (firstDisplayedItem == totalSize - lines - 1 && y_offset < GetDisplayRemainder() - actualItemHeight)
467 y_offset = GetDisplayRemainder() - actualItemHeight;
468 } else if (firstDisplayedItem == totalSize - lines)
469 y_offset = 0;
470 }
471
472 selectedItem = NO_ITEM;
473 mUpdate = 1;
474 scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
475 break;
476 }
477
478 // Provide some debounce on initial touches
479 if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
480 mUpdate = 1;
481 break;
482 }
483
484 selectedItem = NO_ITEM; // nothing is selected because we dragged too far
485 // Handle scrolling
486 if (hasScroll) {
487 y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
488 last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
489 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
490
491 HandleScrolling();
492 } else
493 y_offset = 0;
494 mUpdate = 1;
495 break;
496
497 case TOUCH_RELEASE:
498 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;
509 if (abs(scrollingSpeed) > SCROLLING_FLOOR)
510 scrollingSpeed *= SCROLLING_MULTIPLIER;
511 else
512 scrollingSpeed = 0;
513 }
514 case TOUCH_REPEAT:
515 case TOUCH_HOLD:
516 break;
517 }
518 return 0;
519}
520
521void GUIScrollList::HandleScrolling()
522{
523 // handle dragging downward, scrolling upward
524 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
525 while(firstDisplayedItem && y_offset > 0) {
526 firstDisplayedItem--;
527 y_offset -= actualItemHeight;
528 }
thatde72b6d2015-02-08 08:55:00 +0100529 if (firstDisplayedItem == 0 && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100530 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 +0100531 scrollingSpeed = 0; // stop kinetic scrolling
532 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100533
534 // handle dragging upward, scrolling downward
535 int totalSize = GetItemCount();
536 int lines = GetDisplayItemCount(); // number of full lines our list can display at once
537 int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
538
539 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
540 while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
541 firstDisplayedItem++;
542 y_offset += actualItemHeight;
543 }
544 // Check if we dragged too far, set the list at the bottom and adjust offset as needed
545 if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
546 firstDisplayedItem = totalSize - lines - 1;
547 y_offset = bottom_offset;
thatde72b6d2015-02-08 08:55:00 +0100548 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100549 } else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
550 firstDisplayedItem = totalSize - lines;
551 y_offset = 0;
thatde72b6d2015-02-08 08:55:00 +0100552 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100553 }
554}
555
556int GUIScrollList::GetDisplayItemCount()
557{
558 return (mRenderH - mHeaderH) / (actualItemHeight);
559}
560
561int GUIScrollList::GetDisplayRemainder()
562{
563 return (mRenderH - mHeaderH) % actualItemHeight;
564}
565
566int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
567{
568 GUIObject::NotifyVarChange(varName, value);
569
570 if(!isConditionTrue())
571 return 0;
572
573 if (!mHeaderIsStatic) {
574 std::string newValue = gui_parse_text(mHeaderText);
575 if (mLastHeaderValue != newValue) {
576 mLastHeaderValue = newValue;
577 firstDisplayedItem = 0;
578 y_offset = 0;
579 scrollingSpeed = 0; // stop kinetic scrolling on variable changes
580 mUpdate = 1;
581 }
582 }
583 return 0;
584}
585
586int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
587{
588 mRenderX = x;
589 mRenderY = y;
590 if (w || h)
591 {
592 mRenderW = w;
593 mRenderH = h;
594 }
595 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
596 mUpdate = 1;
597 return 0;
598}
599
600void GUIScrollList::SetPageFocus(int inFocus)
601{
602 if (inFocus) {
603 NotifyVarChange("", ""); // This forces a check for the header text
604 scrollingSpeed = 0; // stop kinetic scrolling on page changes
605 mUpdate = 1;
606 }
607}