blob: bf5a9b06d086ddb0773fec4dc764acb468480375 [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"
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010023}
Ethan Yonkerfbb43532015-12-28 21:54:50 +010024#include "../minuitwrp/minui.h"
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010025
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 +010032
33GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
34{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010035 xml_node<>* child;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010036
37 firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
38 maxIconWidth = maxIconHeight = mHeaderIconHeight = mHeaderIconWidth = 0;
that9876ac32015-02-15 21:40:59 +010039 mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
40 mHeaderIsStatic = false;
thatf6ed8fc2015-02-14 20:23:16 +010041 mBackground = mHeaderIcon = NULL;
42 mFont = NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010043 mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
thata9998212015-02-19 22:51:24 +010044 mFastScrollRectCurrentY = mFastScrollRectCurrentH = mFastScrollRectTouchY = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010045 lastY = last2Y = fastScroll = 0;
46 mUpdate = 0;
47 touchDebounce = 6;
48 ConvertStrToColor("black", &mBackgroundColor);
49 ConvertStrToColor("black", &mHeaderBackgroundColor);
50 ConvertStrToColor("black", &mSeparatorColor);
51 ConvertStrToColor("black", &mHeaderSeparatorColor);
52 ConvertStrToColor("white", &mFontColor);
53 ConvertStrToColor("white", &mHeaderFontColor);
54 ConvertStrToColor("white", &mFastScrollLineColor);
55 ConvertStrToColor("white", &mFastScrollRectColor);
56 hasHighlightColor = false;
that8d46c092015-02-26 01:30:04 +010057 allowSelection = true;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010058 selectedItem = NO_ITEM;
59
60 // Load header text
that8d46c092015-02-26 01:30:04 +010061 // note: node can be NULL for the emergency console
62 child = node ? node->first_node("text") : NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010063 if (child) mHeaderText = child->value();
that9876ac32015-02-15 21:40:59 +010064 // Simple way to check for static state
65 mLastHeaderValue = gui_parse_text(mHeaderText);
66 mHeaderIsStatic = (mLastHeaderValue == mHeaderText);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010067
Ethan Yonker21ff02a2015-02-18 14:35:00 -060068 mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlightColor);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010069
Ethan Yonker21ff02a2015-02-18 14:35:00 -060070 child = FindNode(node, "background");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010071 if (child)
72 {
thatf6ed8fc2015-02-14 20:23:16 +010073 mBackground = LoadAttrImage(child, "resource");
that9876ac32015-02-15 21:40:59 +010074 mBackgroundColor = LoadAttrColor(child, "color");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010075 }
76
77 // Load the placement
Ethan Yonker21ff02a2015-02-18 14:35:00 -060078 LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010079 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
80
81 // Load the font, and possibly override the color
Ethan Yonker21ff02a2015-02-18 14:35:00 -060082 child = FindNode(node, "font");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010083 if (child)
84 {
thatf6ed8fc2015-02-14 20:23:16 +010085 mFont = LoadAttrFont(child, "resource");
that9876ac32015-02-15 21:40:59 +010086 mFontColor = LoadAttrColor(child, "color");
87 mFontHighlightColor = LoadAttrColor(child, "highlightcolor", mFontColor);
88 mItemSpacing = LoadAttrIntScaleY(child, "spacing");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010089 }
90
91 // Load the separator if it exists
Ethan Yonker21ff02a2015-02-18 14:35:00 -060092 child = FindNode(node, "separator");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010093 if (child)
94 {
that9876ac32015-02-15 21:40:59 +010095 mSeparatorColor = LoadAttrColor(child, "color");
96 mSeparatorH = LoadAttrIntScaleY(child, "height");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010097 }
98
that9876ac32015-02-15 21:40:59 +010099 // Fast scroll
Ethan Yonker21ff02a2015-02-18 14:35:00 -0600100 child = FindNode(node, "fastscroll");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100101 if (child)
102 {
that9876ac32015-02-15 21:40:59 +0100103 mFastScrollLineColor = LoadAttrColor(child, "linecolor");
104 mFastScrollRectColor = LoadAttrColor(child, "rectcolor");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100105
that9876ac32015-02-15 21:40:59 +0100106 mFastScrollW = LoadAttrIntScaleX(child, "w");
107 mFastScrollLineW = LoadAttrIntScaleX(child, "linew");
108 mFastScrollRectW = LoadAttrIntScaleX(child, "rectw");
109 mFastScrollRectH = LoadAttrIntScaleY(child, "recth");
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100110 }
111
112 // Retrieve the line height
thatf6ed8fc2015-02-14 20:23:16 +0100113 mFontHeight = mFont->GetHeight();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100114 actualItemHeight = mFontHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100115
116 // Load the header if it exists
Ethan Yonker21ff02a2015-02-18 14:35:00 -0600117 child = FindNode(node, "header");
that9876ac32015-02-15 21:40:59 +0100118 if (child)
119 {
120 mHeaderH = mFontHeight;
121 mHeaderIcon = LoadAttrImage(child, "icon");
122 mHeaderBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
123 mHeaderFontColor = LoadAttrColor(child, "textcolor", mFontColor);
124 mHeaderSeparatorColor = LoadAttrColor(child, "separatorcolor", mSeparatorColor);
125 mHeaderSeparatorH = LoadAttrIntScaleY(child, "separatorheight", mSeparatorH);
126
127 if (mHeaderIcon && mHeaderIcon->GetResource())
128 {
129 mHeaderIconWidth = mHeaderIcon->GetWidth();
130 mHeaderIconHeight = mHeaderIcon->GetHeight();
131 if (mHeaderIconHeight > mHeaderH)
132 mHeaderH = mHeaderIconHeight;
133 if (mHeaderIconWidth > maxIconWidth)
134 maxIconWidth = mHeaderIconWidth;
135 }
136
137 mHeaderH += mItemSpacing + mHeaderSeparatorH;
138 if (mHeaderH < actualItemHeight)
139 mHeaderH = actualItemHeight;
140 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100141
142 if (actualItemHeight / 3 > 6)
143 touchDebounce = actualItemHeight / 3;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100144}
145
146GUIScrollList::~GUIScrollList()
147{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100148}
149
150void GUIScrollList::SetMaxIconSize(int w, int h)
151{
152 if (w > maxIconWidth)
153 maxIconWidth = w;
154 if (h > maxIconHeight)
155 maxIconHeight = h;
156 if (maxIconHeight > mFontHeight) {
157 actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100158 if (mHeaderH > 0 && actualItemHeight > mHeaderH)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100159 mHeaderH = actualItemHeight;
160 }
161}
162
163void GUIScrollList::SetVisibleListLocation(size_t list_index)
164{
165 // This will make sure that the item indicated by list_index is visible on the screen
that8d46c092015-02-26 01:30:04 +0100166 size_t lines = GetDisplayItemCount();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100167
168 if (list_index <= (unsigned)firstDisplayedItem) {
169 // list_index is above the currently displayed items, put the selected item at the very top
170 firstDisplayedItem = list_index;
171 y_offset = 0;
172 } else if (list_index >= firstDisplayedItem + lines) {
173 // list_index is below the currently displayed items, put the selected item at the very bottom
174 firstDisplayedItem = list_index - lines + 1;
175 if (GetDisplayRemainder() != 0) {
176 // There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
177 firstDisplayedItem--;
178 y_offset = GetDisplayRemainder() - actualItemHeight;
179 } else {
180 // There's no partial row so zero out the offset
181 y_offset = 0;
182 }
that8d46c092015-02-26 01:30:04 +0100183 if (firstDisplayedItem < 0)
184 firstDisplayedItem = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100185 }
186 scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
187 mUpdate = 1;
188}
189
190int GUIScrollList::Render(void)
191{
Matt Mowera8a89d12016-12-30 18:10:37 -0600192 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100193 return 0;
194
195 // First step, fill background
that9876ac32015-02-15 21:40:59 +0100196 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100197 gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
198
that9876ac32015-02-15 21:40:59 +0100199 // don't paint outside of the box
200 gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
201
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100202 // Next, render the background resource (if it exists)
203 if (mBackground && mBackground->GetResource())
204 {
that0af77952015-02-25 08:52:19 +0100205 int BackgroundW = mBackground->GetWidth();
206 int BackgroundH = mBackground->GetHeight();
207 int BackgroundX = mRenderX + ((mRenderW - BackgroundW) / 2);
208 int BackgroundY = mRenderY + ((mRenderH - BackgroundH) / 2);
209 gr_blit(mBackground->GetResource(), 0, 0, BackgroundW, BackgroundH, BackgroundX, BackgroundY);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100210 }
211
that9876ac32015-02-15 21:40:59 +0100212 // This tells us how many full lines we can actually render
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100213 size_t lines = GetDisplayItemCount();
214
215 size_t listSize = GetItemCount();
that0af77952015-02-25 08:52:19 +0100216 int listW = mRenderW; // this is only used for the separators - the list items are rendered in the full width of the list
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100217
218 if (listSize <= lines) {
219 hasScroll = false;
220 scrollingSpeed = 0;
221 lines = listSize;
222 y_offset = 0;
223 } else {
224 hasScroll = true;
225 listW -= mFastScrollW; // space for fast scroll
226 lines++;
227 if (lines < listSize)
228 lines++;
229 }
230
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100231 int yPos = mRenderY + mHeaderH + y_offset;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100232
233 // render all visible items
234 for (size_t line = 0; line < lines; line++)
235 {
236 size_t itemindex = line + firstDisplayedItem;
237 if (itemindex >= listSize)
238 break;
239
that0af77952015-02-25 08:52:19 +0100240 RenderItem(itemindex, yPos, itemindex == selectedItem);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100241
242 // Add the separator
that9876ac32015-02-15 21:40:59 +0100243 gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
244 gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100245
246 // Move the yPos
247 yPos += actualItemHeight;
248 }
249
that0af77952015-02-25 08:52:19 +0100250 // Render the Header (last so that it overwrites the top most row for per pixel scrolling)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100251 yPos = mRenderY;
that9876ac32015-02-15 21:40:59 +0100252 if (mHeaderH > 0) {
253 // First step, fill background
254 gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
255 gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
256
that0af77952015-02-25 08:52:19 +0100257 int IconOffsetX = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100258
259 // render the icon if it exists
that0af77952015-02-25 08:52:19 +0100260 if (mHeaderIcon && mHeaderIcon->GetResource())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100261 {
that0af77952015-02-25 08:52:19 +0100262 gr_blit(mHeaderIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
263 IconOffsetX = maxIconWidth;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100264 }
265
266 // render the text
Ethan Yonker58f21322018-08-24 11:17:36 -0500267 if (mFont && mFont->GetResource()) {
268 gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
269 gr_textEx_scaleW(mRenderX + IconOffsetX + 5, yPos + (int)(mHeaderH / 2), mLastHeaderValue.c_str(), mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
270 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100271
272 // Add the separator
that9876ac32015-02-15 21:40:59 +0100273 gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100274 gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
275 }
276
that9876ac32015-02-15 21:40:59 +0100277 // reset clipping
278 gr_noclip();
thata9998212015-02-19 22:51:24 +0100279
280 // render fast scroll
281 if (hasScroll) {
282 int fWidth = mRenderW - listW;
283 int fHeight = mRenderH - mHeaderH;
284 int centerX = listW + mRenderX + fWidth / 2;
285
286 // first determine the total list height and where we are in the list
287 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
288 int topPos = firstDisplayedItem * actualItemHeight - y_offset;
289
290 // now scale it proportionally to the scrollbar height
291 int boxH = fHeight * fHeight / totalHeight; // proportional height of the displayed portion
292 boxH = std::max(boxH, mFastScrollRectH); // but keep a minimum height
293 int boxY = (fHeight - boxH) * topPos / (totalHeight - fHeight); // pixels relative to top of list
294 int boxW = mFastScrollRectW;
295
296 int x = centerX - boxW / 2;
297 int y = mRenderY + mHeaderH + boxY;
298
299 // line above and below box (needs to be split because box can be transparent)
300 gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
301 gr_fill(centerX - mFastScrollLineW / 2, mRenderY + mHeaderH, mFastScrollLineW, boxY);
302 gr_fill(centerX - mFastScrollLineW / 2, y + boxH, mFastScrollLineW, fHeight - boxY - boxH);
303
304 // box
305 gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
306 gr_fill(x, y, boxW, boxH);
307
308 mFastScrollRectCurrentY = boxY;
309 mFastScrollRectCurrentH = boxH;
310 }
311 mUpdate = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100312 return 0;
313}
314
Ethan Yonkerd0514ba2015-10-22 14:17:47 -0500315void GUIScrollList::RenderItem(size_t itemindex __unused, int yPos, bool selected)
that0af77952015-02-25 08:52:19 +0100316{
317 RenderStdItem(yPos, selected, NULL, "implement RenderItem!");
318}
319
320void GUIScrollList::RenderStdItem(int yPos, bool selected, ImageResource* icon, const char* text, int iconAndTextH)
321{
322 if (hasHighlightColor && selected) {
323 // Highlight the item background of the selected item
324 gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
325 gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
326 }
327
328 if (selected) {
329 // Use the highlight color for the font
330 gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
331 } else {
332 // Set the color for the font
333 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
334 }
335
336 if (!iconAndTextH)
337 iconAndTextH = actualItemHeight;
338
339 // render icon
340 if (icon && icon->GetResource()) {
341 int iconH = icon->GetHeight();
342 int iconW = icon->GetWidth();
343 int iconY = yPos + (iconAndTextH - iconH) / 2;
344 int iconX = mRenderX + (maxIconWidth - iconW) / 2;
345 gr_blit(icon->GetResource(), 0, 0, iconW, iconH, iconX, iconY);
346 }
347
348 // render label text
Ethan Yonker58f21322018-08-24 11:17:36 -0500349 if (mFont && mFont->GetResource()) {
350 int textX = mRenderX + maxIconWidth + 5;
351 int textY = yPos + (iconAndTextH / 2);
352 gr_textEx_scaleW(textX, textY, text, mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
353 }
that0af77952015-02-25 08:52:19 +0100354}
355
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100356int GUIScrollList::Update(void)
357{
Matt Mowera8a89d12016-12-30 18:10:37 -0600358 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100359 return 0;
360
361 if (!mHeaderIsStatic) {
362 std::string newValue = gui_parse_text(mHeaderText);
363 if (mLastHeaderValue != newValue) {
364 mLastHeaderValue = newValue;
365 mUpdate = 1;
366 }
367 }
368
369 // Handle kinetic scrolling
that8d46c092015-02-26 01:30:04 +0100370 // maximum number of items to scroll per update
371 float maxItemsScrolledPerFrame = std::max(2.5, float(GetDisplayItemCount() / 4) + 0.5);
372
373 int maxScrollDistance = actualItemHeight * maxItemsScrolledPerFrame;
that10ec0172015-02-15 23:52:28 +0100374 int oldScrollingSpeed = scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100375 if (scrollingSpeed == 0) {
376 // Do nothing
377 return 0;
378 } else if (scrollingSpeed > 0) {
379 if (scrollingSpeed < maxScrollDistance)
380 y_offset += scrollingSpeed;
381 else
382 y_offset += maxScrollDistance;
that10ec0172015-02-15 23:52:28 +0100383 scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
384 if (scrollingSpeed == oldScrollingSpeed)
385 --scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100386 } else if (scrollingSpeed < 0) {
387 if (abs(scrollingSpeed) < maxScrollDistance)
388 y_offset += scrollingSpeed;
389 else
390 y_offset -= maxScrollDistance;
that10ec0172015-02-15 23:52:28 +0100391 scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
392 if (scrollingSpeed == oldScrollingSpeed)
393 ++scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100394 }
395 if (abs(scrollingSpeed) < SCROLLING_FLOOR)
396 scrollingSpeed = 0;
397 HandleScrolling();
398 mUpdate = 1;
399
400 return 0;
401}
402
Ethan Yonkerd0514ba2015-10-22 14:17:47 -0500403size_t GUIScrollList::HitTestItem(int x __unused, int y)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100404{
405 // We only care about y position
406 if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
407 return NO_ITEM;
408
409 int startSelection = (y - mRenderY - mHeaderH);
410
411 // Locate the correct item
412 size_t actualSelection = firstDisplayedItem;
413 int selectY = y_offset;
414 while (selectY + actualItemHeight < startSelection) {
415 selectY += actualItemHeight;
416 actualSelection++;
417 }
418
419 if (actualSelection < GetItemCount())
420 return actualSelection;
421
422 return NO_ITEM;
423}
424
425int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
426{
Matt Mowera8a89d12016-12-30 18:10:37 -0600427 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100428 return -1;
429
430 switch (state)
431 {
432 case TOUCH_START:
thata9998212015-02-19 22:51:24 +0100433 if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100434 fastScroll = 1; // Initial touch is in the fast scroll region
thata9998212015-02-19 22:51:24 +0100435 int fastScrollBoxTop = mFastScrollRectCurrentY + mRenderY + mHeaderH;
436 int fastScrollBoxBottom = fastScrollBoxTop + mFastScrollRectCurrentH;
437 if (y >= fastScrollBoxTop && y < fastScrollBoxBottom)
438 // user grabbed the fastscroll bar
439 // try to keep the initially touched part of the scrollbar under the finger
440 mFastScrollRectTouchY = y - fastScrollBoxTop;
441 else
442 // user tapped outside the fastscroll bar
443 // center fastscroll rect on the initial touch position
444 mFastScrollRectTouchY = mFastScrollRectCurrentH / 2;
445 }
446
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100447 if (scrollingSpeed != 0) {
448 selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
449 scrollingSpeed = 0; // stop scrolling on a new touch
that8d46c092015-02-26 01:30:04 +0100450 } else if (!fastScroll && allowSelection) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100451 // find out which item the user touched
452 selectedItem = HitTestItem(x, y);
453 }
454 if (selectedItem != NO_ITEM)
455 mUpdate = 1;
456 lastY = last2Y = y;
457 break;
458
459 case TOUCH_DRAG:
460 if (fastScroll)
461 {
thata9998212015-02-19 22:51:24 +0100462 int relY = y - mRenderY - mHeaderH; // touch position relative to window
463 int windowH = mRenderH - mHeaderH;
464 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100465
thata9998212015-02-19 22:51:24 +0100466 // calculate new top position of the fastscroll bar relative to window
467 int newY = relY - mFastScrollRectTouchY;
468 // keep it fully inside the list
469 newY = std::min(std::max(newY, 0), windowH - mFastScrollRectCurrentH);
470
471 // now compute the new scroll position for the list
472 int newTopPos = newY * (totalHeight - windowH) / (windowH - mFastScrollRectCurrentH); // new top pixel of list
473 newTopPos = std::min(newTopPos, totalHeight - windowH); // account for rounding errors
474 firstDisplayedItem = newTopPos / actualItemHeight;
475 y_offset = - newTopPos % actualItemHeight;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100476
477 selectedItem = NO_ITEM;
478 mUpdate = 1;
479 scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
480 break;
481 }
482
483 // Provide some debounce on initial touches
484 if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
485 mUpdate = 1;
486 break;
487 }
488
489 selectedItem = NO_ITEM; // nothing is selected because we dragged too far
490 // Handle scrolling
491 if (hasScroll) {
492 y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
493 last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
494 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
495
496 HandleScrolling();
497 } else
498 y_offset = 0;
499 mUpdate = 1;
500 break;
501
502 case TOUCH_RELEASE:
thata9998212015-02-19 22:51:24 +0100503 if (fastScroll)
504 mUpdate = 1; // get rid of touch effects on the fastscroll bar
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100505 fastScroll = 0;
506 if (selectedItem != NO_ITEM) {
507 // We've selected an item!
508 NotifySelect(selectedItem);
509 mUpdate = 1;
510
bigbiff bigbiff3ed778a2019-03-12 19:28:31 -0400511#ifndef TW_NO_HAPTICS
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100512 DataManager::Vibrate("tw_button_vibrate");
bigbiff bigbiff3ed778a2019-03-12 19:28:31 -0400513#endif
514
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100515 selectedItem = NO_ITEM;
516 } else {
517 // Start kinetic scrolling
518 scrollingSpeed = lastY - last2Y;
that10ec0172015-02-15 23:52:28 +0100519 if (abs(scrollingSpeed) < touchDebounce)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100520 scrollingSpeed = 0;
521 }
522 case TOUCH_REPEAT:
523 case TOUCH_HOLD:
524 break;
525 }
526 return 0;
527}
528
529void GUIScrollList::HandleScrolling()
530{
531 // handle dragging downward, scrolling upward
532 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
Matt Mowera8a89d12016-12-30 18:10:37 -0600533 while (firstDisplayedItem && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100534 firstDisplayedItem--;
535 y_offset -= actualItemHeight;
536 }
thatde72b6d2015-02-08 08:55:00 +0100537 if (firstDisplayedItem == 0 && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100538 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 +0100539 scrollingSpeed = 0; // stop kinetic scrolling
540 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100541
542 // handle dragging upward, scrolling downward
543 int totalSize = GetItemCount();
544 int lines = GetDisplayItemCount(); // number of full lines our list can display at once
545 int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
546
547 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
548 while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
549 firstDisplayedItem++;
550 y_offset += actualItemHeight;
551 }
552 // Check if we dragged too far, set the list at the bottom and adjust offset as needed
553 if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
554 firstDisplayedItem = totalSize - lines - 1;
555 y_offset = bottom_offset;
thatde72b6d2015-02-08 08:55:00 +0100556 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100557 } else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
558 firstDisplayedItem = totalSize - lines;
559 y_offset = 0;
thatde72b6d2015-02-08 08:55:00 +0100560 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100561 }
562}
563
564int GUIScrollList::GetDisplayItemCount()
565{
566 return (mRenderH - mHeaderH) / (actualItemHeight);
567}
568
569int GUIScrollList::GetDisplayRemainder()
570{
571 return (mRenderH - mHeaderH) % actualItemHeight;
572}
573
574int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
575{
576 GUIObject::NotifyVarChange(varName, value);
577
Matt Mowera8a89d12016-12-30 18:10:37 -0600578 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100579 return 0;
580
581 if (!mHeaderIsStatic) {
582 std::string newValue = gui_parse_text(mHeaderText);
583 if (mLastHeaderValue != newValue) {
584 mLastHeaderValue = newValue;
585 firstDisplayedItem = 0;
586 y_offset = 0;
587 scrollingSpeed = 0; // stop kinetic scrolling on variable changes
588 mUpdate = 1;
589 }
590 }
591 return 0;
592}
593
594int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
595{
596 mRenderX = x;
597 mRenderY = y;
598 if (w || h)
599 {
600 mRenderW = w;
601 mRenderH = h;
602 }
603 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
604 mUpdate = 1;
605 return 0;
606}
607
608void GUIScrollList::SetPageFocus(int inFocus)
609{
610 if (inFocus) {
611 NotifyVarChange("", ""); // This forces a check for the header text
612 scrollingSpeed = 0; // stop kinetic scrolling on page changes
613 mUpdate = 1;
614 }
615}
Ethan Yonker44925ad2015-07-22 12:33:59 -0500616
617bool GUIScrollList::AddLines(std::vector<std::string>* origText, std::vector<std::string>* origColor, size_t* lastCount, std::vector<std::string>* rText, std::vector<std::string>* rColor)
618{
Ethan Yonker58f21322018-08-24 11:17:36 -0500619 if (!mFont || !mFont->GetResource())
620 return false;
Ethan Yonker44925ad2015-07-22 12:33:59 -0500621 if (*lastCount == origText->size())
622 return false; // nothing to add
623
624 size_t prevCount = *lastCount;
625 *lastCount = origText->size();
626
627 // Due to word wrap, figure out what / how the newly added text needs to be added to the render vector that is word wrapped
628 // Note, that multiple consoles on different GUI pages may be different widths or use different fonts, so the word wrapping
629 // may different in different console windows
630 for (size_t i = prevCount; i < *lastCount; i++) {
631 string curr_line = origText->at(i);
632 string curr_color;
633 if (origColor)
634 curr_color = origColor->at(i);
Matt Mowera8a89d12016-12-30 18:10:37 -0600635 for (;;) {
Ethan Yonker44925ad2015-07-22 12:33:59 -0500636 size_t line_char_width = gr_ttf_maxExW(curr_line.c_str(), mFont->GetResource(), mRenderW);
637 if (line_char_width < curr_line.size()) {
638 //string left = curr_line.substr(0, line_char_width);
639 size_t wrap_pos = curr_line.find_last_of(" ,./:-_;", line_char_width - 1);
640 if (wrap_pos == string::npos)
641 wrap_pos = line_char_width;
642 else if (wrap_pos < line_char_width - 1)
643 wrap_pos++;
644 rText->push_back(curr_line.substr(0, wrap_pos));
645 if (origColor)
646 rColor->push_back(curr_color);
647 curr_line = curr_line.substr(wrap_pos);
that1cc7fed2016-01-15 22:13:45 -0600648 /* After word wrapping, delete any leading spaces. Note that the word wrapping is not smart enough to know not
649 * to wrap in the middle of something like ... so some of the ... could appear on the following line. */
650 curr_line.erase(0, curr_line.find_first_not_of(" "));
Ethan Yonker44925ad2015-07-22 12:33:59 -0500651 } else {
652 rText->push_back(curr_line);
653 if (origColor)
654 rColor->push_back(curr_color);
655 break;
656 }
657 }
658 }
659 return true;
660}