summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-04-22 09:01:28 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-04-22 09:01:28 +0300
commitceec0e852294e1097012f4f310205adeee58e6db (patch)
treeb43075b82016684dbcff0c5fa2ec968470eb003a /src/ui
parent2acc486899b7253dbbc1656ea444c596d8af16ad (diff)
DocumentWidget: SmoothScroll utility
Added a `SmoothScroll` utility to make the smooth scroll and bounce behavior applicable elsewhere as well.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c263
1 files changed, 173 insertions, 90 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 87c724de..eff18f39 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -195,12 +195,123 @@ static void animate_DocumentWidget_ (void *ticker);
195static void animateMedia_DocumentWidget_ (iDocumentWidget *d); 195static void animateMedia_DocumentWidget_ (iDocumentWidget *d);
196static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); 196static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d);
197static void prerender_DocumentWidget_ (iAny *); 197static void prerender_DocumentWidget_ (iAny *);
198static void scrollBegan_DocumentWidget_ (iAnyObject *, int, uint32_t);
198 199
199static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ 200static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */
200static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ 201static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */
201static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */ 202static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */
202static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */ 203static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */
203 204
205
206iDeclareType(SmoothScroll)
207
208typedef void (*iSmoothScrollNotifyFunc)(iAnyObject *, int offset, uint32_t span);
209
210struct Impl_SmoothScroll {
211 iWidget *widget;
212 iSmoothScrollNotifyFunc notify;
213 iAnim pos;
214 int max;
215 int overscroll;
216};
217
218void reset_SmoothScroll(iSmoothScroll *d) {
219 init_Anim(&d->pos, 0);
220 d->max = 0;
221 d->overscroll = (deviceType_App() != desktop_AppDeviceType ? 100 * gap_UI : 0);
222}
223
224void init_SmoothScroll(iSmoothScroll *d, iWidget *owner, iSmoothScrollNotifyFunc notify) {
225 d->widget = owner;
226 d->notify = notify;
227 reset_SmoothScroll(d);
228}
229
230void setMax_SmoothScroll(iSmoothScroll *d, int max) {
231 max = iMax(0, max);
232 if (max != d->max) {
233 d->max = max;
234 if (targetValue_Anim(&d->pos) > d->max) {
235 d->pos.to = d->max;
236 }
237 }
238}
239
240static int overscroll_SmoothScroll_(const iSmoothScroll *d) {
241 if (d->overscroll) {
242 const int y = value_Anim(&d->pos);
243 if (y <= 0) {
244 return y;
245 }
246 if (y >= d->max) {
247 return y - d->max;
248 }
249 }
250 return 0;
251}
252
253float pos_SmoothScroll(const iSmoothScroll *d) {
254 return value_Anim(&d->pos) - overscroll_SmoothScroll_(d) * 0.667f;
255}
256
257iBool isFinished_SmoothScroll(const iSmoothScroll *d) {
258 return isFinished_Anim(&d->pos);
259}
260
261void move_SmoothScroll(iSmoothScroll *d, int offset, uint32_t duration) {
262#if !defined (iPlatformMobile)
263 if (!prefs_App()->smoothScrolling) {
264 duration = 0; /* always instant */
265 }
266#endif
267 int destY = targetValue_Anim(&d->pos) + offset;
268 if (destY < -d->overscroll) {
269 destY = -d->overscroll;
270 }
271 if (d->max > 0) {
272 if (destY >= d->max + d->overscroll) {
273 destY = d->max + d->overscroll;
274 }
275 }
276 else {
277 destY = 0;
278 }
279 if (duration) {
280 setValueEased_Anim(&d->pos, destY, duration);
281 }
282 else {
283 setValue_Anim(&d->pos, destY, 0);
284 }
285 if (d->overscroll && widgetMode_Touch(d->widget) == momentum_WidgetTouchMode) {
286 const int osDelta = overscroll_SmoothScroll_(d);
287 if (osDelta) {
288 const float remaining = stopWidgetMomentum_Touch(d->widget);
289 duration = iMini(1000, 50 * sqrt(remaining / gap_UI));
290 setValue_Anim(&d->pos, osDelta < 0 ? 0 : d->max, duration);
291 d->pos.flags = bounce_AnimFlag | easeOut_AnimFlag | softer_AnimFlag;
292// printf("remaining: %f dur: %d\n", remaining, duration);
293 d->pos.bounce = (osDelta < 0 ? -1 : 1) *
294 iMini(5 * d->overscroll, remaining * remaining * 0.00005f);
295 }
296 }
297 if (d->notify) {
298 d->notify(d->widget, offset, duration);
299 }
300}
301
302iBool processEvent_SmoothScroll(iSmoothScroll *d, const SDL_Event *ev) {
303 if (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode) {
304 const int osDelta = overscroll_SmoothScroll_(d);
305 if (osDelta) {
306 move_SmoothScroll(d, -osDelta, 100 * sqrt(iAbs(osDelta) / gap_UI));
307 d->pos.flags = easeOut_AnimFlag | muchSofter_AnimFlag;
308 }
309 return iTrue;
310 }
311 return iFalse;
312}
313
314
204enum iRequestState { 315enum iRequestState {
205 blank_RequestState, 316 blank_RequestState,
206 fetching_RequestState, 317 fetching_RequestState,
@@ -274,7 +385,9 @@ struct Impl_DocumentWidget {
274 iInt2 contextPos; /* coordinates of latest right click */ 385 iInt2 contextPos; /* coordinates of latest right click */
275 iString pendingGotoHeading; 386 iString pendingGotoHeading;
276 float initNormScrollY; 387 float initNormScrollY;
277 iAnim scrollY; 388// iAnim scrollY;
389// int overscroll;
390 iSmoothScroll scrollY;
278 iAnim sideOpacity; 391 iAnim sideOpacity;
279 iAnim altTextOpacity; 392 iAnim altTextOpacity;
280 iScrollWidget *scroll; 393 iScrollWidget *scroll;
@@ -287,7 +400,6 @@ struct Impl_DocumentWidget {
287 iDrawBufs * drawBufs; /* dynamic state for drawing */ 400 iDrawBufs * drawBufs; /* dynamic state for drawing */
288 iTranslation * translation; 401 iTranslation * translation;
289 iWidget * phoneToolbar; 402 iWidget * phoneToolbar;
290 int overscroll;
291 int pinchZoomInitial; 403 int pinchZoomInitial;
292 int pinchZoomPosted; 404 int pinchZoomPosted;
293}; 405};
@@ -302,7 +414,6 @@ void init_DocumentWidget(iDocumentWidget *d) {
302 init_PersistentDocumentState(&d->mod); 414 init_PersistentDocumentState(&d->mod);
303 d->flags = 0; 415 d->flags = 0;
304 d->phoneToolbar = NULL; 416 d->phoneToolbar = NULL;
305 d->overscroll = deviceType_App() != desktop_AppDeviceType ? 50 * gap_UI : 0;
306 iZap(d->certExpiry); 417 iZap(d->certExpiry);
307 d->certFingerprint = new_Block(0); 418 d->certFingerprint = new_Block(0);
308 d->certFlags = 0; 419 d->certFlags = 0;
@@ -316,7 +427,8 @@ void init_DocumentWidget(iDocumentWidget *d) {
316 d->redirectCount = 0; 427 d->redirectCount = 0;
317 d->ordinalBase = 0; 428 d->ordinalBase = 0;
318 d->initNormScrollY = 0; 429 d->initNormScrollY = 0;
319 init_Anim(&d->scrollY, 0); 430 //init_Anim(&d->scrollY, 0);
431 init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_);
320 d->animWideRunId = 0; 432 d->animWideRunId = 0;
321 init_Anim(&d->animWideRunOffset, 0); 433 init_Anim(&d->animWideRunOffset, 0);
322 d->selectMark = iNullRange; 434 d->selectMark = iNullRange;
@@ -497,19 +609,19 @@ static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) {
497 return zero_Rect(); 609 return zero_Rect();
498 } 610 }
499 const iRect docBounds = documentBounds_DocumentWidget_(d); 611 const iRect docBounds = documentBounds_DocumentWidget_(d);
500 const iInt2 origin = addY_I2(topLeft_Rect(docBounds), -value_Anim(&d->scrollY)); 612 const iInt2 origin = addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY));
501 return moved_Rect(banner->visBounds, origin); 613 return moved_Rect(banner->visBounds, origin);
502} 614}
503 615
504static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 616static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
505 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), 617 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))),
506 value_Anim(&d->scrollY)); 618 pos_SmoothScroll(&d->scrollY));
507} 619}
508 620
509static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { 621static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
510 const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0; 622 const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0;
511 return (iRangei){ value_Anim(&d->scrollY) - margin, 623 return (iRangei){ pos_SmoothScroll(&d->scrollY) - margin,
512 value_Anim(&d->scrollY) + height_Rect(bounds_Widget(constAs_Widget(d))) - 624 pos_SmoothScroll(&d->scrollY) + height_Rect(bounds_Widget(constAs_Widget(d))) -
513 margin }; 625 margin };
514} 626}
515 627
@@ -549,7 +661,7 @@ static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) {
549static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { 661static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) {
550 const int docSize = size_GmDocument(d->doc).y; 662 const int docSize = size_GmDocument(d->doc).y;
551 if (docSize) { 663 if (docSize) {
552 return value_Anim(&d->scrollY) / (float) docSize; 664 return pos_SmoothScroll(&d->scrollY) / (float) docSize;
553 } 665 }
554 return 0; 666 return 0;
555} 667}
@@ -642,7 +754,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
642 const iGmRun * oldHoverLink = d->hoverLink; 754 const iGmRun * oldHoverLink = d->hoverLink;
643 d->hoverPre = NULL; 755 d->hoverPre = NULL;
644 d->hoverLink = NULL; 756 d->hoverLink = NULL;
645 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), value_Anim(&d->scrollY)); 757 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), pos_SmoothScroll(&d->scrollY));
646 if (isHoverAllowed_DocumentWidget_(d)) { 758 if (isHoverAllowed_DocumentWidget_(d)) {
647 iConstForEach(PtrArray, i, &d->visibleLinks) { 759 iConstForEach(PtrArray, i, &d->visibleLinks) {
648 const iGmRun *run = i.ptr; 760 const iGmRun *run = i.ptr;
@@ -701,7 +813,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
701static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { 813static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) {
702 float opacity = 0.0f; 814 float opacity = 0.0f;
703 const iGmRun *banner = siteBanner_GmDocument(d->doc); 815 const iGmRun *banner = siteBanner_GmDocument(d->doc);
704 if (banner && bottom_Rect(banner->visBounds) < value_Anim(&d->scrollY)) { 816 if (banner && bottom_Rect(banner->visBounds) < pos_SmoothScroll(&d->scrollY)) {
705 opacity = 1.0f; 817 opacity = 1.0f;
706 } 818 }
707 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); 819 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0);
@@ -797,12 +909,14 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
797 centerVertically_DocumentWidgetFlag, 909 centerVertically_DocumentWidgetFlag,
798 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") || 910 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") ||
799 !isSuccess_GmStatusCode(d->sourceStatus)); 911 !isSuccess_GmStatusCode(d->sourceStatus));
800 const iRangei visRange = visibleRange_DocumentWidget_(d); 912 const iRangei visRange = visibleRange_DocumentWidget_(d);
801 const iRect bounds = bounds_Widget(as_Widget(d)); 913 const iRect bounds = bounds_Widget(as_Widget(d));
802 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); 914 const int scrollMax = scrollMax_DocumentWidget_(d);
915 setMax_SmoothScroll(&d->scrollY, scrollMax);
916 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax });
803 const int docSize = size_GmDocument(d->doc).y; 917 const int docSize = size_GmDocument(d->doc).y;
804 setThumb_ScrollWidget(d->scroll, 918 setThumb_ScrollWidget(d->scroll,
805 value_Anim(&d->scrollY), 919 pos_SmoothScroll(&d->scrollY),
806 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); 920 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
807 clear_PtrArray(&d->visibleLinks); 921 clear_PtrArray(&d->visibleLinks);
808 clear_PtrArray(&d->visibleWideRuns); 922 clear_PtrArray(&d->visibleWideRuns);
@@ -1016,7 +1130,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1016 translate_Lang(src); 1130 translate_Lang(src);
1017 setSource_DocumentWidget(d, src); 1131 setSource_DocumentWidget(d, src);
1018 updateTheme_DocumentWidget_(d); 1132 updateTheme_DocumentWidget_(d);
1019 init_Anim(&d->scrollY, 0); 1133 reset_SmoothScroll(&d->scrollY);
1020 init_Anim(&d->sideOpacity, 0); 1134 init_Anim(&d->sideOpacity, 0);
1021 init_Anim(&d->altTextOpacity, 0); 1135 init_Anim(&d->altTextOpacity, 0);
1022 resetWideRuns_DocumentWidget_(d); 1136 resetWideRuns_DocumentWidget_(d);
@@ -1224,12 +1338,14 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1224 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; 1338 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag;
1225 set_Block(&d->sourceContent, &resp->body); 1339 set_Block(&d->sourceContent, &resp->body);
1226 updateDocument_DocumentWidget_(d, resp, iTrue); 1340 updateDocument_DocumentWidget_(d, resp, iTrue);
1227 init_Anim(&d->scrollY, d->initNormScrollY * size_GmDocument(d->doc).y);
1228 init_Anim(&d->altTextOpacity, 0); 1341 init_Anim(&d->altTextOpacity, 0);
1229 d->state = ready_RequestState; 1342 d->state = ready_RequestState;
1343 reset_SmoothScroll(&d->scrollY);
1344 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
1230 updateSideOpacity_DocumentWidget_(d, iFalse); 1345 updateSideOpacity_DocumentWidget_(d, iFalse);
1231 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 1346 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
1232 updateVisible_DocumentWidget_(d); 1347 updateVisible_DocumentWidget_(d);
1348 move_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */
1233 cacheDocumentGlyphs_DocumentWidget_(d); 1349 cacheDocumentGlyphs_DocumentWidget_(d);
1234 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1350 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1235 return iTrue; 1351 return iTrue;
@@ -1252,26 +1368,13 @@ static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) {
1252 if (isFinished_Anim(&d->animWideRunOffset)) { 1368 if (isFinished_Anim(&d->animWideRunOffset)) {
1253 d->animWideRunId = 0; 1369 d->animWideRunId = 0;
1254 } 1370 }
1255 if (!isFinished_Anim(&d->scrollY) || !isFinished_Anim(&d->animWideRunOffset)) { 1371 if (!isFinished_SmoothScroll(&d->scrollY) || !isFinished_Anim(&d->animWideRunOffset)) {
1256 addTicker_App(refreshWhileScrolling_DocumentWidget_, d); 1372 addTicker_App(refreshWhileScrolling_DocumentWidget_, d);
1257 } 1373 }
1258} 1374}
1259 1375
1260static int overscroll_DocumentWidget_(const iDocumentWidget *d) { 1376static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t duration) {
1261 if (d->overscroll) { 1377 iDocumentWidget *d = any;
1262 const int y = value_Anim(&d->scrollY);
1263 if (y <= 0) {
1264 return y;
1265 }
1266 const int scrollMax = scrollMax_DocumentWidget_(d);
1267 if (y >= scrollMax) {
1268 return y - scrollMax;
1269 }
1270 }
1271 return 0;
1272}
1273
1274static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) {
1275 /* Get rid of link numbers when scrolling. */ 1378 /* Get rid of link numbers when scrolling. */
1276 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { 1379 if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
1277 setLinkNumberMode_DocumentWidget_(d, iFalse); 1380 setLinkNumberMode_DocumentWidget_(d, iFalse);
@@ -1283,42 +1386,6 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
1283 showToolbars_Window(get_Window(), offset < 0); 1386 showToolbars_Window(get_Window(), offset < 0);
1284 } 1387 }
1285 } 1388 }
1286#if !defined (iPlatformMobile)
1287 if (!prefs_App()->smoothScrolling) {
1288 duration = 0; /* always instant */
1289 }
1290#endif
1291 int destY = targetValue_Anim(&d->scrollY) + offset;
1292 if (destY < -2 * d->overscroll) {
1293 destY = -2 * d->overscroll;
1294 }
1295 const int scrollMax = scrollMax_DocumentWidget_(d);
1296 if (scrollMax > 0) {
1297 if (destY >= scrollMax + 2 * d->overscroll) {
1298 destY = scrollMax + 2 * d->overscroll;
1299 }
1300 }
1301 else {
1302 destY = 0;
1303 }
1304 if (duration) {
1305 setValueEased_Anim(&d->scrollY, destY, duration);
1306 }
1307 else {
1308 setValue_Anim(&d->scrollY, destY, 0);
1309 }
1310 if (d->overscroll && widgetMode_Touch(as_Widget(d)) == momentum_WidgetTouchMode) {
1311 const int osDelta = overscroll_DocumentWidget_(d);
1312 if (osDelta) {
1313 const float remaining = stopWidgetMomentum_Touch(as_Widget(d));
1314 duration = iMini(1000, 50 * sqrt(remaining / gap_UI));
1315 setValue_Anim(&d->scrollY, osDelta < 0 ? 0 : scrollMax, duration);
1316 d->scrollY.flags = bounce_AnimFlag | easeOut_AnimFlag | softer_AnimFlag;
1317 printf("remaining: %f dur: %d\n", remaining, duration);
1318 d->scrollY.bounce = (osDelta < 0 ? -1 : 1) *
1319 iMini(10 * d->overscroll, remaining * remaining * 0.00005f);
1320 }
1321 }
1322 updateVisible_DocumentWidget_(d); 1389 updateVisible_DocumentWidget_(d);
1323 refresh_Widget(as_Widget(d)); 1390 refresh_Widget(as_Widget(d));
1324 if (duration > 0) { 1391 if (duration > 0) {
@@ -1327,6 +1394,27 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
1327 } 1394 }
1328} 1395}
1329 1396
1397static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) {
1398// /* Get rid of link numbers when scrolling. */
1399// if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) {
1400// setLinkNumberMode_DocumentWidget_(d, iFalse);
1401// invalidateVisibleLinks_DocumentWidget_(d);
1402// }
1403// /* Show and hide toolbar on scroll. */
1404// if (deviceType_App() == phone_AppDeviceType) {
1405// if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) {
1406// showToolbars_Window(get_Window(), offset < 0);
1407// }
1408// }
1409 move_SmoothScroll(&d->scrollY, offset, duration);
1410// updateVisible_DocumentWidget_(d);
1411// refresh_Widget(as_Widget(d));
1412// if (duration > 0) {
1413// iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue);
1414// addTicker_App(refreshWhileScrolling_DocumentWidget_, d);
1415// }
1416}
1417
1330static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { 1418static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
1331 smoothScroll_DocumentWidget_(d, offset, 0 /* instantly */); 1419 smoothScroll_DocumentWidget_(d, offset, 0 /* instantly */);
1332} 1420}
@@ -1335,7 +1423,7 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool ce
1335 if (!hasSiteBanner_GmDocument(d->doc)) { 1423 if (!hasSiteBanner_GmDocument(d->doc)) {
1336 documentY += d->pageMargin * gap_UI; 1424 documentY += d->pageMargin * gap_UI;
1337 } 1425 }
1338 init_Anim(&d->scrollY, 1426 init_Anim(&d->scrollY.pos,
1339 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 1427 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2
1340 : lineHeight_Text(paragraph_FontId))); 1428 : lineHeight_Text(paragraph_FontId)));
1341 scroll_DocumentWidget_(d, 0); /* clamp it */ 1429 scroll_DocumentWidget_(d, 0); /* clamp it */
@@ -1449,7 +1537,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1449 break; 1537 break;
1450 } 1538 }
1451 case categorySuccess_GmStatusCode: 1539 case categorySuccess_GmStatusCode:
1452 init_Anim(&d->scrollY, 0); 1540 reset_SmoothScroll(&d->scrollY);
1453 reset_GmDocument(d->doc); /* new content incoming */ 1541 reset_GmDocument(d->doc); /* new content incoming */
1454 resetWideRuns_DocumentWidget_(d); 1542 resetWideRuns_DocumentWidget_(d);
1455 updateDocument_DocumentWidget_(d, resp, iTrue); 1543 updateDocument_DocumentWidget_(d, resp, iTrue);
@@ -2084,7 +2172,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2084 } 2172 }
2085 updateFetchProgress_DocumentWidget_(d); 2173 updateFetchProgress_DocumentWidget_(d);
2086 checkResponse_DocumentWidget_(d); 2174 checkResponse_DocumentWidget_(d);
2087 init_Anim(&d->scrollY, d->initNormScrollY * size_GmDocument(d->doc).y); 2175 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */
2088 d->state = ready_RequestState; 2176 d->state = ready_RequestState;
2089 /* The response may be cached. */ { 2177 /* The response may be cached. */ {
2090 if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && 2178 if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") &&
@@ -2249,7 +2337,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2249 return iTrue; 2337 return iTrue;
2250 } 2338 }
2251 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 2339 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
2252 init_Anim(&d->scrollY, arg_Command(cmd)); 2340 init_Anim(&d->scrollY.pos, arg_Command(cmd));
2253 updateVisible_DocumentWidget_(d); 2341 updateVisible_DocumentWidget_(d);
2254 return iTrue; 2342 return iTrue;
2255 } 2343 }
@@ -2267,7 +2355,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2267 return iTrue; 2355 return iTrue;
2268 } 2356 }
2269 else if (equal_Command(cmd, "scroll.top") && document_App() == d) { 2357 else if (equal_Command(cmd, "scroll.top") && document_App() == d) {
2270 init_Anim(&d->scrollY, 0); 2358 init_Anim(&d->scrollY.pos, 0);
2271 invalidate_VisBuf(d->visBuf); 2359 invalidate_VisBuf(d->visBuf);
2272 scroll_DocumentWidget_(d, 0); 2360 scroll_DocumentWidget_(d, 0);
2273 updateVisible_DocumentWidget_(d); 2361 updateVisible_DocumentWidget_(d);
@@ -2275,7 +2363,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2275 return iTrue; 2363 return iTrue;
2276 } 2364 }
2277 else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { 2365 else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) {
2278 init_Anim(&d->scrollY, scrollMax_DocumentWidget_(d)); 2366 init_Anim(&d->scrollY.pos, d->scrollY.max);
2279 invalidate_VisBuf(d->visBuf); 2367 invalidate_VisBuf(d->visBuf);
2280 scroll_DocumentWidget_(d, 0); 2368 scroll_DocumentWidget_(d, 0);
2281 updateVisible_DocumentWidget_(d); 2369 updateVisible_DocumentWidget_(d);
@@ -2438,7 +2526,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2438 2526
2439static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { 2527static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
2440 const iRect docBounds = documentBounds_DocumentWidget_(d); 2528 const iRect docBounds = documentBounds_DocumentWidget_(d);
2441 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -value_Anim(&d->scrollY))); 2529 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY)));
2442} 2530}
2443 2531
2444static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 2532static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
@@ -2622,12 +2710,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2622 if (isMetricsChange_UserEvent(ev)) { 2710 if (isMetricsChange_UserEvent(ev)) {
2623 updateSize_DocumentWidget(d); 2711 updateSize_DocumentWidget(d);
2624 } 2712 }
2625 else if (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode) { 2713 else if (processEvent_SmoothScroll(&d->scrollY, ev)) {
2626 const int osDelta = overscroll_DocumentWidget_(d);
2627 if (osDelta) {
2628 smoothScroll_DocumentWidget_(d, -osDelta, 100 * sqrt(iAbs(osDelta) / gap_UI));
2629 d->scrollY.flags = easeOut_AnimFlag | muchSofter_AnimFlag;
2630 }
2631 return iTrue; 2714 return iTrue;
2632 } 2715 }
2633 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 2716 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
@@ -2715,8 +2798,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2715 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 2798 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
2716 const iInt2 mouseCoord = mouseCoord_Window(get_Window()); 2799 const iInt2 mouseCoord = mouseCoord_Window(get_Window());
2717 if (isPerPixel_MouseWheelEvent(&ev->wheel)) { 2800 if (isPerPixel_MouseWheelEvent(&ev->wheel)) {
2718 stop_Anim(&d->scrollY); 2801 const iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
2719 iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); 2802 stop_Anim(&d->scrollY.pos);
2720 scroll_DocumentWidget_(d, -wheel.y); 2803 scroll_DocumentWidget_(d, -wheel.y);
2721 scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0); 2804 scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0);
2722 } 2805 }
@@ -2732,7 +2815,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2732 -3 * amount * lineHeight_Text(paragraph_FontId), 2815 -3 * amount * lineHeight_Text(paragraph_FontId),
2733 smoothDuration_DocumentWidget_ * 2816 smoothDuration_DocumentWidget_ *
2734 /* accelerated speed for repeated wheelings */ 2817 /* accelerated speed for repeated wheelings */
2735 (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); 2818 (!isFinished_SmoothScroll(&d->scrollY) && pos_Anim(&d->scrollY.pos) < 0.25f ? 0.5f : 1.0f));
2736 scrollWideBlock_DocumentWidget_( 2819 scrollWideBlock_DocumentWidget_(
2737 d, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167); 2820 d, mouseCoord, -3 * ev->wheel.x * lineHeight_Text(paragraph_FontId), 167);
2738 } 2821 }
@@ -3239,7 +3322,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
3239 } 3322 }
3240 if (~run->flags & decoration_GmRunFlag) { 3323 if (~run->flags & decoration_GmRunFlag) {
3241 const iInt2 visPos = 3324 const iInt2 visPos =
3242 add_I2(run->bounds.pos, addY_I2(d->viewPos, -value_Anim(&d->widget->scrollY))); 3325 add_I2(run->bounds.pos, addY_I2(d->viewPos, -pos_SmoothScroll(&d->widget->scrollY)));
3243 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }; 3326 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) };
3244 if (rangeRect.size.x) { 3327 if (rangeRect.size.x) {
3245 fillRect_Paint(&d->paint, rangeRect, color); 3328 fillRect_Paint(&d->paint, rangeRect, color);
@@ -3259,7 +3342,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
3259 (contains_Range(&url, mark.end) || url.end == mark.end)) { 3342 (contains_Range(&url, mark.end) || url.end == mark.end)) {
3260 fillRect_Paint( 3343 fillRect_Paint(
3261 &d->paint, 3344 &d->paint,
3262 moved_Rect(run->visBounds, addY_I2(d->viewPos, -value_Anim(&d->widget->scrollY))), 3345 moved_Rect(run->visBounds, addY_I2(d->viewPos, -pos_SmoothScroll(&d->widget->scrollY))),
3263 color); 3346 color);
3264 } 3347 }
3265 } 3348 }
@@ -3704,7 +3787,7 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
3704 bottomLeft_Rect(bounds), 3787 bottomLeft_Rect(bounds),
3705 init_I2(margin, 3788 init_I2(margin,
3706 -margin + -dbuf->timestampBuf->size.y + 3789 -margin + -dbuf->timestampBuf->size.y +
3707 iMax(0, scrollMax_DocumentWidget_(d) - value_Anim(&d->scrollY)))), 3790 iMax(0, d->scrollY.max - pos_SmoothScroll(&d->scrollY)))),
3708 tmQuoteIcon_ColorId); 3791 tmQuoteIcon_ColorId);
3709 } 3792 }
3710 unsetClip_Paint(&p); 3793 unsetClip_Paint(&p);
@@ -3949,7 +4032,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
3949 }; 4032 };
3950 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 4033 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
3951 setClip_Paint(&ctx.paint, bounds); 4034 setClip_Paint(&ctx.paint, bounds);
3952 int yTop = docBounds.pos.y - value_Anim(&d->scrollY) + overscroll_DocumentWidget_(d) * 0.667f; 4035 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY);
3953 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); 4036 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
3954 /* Text markers. */ 4037 /* Text markers. */
3955 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 4038 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
@@ -4021,7 +4104,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4021 const int altFont = uiLabel_FontId; 4104 const int altFont = uiLabel_FontId;
4022 const int wrap = docBounds.size.x - 2 * margin; 4105 const int wrap = docBounds.size.x - 2 * margin;
4023 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), 4106 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos),
4024 -value_Anim(&d->scrollY)); 4107 -pos_SmoothScroll(&d->scrollY));
4025 const iInt2 textSize = advanceWrapRange_Text(altFont, wrap, meta->altText); 4108 const iInt2 textSize = advanceWrapRange_Text(altFont, wrap, meta->altText);
4026 pos.y -= textSize.y + gap_UI; 4109 pos.y -= textSize.y + gap_UI;
4027 pos.y = iMax(pos.y, top_Rect(bounds)); 4110 pos.y = iMax(pos.y, top_Rect(bounds));