diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-21 19:51:22 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-04-21 19:51:22 +0300 |
commit | 2acc486899b7253dbbc1656ea444c596d8af16ad (patch) | |
tree | 67a4ed03353e710c8a1164c1173f025f8f915e5c | |
parent | 2e7b41f2d20cee278514b84ccf131062a62b3fee (diff) |
Mobile: Working on scroll bounce behavior
-rw-r--r-- | src/app.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 54 | ||||
-rw-r--r-- | src/ui/touch.c | 25 | ||||
-rw-r--r-- | src/ui/touch.h | 12 | ||||
-rw-r--r-- | src/ui/util.c | 23 | ||||
-rw-r--r-- | src/ui/util.h | 4 |
6 files changed, 101 insertions, 18 deletions
@@ -61,6 +61,7 @@ enum iUserEventCode { | |||
61 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will | 61 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will |
62 | take, it could turn into a tap-and-hold for example. */ | 62 | take, it could turn into a tap-and-hold for example. */ |
63 | widgetTapBegins_UserEventCode = 4, | 63 | widgetTapBegins_UserEventCode = 4, |
64 | widgetTouchEnds_UserEventCode = 5, /* finger lifted, but momentum may continue */ | ||
64 | }; | 65 | }; |
65 | 66 | ||
66 | const iString *execPath_App (void); | 67 | const iString *execPath_App (void); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index c3728b75..87c724de 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -287,6 +287,7 @@ struct Impl_DocumentWidget { | |||
287 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | 287 | iDrawBufs * drawBufs; /* dynamic state for drawing */ |
288 | iTranslation * translation; | 288 | iTranslation * translation; |
289 | iWidget * phoneToolbar; | 289 | iWidget * phoneToolbar; |
290 | int overscroll; | ||
290 | int pinchZoomInitial; | 291 | int pinchZoomInitial; |
291 | int pinchZoomPosted; | 292 | int pinchZoomPosted; |
292 | }; | 293 | }; |
@@ -299,8 +300,9 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
299 | setId_Widget(w, "document000"); | 300 | setId_Widget(w, "document000"); |
300 | setFlags_Widget(w, hover_WidgetFlag, iTrue); | 301 | setFlags_Widget(w, hover_WidgetFlag, iTrue); |
301 | init_PersistentDocumentState(&d->mod); | 302 | init_PersistentDocumentState(&d->mod); |
302 | d->flags = 0; | 303 | d->flags = 0; |
303 | d->phoneToolbar = NULL; | 304 | d->phoneToolbar = NULL; |
305 | d->overscroll = deviceType_App() != desktop_AppDeviceType ? 50 * gap_UI : 0; | ||
304 | iZap(d->certExpiry); | 306 | iZap(d->certExpiry); |
305 | d->certFingerprint = new_Block(0); | 307 | d->certFingerprint = new_Block(0); |
306 | d->certFlags = 0; | 308 | d->certFlags = 0; |
@@ -1255,6 +1257,20 @@ static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { | |||
1255 | } | 1257 | } |
1256 | } | 1258 | } |
1257 | 1259 | ||
1260 | static int overscroll_DocumentWidget_(const iDocumentWidget *d) { | ||
1261 | if (d->overscroll) { | ||
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 | |||
1258 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { | 1274 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { |
1259 | /* Get rid of link numbers when scrolling. */ | 1275 | /* Get rid of link numbers when scrolling. */ |
1260 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { | 1276 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { |
@@ -1267,19 +1283,19 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur | |||
1267 | showToolbars_Window(get_Window(), offset < 0); | 1283 | showToolbars_Window(get_Window(), offset < 0); |
1268 | } | 1284 | } |
1269 | } | 1285 | } |
1286 | #if !defined (iPlatformMobile) | ||
1270 | if (!prefs_App()->smoothScrolling) { | 1287 | if (!prefs_App()->smoothScrolling) { |
1271 | duration = 0; /* always instant */ | 1288 | duration = 0; /* always instant */ |
1272 | } | 1289 | } |
1290 | #endif | ||
1273 | int destY = targetValue_Anim(&d->scrollY) + offset; | 1291 | int destY = targetValue_Anim(&d->scrollY) + offset; |
1274 | if (destY < 0) { | 1292 | if (destY < -2 * d->overscroll) { |
1275 | destY = 0; | 1293 | destY = -2 * d->overscroll; |
1276 | stopWidgetMomentum_Touch(as_Widget(d)); | ||
1277 | } | 1294 | } |
1278 | const int scrollMax = scrollMax_DocumentWidget_(d); | 1295 | const int scrollMax = scrollMax_DocumentWidget_(d); |
1279 | if (scrollMax > 0) { | 1296 | if (scrollMax > 0) { |
1280 | if (destY >= scrollMax) { | 1297 | if (destY >= scrollMax + 2 * d->overscroll) { |
1281 | stopWidgetMomentum_Touch(as_Widget(d)); | 1298 | destY = scrollMax + 2 * d->overscroll; |
1282 | destY = scrollMax; | ||
1283 | } | 1299 | } |
1284 | } | 1300 | } |
1285 | else { | 1301 | else { |
@@ -1291,6 +1307,18 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur | |||
1291 | else { | 1307 | else { |
1292 | setValue_Anim(&d->scrollY, destY, 0); | 1308 | setValue_Anim(&d->scrollY, destY, 0); |
1293 | } | 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 | } | ||
1294 | updateVisible_DocumentWidget_(d); | 1322 | updateVisible_DocumentWidget_(d); |
1295 | refresh_Widget(as_Widget(d)); | 1323 | refresh_Widget(as_Widget(d)); |
1296 | if (duration > 0) { | 1324 | if (duration > 0) { |
@@ -2594,6 +2622,14 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2594 | if (isMetricsChange_UserEvent(ev)) { | 2622 | if (isMetricsChange_UserEvent(ev)) { |
2595 | updateSize_DocumentWidget(d); | 2623 | updateSize_DocumentWidget(d); |
2596 | } | 2624 | } |
2625 | else if (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode) { | ||
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; | ||
2632 | } | ||
2597 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { | 2633 | else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { |
2598 | if (!handleCommand_DocumentWidget_(d, command_UserEvent(ev))) { | 2634 | if (!handleCommand_DocumentWidget_(d, command_UserEvent(ev))) { |
2599 | /* Base class commands. */ | 2635 | /* Base class commands. */ |
@@ -3913,7 +3949,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3913 | }; | 3949 | }; |
3914 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); | 3950 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); |
3915 | setClip_Paint(&ctx.paint, bounds); | 3951 | setClip_Paint(&ctx.paint, bounds); |
3916 | const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); | 3952 | int yTop = docBounds.pos.y - value_Anim(&d->scrollY) + overscroll_DocumentWidget_(d) * 0.667f; |
3917 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); | 3953 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); |
3918 | /* Text markers. */ | 3954 | /* Text markers. */ |
3919 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | 3955 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; |
diff --git a/src/ui/touch.c b/src/ui/touch.c index 834fe4f8..22f22f9b 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -686,6 +686,9 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
686 | //dispatchMotion_Touch_(touch->startPos, 0); | 686 | //dispatchMotion_Touch_(touch->startPos, 0); |
687 | } | 687 | } |
688 | else { | 688 | else { |
689 | if (touch->affinity) { | ||
690 | dispatchNotification_Touch_(touch, widgetTouchEnds_UserEventCode); | ||
691 | } | ||
689 | dispatchButtonUp_Touch_(pos); | 692 | dispatchButtonUp_Touch_(pos); |
690 | setHover_Widget(NULL); | 693 | setHover_Widget(NULL); |
691 | } | 694 | } |
@@ -696,14 +699,34 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
696 | return iTrue; | 699 | return iTrue; |
697 | } | 700 | } |
698 | 701 | ||
699 | void stopWidgetMomentum_Touch(iWidget *widget) { | 702 | float stopWidgetMomentum_Touch(const iWidget *widget) { |
700 | iTouchState *d = touchState_(); | 703 | iTouchState *d = touchState_(); |
704 | float remaining = 0.0f; | ||
701 | iForEach(Array, i, d->moms) { | 705 | iForEach(Array, i, d->moms) { |
702 | iMomentum *mom = i.value; | 706 | iMomentum *mom = i.value; |
703 | if (mom->affinity == widget) { | 707 | if (mom->affinity == widget) { |
708 | remaining = length_F3(mom->velocity); | ||
704 | remove_ArrayIterator(&i); | 709 | remove_ArrayIterator(&i); |
705 | } | 710 | } |
706 | } | 711 | } |
712 | return remaining; | ||
713 | } | ||
714 | |||
715 | enum iWidgetTouchMode widgetMode_Touch(const iWidget *widget) { | ||
716 | iTouchState *d = touchState_(); | ||
717 | iConstForEach(Array, i, d->touches) { | ||
718 | const iTouch *touch = i.value; | ||
719 | if (touch->affinity == widget) { | ||
720 | return touch_WidgetTouchMode; | ||
721 | } | ||
722 | } | ||
723 | iConstForEach(Array, j, d->moms) { | ||
724 | const iMomentum *mom = j.value; | ||
725 | if (mom->affinity == widget) { | ||
726 | return momentum_WidgetTouchMode; | ||
727 | } | ||
728 | } | ||
729 | return none_WidgetTouchMode; | ||
707 | } | 730 | } |
708 | 731 | ||
709 | void widgetDestroyed_Touch(iWidget *widget) { | 732 | void widgetDestroyed_Touch(iWidget *widget) { |
diff --git a/src/ui/touch.h b/src/ui/touch.h index 0a24248b..1a6fb350 100644 --- a/src/ui/touch.h +++ b/src/ui/touch.h | |||
@@ -27,10 +27,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | 27 | ||
28 | iDeclareType(Widget) | 28 | iDeclareType(Widget) |
29 | 29 | ||
30 | enum iWidgetTouchMode { | ||
31 | none_WidgetTouchMode, | ||
32 | touch_WidgetTouchMode, | ||
33 | momentum_WidgetTouchMode, | ||
34 | }; | ||
35 | |||
30 | iBool processEvent_Touch (const SDL_Event *); | 36 | iBool processEvent_Touch (const SDL_Event *); |
31 | void update_Touch (void); | 37 | void update_Touch (void); |
32 | void stopWidgetMomentum_Touch(iWidget *widget); | 38 | |
33 | void widgetDestroyed_Touch (iWidget *widget); | 39 | float stopWidgetMomentum_Touch (const iWidget *widget); |
40 | enum iWidgetTouchMode widgetMode_Touch (const iWidget *widget); | ||
41 | void widgetDestroyed_Touch (iWidget *widget); | ||
34 | 42 | ||
35 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ | 43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ |
36 | size_t numFingers_Touch (void); | 44 | size_t numFingers_Touch (void); |
diff --git a/src/ui/util.c b/src/ui/util.c index 8e808bd2..877ec7c1 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -246,6 +246,7 @@ iBool isFinished_Anim(const iAnim *d) { | |||
246 | void init_Anim(iAnim *d, float value) { | 246 | void init_Anim(iAnim *d, float value) { |
247 | d->due = d->when = SDL_GetTicks(); | 247 | d->due = d->when = SDL_GetTicks(); |
248 | d->from = d->to = value; | 248 | d->from = d->to = value; |
249 | d->bounce = 0.0f; | ||
249 | d->flags = 0; | 250 | d->flags = 0; |
250 | } | 251 | } |
251 | 252 | ||
@@ -276,20 +277,29 @@ static float valueAt_Anim_(const iAnim *d, const uint32_t now) { | |||
276 | return d->from; | 277 | return d->from; |
277 | } | 278 | } |
278 | float t = pos_Anim_(d, now); | 279 | float t = pos_Anim_(d, now); |
279 | const iBool isSoft = (d->flags & softer_AnimFlag) != 0; | 280 | const iBool isSoft = (d->flags & softer_AnimFlag) != 0; |
281 | const iBool isVerySoft = (d->flags & muchSofter_AnimFlag) != 0; | ||
280 | if ((d->flags & easeBoth_AnimFlag) == easeBoth_AnimFlag) { | 282 | if ((d->flags & easeBoth_AnimFlag) == easeBoth_AnimFlag) { |
281 | t = easeBoth_(t); | 283 | t = easeBoth_(t); |
282 | if (isSoft) t = easeBoth_(t); | 284 | if (isSoft) t = easeBoth_(t); |
285 | if (isVerySoft) t = easeBoth_(easeBoth_(t)); | ||
283 | } | 286 | } |
284 | else if (d->flags & easeIn_AnimFlag) { | 287 | else if (d->flags & easeIn_AnimFlag) { |
285 | t = easeIn_(t); | 288 | t = easeIn_(t); |
286 | if (isSoft) t = easeIn_(t); | 289 | if (isSoft) t = easeIn_(t); |
290 | if (isVerySoft) t = easeIn_(easeIn_(t)); | ||
287 | } | 291 | } |
288 | else if (d->flags & easeOut_AnimFlag) { | 292 | else if (d->flags & easeOut_AnimFlag) { |
289 | t = easeOut_(t); | 293 | t = easeOut_(t); |
290 | if (isSoft) t = easeOut_(t); | 294 | if (isSoft) t = easeOut_(t); |
295 | if (isVerySoft) t = easeOut_(easeOut_(t)); | ||
291 | } | 296 | } |
292 | return d->from * (1.0f - t) + d->to * t; | 297 | float value = d->from * (1.0f - t) + d->to * t; |
298 | if (d->flags & bounce_AnimFlag) { | ||
299 | t = (1.0f - easeOut_(easeOut_(t))) * easeOut_(t); | ||
300 | value += d->bounce * t; | ||
301 | } | ||
302 | return value; | ||
293 | } | 303 | } |
294 | 304 | ||
295 | void setValue_Anim(iAnim *d, float to, uint32_t span) { | 305 | void setValue_Anim(iAnim *d, float to, uint32_t span) { |
@@ -304,6 +314,7 @@ void setValue_Anim(iAnim *d, float to, uint32_t span) { | |||
304 | d->when = now; | 314 | d->when = now; |
305 | d->due = now + span; | 315 | d->due = now + span; |
306 | } | 316 | } |
317 | d->bounce = 0; | ||
307 | } | 318 | } |
308 | 319 | ||
309 | void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { | 320 | void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { |
@@ -316,6 +327,7 @@ void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { | |||
316 | d->to = to; | 327 | d->to = to; |
317 | d->when = now; | 328 | d->when = now; |
318 | d->due = d->when + span; | 329 | d->due = d->when + span; |
330 | d->bounce = 0; | ||
319 | } | 331 | } |
320 | } | 332 | } |
321 | 333 | ||
@@ -333,9 +345,10 @@ void setValueEased_Anim(iAnim *d, float to, uint32_t span) { | |||
333 | d->from = valueAt_Anim_(d, now); | 345 | d->from = valueAt_Anim_(d, now); |
334 | d->flags = easeOut_AnimFlag; | 346 | d->flags = easeOut_AnimFlag; |
335 | } | 347 | } |
336 | d->to = to; | 348 | d->to = to; |
337 | d->when = now; | 349 | d->when = now; |
338 | d->due = now + span; | 350 | d->due = now + span; |
351 | d->bounce = 0; | ||
339 | } | 352 | } |
340 | 353 | ||
341 | void setFlags_Anim(iAnim *d, int flags, iBool set) { | 354 | void setFlags_Anim(iAnim *d, int flags, iBool set) { |
diff --git a/src/ui/util.h b/src/ui/util.h index d39a00fa..191b8a87 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -105,10 +105,12 @@ enum iAnimFlag { | |||
105 | easeOut_AnimFlag = iBit(3), | 105 | easeOut_AnimFlag = iBit(3), |
106 | easeBoth_AnimFlag = easeIn_AnimFlag | easeOut_AnimFlag, | 106 | easeBoth_AnimFlag = easeIn_AnimFlag | easeOut_AnimFlag, |
107 | softer_AnimFlag = iBit(4), | 107 | softer_AnimFlag = iBit(4), |
108 | muchSofter_AnimFlag = iBit(5), | ||
109 | bounce_AnimFlag = iBit(6), | ||
108 | }; | 110 | }; |
109 | 111 | ||
110 | struct Impl_Anim { | 112 | struct Impl_Anim { |
111 | float from, to; | 113 | float from, to, bounce; |
112 | uint32_t when, due; | 114 | uint32_t when, due; |
113 | int flags; | 115 | int flags; |
114 | }; | 116 | }; |