blob: 0a4eed85b2f289657a21049cc003b99507791e31 [file] [log] [blame]
Ethan Yonker0a3a98f2015-02-05 00:48:28 +01001/*
bigbiffd58ba182020-03-23 10:02:29 -04002 Copyright 2012 to 2020 TeamWin
Ethan Yonker0a3a98f2015-02-05 00:48:28 +01003 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"
bigbiffd58ba182020-03-23 10:02:29 -040025#include "../minuitwrp/truetype.hpp"
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010026
27#include "rapidxml.hpp"
28#include "objects.hpp"
29#include "../data.hpp"
30
that10ec0172015-02-15 23:52:28 +010031const float SCROLLING_SPEED_DECREMENT = 0.9; // friction
32const int SCROLLING_FLOOR = 2; // minimum pixels for scrolling to stop
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010033
34GUIScrollList::GUIScrollList(xml_node<>* node) : GUIObject(node)
35{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010036 xml_node<>* child;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010037
38 firstDisplayedItem = mItemSpacing = mFontHeight = mSeparatorH = y_offset = scrollingSpeed = 0;
39 maxIconWidth = maxIconHeight = mHeaderIconHeight = mHeaderIconWidth = 0;
that9876ac32015-02-15 21:40:59 +010040 mHeaderSeparatorH = mHeaderH = actualItemHeight = 0;
41 mHeaderIsStatic = false;
thatf6ed8fc2015-02-14 20:23:16 +010042 mBackground = mHeaderIcon = NULL;
43 mFont = NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010044 mFastScrollW = mFastScrollLineW = mFastScrollRectW = mFastScrollRectH = 0;
thata9998212015-02-19 22:51:24 +010045 mFastScrollRectCurrentY = mFastScrollRectCurrentH = mFastScrollRectTouchY = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010046 lastY = last2Y = fastScroll = 0;
47 mUpdate = 0;
48 touchDebounce = 6;
49 ConvertStrToColor("black", &mBackgroundColor);
50 ConvertStrToColor("black", &mHeaderBackgroundColor);
51 ConvertStrToColor("black", &mSeparatorColor);
52 ConvertStrToColor("black", &mHeaderSeparatorColor);
53 ConvertStrToColor("white", &mFontColor);
54 ConvertStrToColor("white", &mHeaderFontColor);
55 ConvertStrToColor("white", &mFastScrollLineColor);
56 ConvertStrToColor("white", &mFastScrollRectColor);
57 hasHighlightColor = false;
that8d46c092015-02-26 01:30:04 +010058 allowSelection = true;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010059 selectedItem = NO_ITEM;
60
61 // Load header text
that8d46c092015-02-26 01:30:04 +010062 // note: node can be NULL for the emergency console
63 child = node ? node->first_node("text") : NULL;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +010064 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;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100145}
146
147GUIScrollList::~GUIScrollList()
148{
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100149}
150
151void GUIScrollList::SetMaxIconSize(int w, int h)
152{
153 if (w > maxIconWidth)
154 maxIconWidth = w;
155 if (h > maxIconHeight)
156 maxIconHeight = h;
157 if (maxIconHeight > mFontHeight) {
158 actualItemHeight = maxIconHeight + mItemSpacing + mSeparatorH;
that9876ac32015-02-15 21:40:59 +0100159 if (mHeaderH > 0 && actualItemHeight > mHeaderH)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100160 mHeaderH = actualItemHeight;
161 }
162}
163
164void GUIScrollList::SetVisibleListLocation(size_t list_index)
165{
166 // This will make sure that the item indicated by list_index is visible on the screen
that8d46c092015-02-26 01:30:04 +0100167 size_t lines = GetDisplayItemCount();
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100168
169 if (list_index <= (unsigned)firstDisplayedItem) {
170 // list_index is above the currently displayed items, put the selected item at the very top
171 firstDisplayedItem = list_index;
172 y_offset = 0;
173 } else if (list_index >= firstDisplayedItem + lines) {
174 // list_index is below the currently displayed items, put the selected item at the very bottom
175 firstDisplayedItem = list_index - lines + 1;
176 if (GetDisplayRemainder() != 0) {
177 // There's a partial row displayed, set the scrolling offset so that the selected item really is at the very bottom
178 firstDisplayedItem--;
179 y_offset = GetDisplayRemainder() - actualItemHeight;
180 } else {
181 // There's no partial row so zero out the offset
182 y_offset = 0;
183 }
that8d46c092015-02-26 01:30:04 +0100184 if (firstDisplayedItem < 0)
185 firstDisplayedItem = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100186 }
187 scrollingSpeed = 0; // stop kinetic scrolling on setting visible location
188 mUpdate = 1;
189}
190
191int GUIScrollList::Render(void)
192{
Matt Mowera8a89d12016-12-30 18:10:37 -0600193 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100194 return 0;
195
196 // First step, fill background
that9876ac32015-02-15 21:40:59 +0100197 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100198 gr_fill(mRenderX, mRenderY + mHeaderH, mRenderW, mRenderH - mHeaderH);
199
that9876ac32015-02-15 21:40:59 +0100200 // don't paint outside of the box
201 gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
202
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100203 // Next, render the background resource (if it exists)
204 if (mBackground && mBackground->GetResource())
205 {
that0af77952015-02-25 08:52:19 +0100206 int BackgroundW = mBackground->GetWidth();
207 int BackgroundH = mBackground->GetHeight();
208 int BackgroundX = mRenderX + ((mRenderW - BackgroundW) / 2);
209 int BackgroundY = mRenderY + ((mRenderH - BackgroundH) / 2);
210 gr_blit(mBackground->GetResource(), 0, 0, BackgroundW, BackgroundH, BackgroundX, BackgroundY);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100211 }
212
that9876ac32015-02-15 21:40:59 +0100213 // This tells us how many full lines we can actually render
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100214 size_t lines = GetDisplayItemCount();
215
216 size_t listSize = GetItemCount();
that0af77952015-02-25 08:52:19 +0100217 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 +0100218
219 if (listSize <= lines) {
220 hasScroll = false;
221 scrollingSpeed = 0;
222 lines = listSize;
223 y_offset = 0;
224 } else {
225 hasScroll = true;
226 listW -= mFastScrollW; // space for fast scroll
227 lines++;
228 if (lines < listSize)
229 lines++;
230 }
231
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100232 int yPos = mRenderY + mHeaderH + y_offset;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100233
234 // render all visible items
235 for (size_t line = 0; line < lines; line++)
236 {
237 size_t itemindex = line + firstDisplayedItem;
238 if (itemindex >= listSize)
239 break;
240
that0af77952015-02-25 08:52:19 +0100241 RenderItem(itemindex, yPos, itemindex == selectedItem);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100242
243 // Add the separator
that9876ac32015-02-15 21:40:59 +0100244 gr_color(mSeparatorColor.red, mSeparatorColor.green, mSeparatorColor.blue, mSeparatorColor.alpha);
245 gr_fill(mRenderX, yPos + actualItemHeight - mSeparatorH, listW, mSeparatorH);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100246
247 // Move the yPos
248 yPos += actualItemHeight;
249 }
250
that0af77952015-02-25 08:52:19 +0100251 // Render the Header (last so that it overwrites the top most row for per pixel scrolling)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100252 yPos = mRenderY;
that9876ac32015-02-15 21:40:59 +0100253 if (mHeaderH > 0) {
254 // First step, fill background
255 gr_color(mHeaderBackgroundColor.red, mHeaderBackgroundColor.green, mHeaderBackgroundColor.blue, mHeaderBackgroundColor.alpha);
256 gr_fill(mRenderX, mRenderY, mRenderW, mHeaderH);
257
that0af77952015-02-25 08:52:19 +0100258 int IconOffsetX = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100259
260 // render the icon if it exists
that0af77952015-02-25 08:52:19 +0100261 if (mHeaderIcon && mHeaderIcon->GetResource())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100262 {
that0af77952015-02-25 08:52:19 +0100263 gr_blit(mHeaderIcon->GetResource(), 0, 0, mHeaderIconWidth, mHeaderIconHeight, mRenderX + ((mHeaderIconWidth - maxIconWidth) / 2), (yPos + (int)((mHeaderH - mHeaderIconHeight) / 2)));
264 IconOffsetX = maxIconWidth;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100265 }
266
267 // render the text
Ethan Yonker58f21322018-08-24 11:17:36 -0500268 if (mFont && mFont->GetResource()) {
269 gr_color(mHeaderFontColor.red, mHeaderFontColor.green, mHeaderFontColor.blue, mHeaderFontColor.alpha);
270 gr_textEx_scaleW(mRenderX + IconOffsetX + 5, yPos + (int)(mHeaderH / 2), mLastHeaderValue.c_str(), mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
271 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100272
273 // Add the separator
that9876ac32015-02-15 21:40:59 +0100274 gr_color(mHeaderSeparatorColor.red, mHeaderSeparatorColor.green, mHeaderSeparatorColor.blue, mHeaderSeparatorColor.alpha);
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100275 gr_fill(mRenderX, yPos + mHeaderH - mHeaderSeparatorH, mRenderW, mHeaderSeparatorH);
276 }
277
that9876ac32015-02-15 21:40:59 +0100278 // reset clipping
279 gr_noclip();
thata9998212015-02-19 22:51:24 +0100280
281 // render fast scroll
282 if (hasScroll) {
283 int fWidth = mRenderW - listW;
284 int fHeight = mRenderH - mHeaderH;
285 int centerX = listW + mRenderX + fWidth / 2;
286
287 // first determine the total list height and where we are in the list
288 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
289 int topPos = firstDisplayedItem * actualItemHeight - y_offset;
290
291 // now scale it proportionally to the scrollbar height
292 int boxH = fHeight * fHeight / totalHeight; // proportional height of the displayed portion
293 boxH = std::max(boxH, mFastScrollRectH); // but keep a minimum height
294 int boxY = (fHeight - boxH) * topPos / (totalHeight - fHeight); // pixels relative to top of list
295 int boxW = mFastScrollRectW;
296
297 int x = centerX - boxW / 2;
298 int y = mRenderY + mHeaderH + boxY;
299
300 // line above and below box (needs to be split because box can be transparent)
301 gr_color(mFastScrollLineColor.red, mFastScrollLineColor.green, mFastScrollLineColor.blue, mFastScrollLineColor.alpha);
302 gr_fill(centerX - mFastScrollLineW / 2, mRenderY + mHeaderH, mFastScrollLineW, boxY);
303 gr_fill(centerX - mFastScrollLineW / 2, y + boxH, mFastScrollLineW, fHeight - boxY - boxH);
304
305 // box
306 gr_color(mFastScrollRectColor.red, mFastScrollRectColor.green, mFastScrollRectColor.blue, mFastScrollRectColor.alpha);
307 gr_fill(x, y, boxW, boxH);
308
309 mFastScrollRectCurrentY = boxY;
310 mFastScrollRectCurrentH = boxH;
311 }
312 mUpdate = 0;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100313 return 0;
314}
315
Ethan Yonkerd0514ba2015-10-22 14:17:47 -0500316void GUIScrollList::RenderItem(size_t itemindex __unused, int yPos, bool selected)
that0af77952015-02-25 08:52:19 +0100317{
318 RenderStdItem(yPos, selected, NULL, "implement RenderItem!");
319}
320
321void GUIScrollList::RenderStdItem(int yPos, bool selected, ImageResource* icon, const char* text, int iconAndTextH)
322{
323 if (hasHighlightColor && selected) {
324 // Highlight the item background of the selected item
325 gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
326 gr_fill(mRenderX, yPos, mRenderW, actualItemHeight);
327 }
328
329 if (selected) {
330 // Use the highlight color for the font
331 gr_color(mFontHighlightColor.red, mFontHighlightColor.green, mFontHighlightColor.blue, mFontHighlightColor.alpha);
332 } else {
333 // Set the color for the font
334 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
335 }
336
337 if (!iconAndTextH)
338 iconAndTextH = actualItemHeight;
339
340 // render icon
341 if (icon && icon->GetResource()) {
342 int iconH = icon->GetHeight();
343 int iconW = icon->GetWidth();
344 int iconY = yPos + (iconAndTextH - iconH) / 2;
345 int iconX = mRenderX + (maxIconWidth - iconW) / 2;
346 gr_blit(icon->GetResource(), 0, 0, iconW, iconH, iconX, iconY);
347 }
348
349 // render label text
Ethan Yonker58f21322018-08-24 11:17:36 -0500350 if (mFont && mFont->GetResource()) {
351 int textX = mRenderX + maxIconWidth + 5;
352 int textY = yPos + (iconAndTextH / 2);
353 gr_textEx_scaleW(textX, textY, text, mFont->GetResource(), mRenderW, TEXT_ONLY_RIGHT, 0);
354 }
that0af77952015-02-25 08:52:19 +0100355}
356
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100357int GUIScrollList::Update(void)
358{
Matt Mowera8a89d12016-12-30 18:10:37 -0600359 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100360 return 0;
361
362 if (!mHeaderIsStatic) {
363 std::string newValue = gui_parse_text(mHeaderText);
364 if (mLastHeaderValue != newValue) {
365 mLastHeaderValue = newValue;
366 mUpdate = 1;
367 }
368 }
369
370 // Handle kinetic scrolling
that8d46c092015-02-26 01:30:04 +0100371 // maximum number of items to scroll per update
372 float maxItemsScrolledPerFrame = std::max(2.5, float(GetDisplayItemCount() / 4) + 0.5);
373
374 int maxScrollDistance = actualItemHeight * maxItemsScrolledPerFrame;
that10ec0172015-02-15 23:52:28 +0100375 int oldScrollingSpeed = scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100376 if (scrollingSpeed == 0) {
377 // Do nothing
378 return 0;
379 } else if (scrollingSpeed > 0) {
380 if (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 } else if (scrollingSpeed < 0) {
388 if (abs(scrollingSpeed) < maxScrollDistance)
389 y_offset += scrollingSpeed;
390 else
391 y_offset -= maxScrollDistance;
that10ec0172015-02-15 23:52:28 +0100392 scrollingSpeed *= SCROLLING_SPEED_DECREMENT;
393 if (scrollingSpeed == oldScrollingSpeed)
394 ++scrollingSpeed;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100395 }
396 if (abs(scrollingSpeed) < SCROLLING_FLOOR)
397 scrollingSpeed = 0;
398 HandleScrolling();
399 mUpdate = 1;
400
401 return 0;
402}
403
Ethan Yonkerd0514ba2015-10-22 14:17:47 -0500404size_t GUIScrollList::HitTestItem(int x __unused, int y)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100405{
406 // We only care about y position
407 if (y < mRenderY || y - mRenderY <= mHeaderH || y - mRenderY > mRenderH)
408 return NO_ITEM;
409
410 int startSelection = (y - mRenderY - mHeaderH);
411
412 // Locate the correct item
413 size_t actualSelection = firstDisplayedItem;
414 int selectY = y_offset;
415 while (selectY + actualItemHeight < startSelection) {
416 selectY += actualItemHeight;
417 actualSelection++;
418 }
419
420 if (actualSelection < GetItemCount())
421 return actualSelection;
422
423 return NO_ITEM;
424}
425
426int GUIScrollList::NotifyTouch(TOUCH_STATE state, int x, int y)
427{
Matt Mowera8a89d12016-12-30 18:10:37 -0600428 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100429 return -1;
430
431 switch (state)
432 {
433 case TOUCH_START:
thata9998212015-02-19 22:51:24 +0100434 if (hasScroll && x >= mRenderX + mRenderW - mFastScrollW) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100435 fastScroll = 1; // Initial touch is in the fast scroll region
thata9998212015-02-19 22:51:24 +0100436 int fastScrollBoxTop = mFastScrollRectCurrentY + mRenderY + mHeaderH;
437 int fastScrollBoxBottom = fastScrollBoxTop + mFastScrollRectCurrentH;
438 if (y >= fastScrollBoxTop && y < fastScrollBoxBottom)
439 // user grabbed the fastscroll bar
440 // try to keep the initially touched part of the scrollbar under the finger
441 mFastScrollRectTouchY = y - fastScrollBoxTop;
442 else
443 // user tapped outside the fastscroll bar
444 // center fastscroll rect on the initial touch position
445 mFastScrollRectTouchY = mFastScrollRectCurrentH / 2;
446 }
447
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100448 if (scrollingSpeed != 0) {
449 selectedItem = NO_ITEM; // this allows the user to tap the list to stop the scrolling without selecting the item they tap
450 scrollingSpeed = 0; // stop scrolling on a new touch
that8d46c092015-02-26 01:30:04 +0100451 } else if (!fastScroll && allowSelection) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100452 // find out which item the user touched
453 selectedItem = HitTestItem(x, y);
454 }
455 if (selectedItem != NO_ITEM)
456 mUpdate = 1;
457 lastY = last2Y = y;
458 break;
459
460 case TOUCH_DRAG:
461 if (fastScroll)
462 {
thata9998212015-02-19 22:51:24 +0100463 int relY = y - mRenderY - mHeaderH; // touch position relative to window
464 int windowH = mRenderH - mHeaderH;
465 int totalHeight = GetItemCount() * actualItemHeight; // total height of the full list in pixels
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100466
thata9998212015-02-19 22:51:24 +0100467 // calculate new top position of the fastscroll bar relative to window
468 int newY = relY - mFastScrollRectTouchY;
469 // keep it fully inside the list
470 newY = std::min(std::max(newY, 0), windowH - mFastScrollRectCurrentH);
471
472 // now compute the new scroll position for the list
473 int newTopPos = newY * (totalHeight - windowH) / (windowH - mFastScrollRectCurrentH); // new top pixel of list
474 newTopPos = std::min(newTopPos, totalHeight - windowH); // account for rounding errors
475 firstDisplayedItem = newTopPos / actualItemHeight;
476 y_offset = - newTopPos % actualItemHeight;
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100477
478 selectedItem = NO_ITEM;
479 mUpdate = 1;
480 scrollingSpeed = 0; // prevent kinetic scrolling when using fast scroll
481 break;
482 }
483
484 // Provide some debounce on initial touches
485 if (selectedItem != NO_ITEM && abs(y - lastY) < touchDebounce) {
486 mUpdate = 1;
487 break;
488 }
489
490 selectedItem = NO_ITEM; // nothing is selected because we dragged too far
491 // Handle scrolling
492 if (hasScroll) {
493 y_offset += y - lastY; // adjust the scrolling offset based on the difference between the starting touch and the current touch
494 last2Y = lastY; // keep track of previous y locations so that we can tell how fast to scroll for kinetic scrolling
495 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
496
497 HandleScrolling();
498 } else
499 y_offset = 0;
500 mUpdate = 1;
501 break;
502
503 case TOUCH_RELEASE:
thata9998212015-02-19 22:51:24 +0100504 if (fastScroll)
505 mUpdate = 1; // get rid of touch effects on the fastscroll bar
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100506 fastScroll = 0;
507 if (selectedItem != NO_ITEM) {
508 // We've selected an item!
509 NotifySelect(selectedItem);
510 mUpdate = 1;
511
bigbiff bigbiff3ed778a2019-03-12 19:28:31 -0400512#ifndef TW_NO_HAPTICS
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100513 DataManager::Vibrate("tw_button_vibrate");
bigbiff bigbiff3ed778a2019-03-12 19:28:31 -0400514#endif
515
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100516 selectedItem = NO_ITEM;
517 } else {
518 // Start kinetic scrolling
519 scrollingSpeed = lastY - last2Y;
that10ec0172015-02-15 23:52:28 +0100520 if (abs(scrollingSpeed) < touchDebounce)
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100521 scrollingSpeed = 0;
522 }
523 case TOUCH_REPEAT:
524 case TOUCH_HOLD:
525 break;
526 }
527 return 0;
528}
529
530void GUIScrollList::HandleScrolling()
531{
532 // handle dragging downward, scrolling upward
533 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
Matt Mowera8a89d12016-12-30 18:10:37 -0600534 while (firstDisplayedItem && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100535 firstDisplayedItem--;
536 y_offset -= actualItemHeight;
537 }
thatde72b6d2015-02-08 08:55:00 +0100538 if (firstDisplayedItem == 0 && y_offset > 0) {
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100539 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 +0100540 scrollingSpeed = 0; // stop kinetic scrolling
541 }
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100542
543 // handle dragging upward, scrolling downward
544 int totalSize = GetItemCount();
545 int lines = GetDisplayItemCount(); // number of full lines our list can display at once
546 int bottom_offset = GetDisplayRemainder() - actualItemHeight; // extra display area that can display a partial line for per pixel scrolling
547
548 // the offset should always be <= 0 and > -actualItemHeight, adjust the first display row and offset as needed
549 while (firstDisplayedItem + lines + (bottom_offset ? 1 : 0) < totalSize && abs(y_offset) > actualItemHeight) {
550 firstDisplayedItem++;
551 y_offset += actualItemHeight;
552 }
553 // Check if we dragged too far, set the list at the bottom and adjust offset as needed
554 if (bottom_offset != 0 && firstDisplayedItem + lines + 1 >= totalSize && y_offset <= bottom_offset) {
555 firstDisplayedItem = totalSize - lines - 1;
556 y_offset = bottom_offset;
thatde72b6d2015-02-08 08:55:00 +0100557 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100558 } else if (firstDisplayedItem + lines >= totalSize && y_offset < 0) {
559 firstDisplayedItem = totalSize - lines;
560 y_offset = 0;
thatde72b6d2015-02-08 08:55:00 +0100561 scrollingSpeed = 0; // stop kinetic scrolling
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100562 }
563}
564
565int GUIScrollList::GetDisplayItemCount()
566{
567 return (mRenderH - mHeaderH) / (actualItemHeight);
568}
569
570int GUIScrollList::GetDisplayRemainder()
571{
572 return (mRenderH - mHeaderH) % actualItemHeight;
573}
574
575int GUIScrollList::NotifyVarChange(const std::string& varName, const std::string& value)
576{
577 GUIObject::NotifyVarChange(varName, value);
578
Matt Mowera8a89d12016-12-30 18:10:37 -0600579 if (!isConditionTrue())
Ethan Yonker0a3a98f2015-02-05 00:48:28 +0100580 return 0;
581
582 if (!mHeaderIsStatic) {
583 std::string newValue = gui_parse_text(mHeaderText);
584 if (mLastHeaderValue != newValue) {
585 mLastHeaderValue = newValue;
586 firstDisplayedItem = 0;
587 y_offset = 0;
588 scrollingSpeed = 0; // stop kinetic scrolling on variable changes
589 mUpdate = 1;
590 }
591 }
592 return 0;
593}
594
595int GUIScrollList::SetRenderPos(int x, int y, int w /* = 0 */, int h /* = 0 */)
596{
597 mRenderX = x;
598 mRenderY = y;
599 if (w || h)
600 {
601 mRenderW = w;
602 mRenderH = h;
603 }
604 SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
605 mUpdate = 1;
606 return 0;
607}
608
609void GUIScrollList::SetPageFocus(int inFocus)
610{
611 if (inFocus) {
612 NotifyVarChange("", ""); // This forces a check for the header text
613 scrollingSpeed = 0; // stop kinetic scrolling on page changes
614 mUpdate = 1;
615 }
616}
Ethan Yonker44925ad2015-07-22 12:33:59 -0500617
618bool 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)
619{
Ethan Yonker58f21322018-08-24 11:17:36 -0500620 if (!mFont || !mFont->GetResource())
621 return false;
Ethan Yonker44925ad2015-07-22 12:33:59 -0500622 if (*lastCount == origText->size())
623 return false; // nothing to add
624
625 size_t prevCount = *lastCount;
626 *lastCount = origText->size();
627
628 // Due to word wrap, figure out what / how the newly added text needs to be added to the render vector that is word wrapped
629 // Note, that multiple consoles on different GUI pages may be different widths or use different fonts, so the word wrapping
630 // may different in different console windows
631 for (size_t i = prevCount; i < *lastCount; i++) {
632 string curr_line = origText->at(i);
633 string curr_color;
634 if (origColor)
635 curr_color = origColor->at(i);
Matt Mowera8a89d12016-12-30 18:10:37 -0600636 for (;;) {
bigbiffd58ba182020-03-23 10:02:29 -0400637 size_t line_char_width = twrpTruetype::gr_ttf_maxExW(curr_line.c_str(), mFont->GetResource(), mRenderW);
Ethan Yonker44925ad2015-07-22 12:33:59 -0500638 if (line_char_width < curr_line.size()) {
639 //string left = curr_line.substr(0, line_char_width);
640 size_t wrap_pos = curr_line.find_last_of(" ,./:-_;", line_char_width - 1);
641 if (wrap_pos == string::npos)
642 wrap_pos = line_char_width;
643 else if (wrap_pos < line_char_width - 1)
644 wrap_pos++;
645 rText->push_back(curr_line.substr(0, wrap_pos));
646 if (origColor)
647 rColor->push_back(curr_color);
648 curr_line = curr_line.substr(wrap_pos);
that1cc7fed2016-01-15 22:13:45 -0600649 /* After word wrapping, delete any leading spaces. Note that the word wrapping is not smart enough to know not
650 * to wrap in the middle of something like ... so some of the ... could appear on the following line. */
651 curr_line.erase(0, curr_line.find_first_not_of(" "));
Ethan Yonker44925ad2015-07-22 12:33:59 -0500652 } else {
653 rText->push_back(curr_line);
654 if (origColor)
655 rColor->push_back(curr_color);
656 break;
657 }
658 }
659 }
660 return true;
661}