diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-23 14:53:28 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-23 14:53:28 +0300 |
commit | 0c9806529bc33f02431570ba0cc2d48039b8afe1 (patch) | |
tree | c8915ed3b93aa7b9b5c67bea100e800f0cda0598 /src | |
parent | 97fd3c886ee1aa069784da881dc206741d282b3c (diff) |
Improved smooth scrolling
Use proper easing curves for a smoother animation. Ensure that mouse hover on links is disabled when scrolling.
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/documentwidget.c | 268 | ||||
-rw-r--r-- | src/ui/util.c | 64 | ||||
-rw-r--r-- | src/ui/util.h | 28 |
3 files changed, 226 insertions, 134 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 11d84414..9dea6cbb 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -144,7 +144,7 @@ struct Impl_OutlineItem { | |||
144 | 144 | ||
145 | static void animatePlayers_DocumentWidget_(iDocumentWidget *d); | 145 | static void animatePlayers_DocumentWidget_(iDocumentWidget *d); |
146 | 146 | ||
147 | static const int smoothSpeed_DocumentWidget_ = 120; /* unit: gap_Text per second */ | 147 | static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ |
148 | static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ | 148 | static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ |
149 | static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */ | 149 | static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */ |
150 | static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */ | 150 | static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */ |
@@ -156,6 +156,12 @@ enum iRequestState { | |||
156 | ready_RequestState, | 156 | ready_RequestState, |
157 | }; | 157 | }; |
158 | 158 | ||
159 | enum iDocumentWidgetFlag { | ||
160 | selecting_DocumentWidgetFlag = iBit(1), | ||
161 | noHoverWhileScrolling_DocumentWidgetFlag = iBit(2), | ||
162 | showLinkNumbers_DocumentWidgetFlag = iBit(3), | ||
163 | }; | ||
164 | |||
159 | struct Impl_DocumentWidget { | 165 | struct Impl_DocumentWidget { |
160 | iWidget widget; | 166 | iWidget widget; |
161 | enum iRequestState state; | 167 | enum iRequestState state; |
@@ -172,7 +178,8 @@ struct Impl_DocumentWidget { | |||
172 | iDate certExpiry; | 178 | iDate certExpiry; |
173 | iString * certSubject; | 179 | iString * certSubject; |
174 | int redirectCount; | 180 | int redirectCount; |
175 | iBool selecting; | 181 | int flags; |
182 | // iBool selecting; | ||
176 | iRangecc selectMark; | 183 | iRangecc selectMark; |
177 | iRangecc foundMark; | 184 | iRangecc foundMark; |
178 | int pageMargin; | 185 | int pageMargin; |
@@ -183,21 +190,22 @@ struct Impl_DocumentWidget { | |||
183 | int playerTimer; | 190 | int playerTimer; |
184 | const iGmRun * hoverLink; | 191 | const iGmRun * hoverLink; |
185 | const iGmRun * contextLink; | 192 | const iGmRun * contextLink; |
186 | iBool noHoverWhileScrolling; | 193 | // iBool noHoverWhileScrolling; |
187 | iBool showLinkNumbers; | 194 | // iBool showLinkNumbers; |
188 | const iGmRun * firstVisibleRun; | 195 | const iGmRun * firstVisibleRun; |
189 | const iGmRun * lastVisibleRun; | 196 | const iGmRun * lastVisibleRun; |
190 | iClick click; | 197 | iClick click; |
191 | float initNormScrollY; | 198 | float initNormScrollY; |
192 | int scrollY; | 199 | // int scrollY; |
193 | iScrollWidget *scroll; | 200 | // int smoothScroll; |
194 | int smoothScroll; | 201 | // int smoothSpeed; |
195 | int smoothSpeed; | 202 | // int smoothLastOffset; |
196 | int smoothLastOffset; | 203 | // iBool smoothContinue; |
197 | iBool smoothContinue; | 204 | iAnim scrollY; |
198 | iAnim sideOpacity; | 205 | iAnim sideOpacity; |
199 | iAnim outlineOpacity; | 206 | iAnim outlineOpacity; |
200 | iArray outline; | 207 | iArray outline; |
208 | iScrollWidget *scroll; | ||
201 | iWidget * menu; | 209 | iWidget * menu; |
202 | iWidget * playerMenu; | 210 | iWidget * playerMenu; |
203 | iVisBuf * visBuf; | 211 | iVisBuf * visBuf; |
@@ -223,19 +231,20 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
223 | d->doc = new_GmDocument(); | 231 | d->doc = new_GmDocument(); |
224 | d->redirectCount = 0; | 232 | d->redirectCount = 0; |
225 | d->initNormScrollY = 0; | 233 | d->initNormScrollY = 0; |
226 | d->scrollY = 0; | 234 | init_Anim(&d->scrollY, 0); |
227 | d->smoothScroll = 0; | 235 | // d->scrollY = 0; |
228 | d->smoothSpeed = 0; | 236 | // d->smoothScroll = 0; |
229 | d->smoothLastOffset = 0; | 237 | // d->smoothSpeed = 0; |
230 | d->smoothContinue = iFalse; | 238 | // d->smoothLastOffset = 0; |
231 | d->selecting = iFalse; | 239 | // d->smoothContinue = iFalse; |
240 | d->flags = 0; | ||
232 | d->selectMark = iNullRange; | 241 | d->selectMark = iNullRange; |
233 | d->foundMark = iNullRange; | 242 | d->foundMark = iNullRange; |
234 | d->pageMargin = 5; | 243 | d->pageMargin = 5; |
235 | d->hoverLink = NULL; | 244 | d->hoverLink = NULL; |
236 | d->contextLink = NULL; | 245 | d->contextLink = NULL; |
237 | d->noHoverWhileScrolling = iFalse; | 246 | // d->noHoverWhileScrolling = iFalse; |
238 | d->showLinkNumbers = iFalse; | 247 | // d->showLinkNumbers = iFalse; |
239 | d->firstVisibleRun = NULL; | 248 | d->firstVisibleRun = NULL; |
240 | d->lastVisibleRun = NULL; | 249 | d->lastVisibleRun = NULL; |
241 | d->visBuf = new_VisBuf(); | 250 | d->visBuf = new_VisBuf(); |
@@ -298,13 +307,6 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) { | |||
298 | postCommand_Widget(obj, "document.request.finished doc:%p request:%p", d, d->request); | 307 | postCommand_Widget(obj, "document.request.finished doc:%p request:%p", d, d->request); |
299 | } | 308 | } |
300 | 309 | ||
301 | static void resetSmoothScroll_DocumentWidget_(iDocumentWidget *d) { | ||
302 | d->smoothSpeed = 0; | ||
303 | d->smoothScroll = 0; | ||
304 | d->smoothLastOffset = 0; | ||
305 | d->smoothContinue = iFalse; | ||
306 | } | ||
307 | |||
308 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | 310 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { |
309 | const iWidget *w = constAs_Widget(d); | 311 | const iWidget *w = constAs_Widget(d); |
310 | const iRect bounds = bounds_Widget(w); | 312 | const iRect bounds = bounds_Widget(w); |
@@ -345,13 +347,15 @@ static int forceBreakWidth_DocumentWidget_(const iDocumentWidget *d) { | |||
345 | } | 347 | } |
346 | 348 | ||
347 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | 349 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { |
348 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY); | 350 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), |
351 | value_Anim(&d->scrollY)); | ||
349 | } | 352 | } |
350 | 353 | ||
351 | static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { | 354 | static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { |
352 | const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0; | 355 | const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0; |
353 | return (iRangei){ d->scrollY - margin, | 356 | return (iRangei){ value_Anim(&d->scrollY) - margin, |
354 | d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) - margin }; | 357 | value_Anim(&d->scrollY) + height_Rect(bounds_Widget(constAs_Widget(d))) - |
358 | margin }; | ||
355 | } | 359 | } |
356 | 360 | ||
357 | static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | 361 | static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { |
@@ -373,7 +377,7 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
373 | static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { | 377 | static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { |
374 | const int docSize = size_GmDocument(d->doc).y; | 378 | const int docSize = size_GmDocument(d->doc).y; |
375 | if (docSize) { | 379 | if (docSize) { |
376 | return (float) d->scrollY / (float) docSize; | 380 | return value_Anim(&d->scrollY) / (float) docSize; |
377 | } | 381 | } |
378 | return 0; | 382 | return 0; |
379 | } | 383 | } |
@@ -398,8 +402,8 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
398 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 402 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
399 | const iGmRun * oldHoverLink = d->hoverLink; | 403 | const iGmRun * oldHoverLink = d->hoverLink; |
400 | d->hoverLink = NULL; | 404 | d->hoverLink = NULL; |
401 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), d->scrollY); | 405 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), value_Anim(&d->scrollY)); |
402 | if (isHover_Widget(w) && !d->noHoverWhileScrolling && | 406 | if (isHover_Widget(w) && (~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) && |
403 | (d->state == ready_RequestState || d->state == receivedPartialResponse_RequestState)) { | 407 | (d->state == ready_RequestState || d->state == receivedPartialResponse_RequestState)) { |
404 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 408 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
405 | const iGmRun *run = i.ptr; | 409 | const iGmRun *run = i.ptr; |
@@ -438,7 +442,7 @@ static void animate_DocumentWidget_(void *ticker) { | |||
438 | static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { | 442 | static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { |
439 | float opacity = 0.0f; | 443 | float opacity = 0.0f; |
440 | const iGmRun *banner = siteBanner_GmDocument(d->doc); | 444 | const iGmRun *banner = siteBanner_GmDocument(d->doc); |
441 | if (banner && bottom_Rect(banner->visBounds) < d->scrollY) { | 445 | if (banner && bottom_Rect(banner->visBounds) < value_Anim(&d->scrollY)) { |
442 | opacity = 1.0f; | 446 | opacity = 1.0f; |
443 | } | 447 | } |
444 | setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); | 448 | setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); |
@@ -519,7 +523,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
519 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); | 523 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); |
520 | const int docSize = size_GmDocument(d->doc).y; | 524 | const int docSize = size_GmDocument(d->doc).y; |
521 | setThumb_ScrollWidget(d->scroll, | 525 | setThumb_ScrollWidget(d->scroll, |
522 | d->scrollY, | 526 | value_Anim(&d->scrollY), |
523 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); | 527 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); |
524 | clear_PtrArray(&d->visibleLinks); | 528 | clear_PtrArray(&d->visibleLinks); |
525 | clear_PtrArray(&d->visiblePlayers); | 529 | clear_PtrArray(&d->visiblePlayers); |
@@ -709,8 +713,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
709 | } | 713 | } |
710 | } | 714 | } |
711 | setSource_DocumentWidget_(d, src); | 715 | setSource_DocumentWidget_(d, src); |
712 | resetSmoothScroll_DocumentWidget_(d); | 716 | init_Anim(&d->scrollY, 0); |
713 | d->scrollY = 0; | ||
714 | init_Anim(&d->sideOpacity, 0); | 717 | init_Anim(&d->sideOpacity, 0); |
715 | d->state = ready_RequestState; | 718 | d->state = ready_RequestState; |
716 | } | 719 | } |
@@ -912,7 +915,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
912 | d->sourceTime = resp->when; | 915 | d->sourceTime = resp->when; |
913 | set_Block(&d->sourceContent, &resp->body); | 916 | set_Block(&d->sourceContent, &resp->body); |
914 | updateDocument_DocumentWidget_(d, resp, iTrue); | 917 | updateDocument_DocumentWidget_(d, resp, iTrue); |
915 | d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; | 918 | init_Anim(&d->scrollY, d->initNormScrollY * size_GmDocument(d->doc).y); |
916 | d->state = ready_RequestState; | 919 | d->state = ready_RequestState; |
917 | updateSideOpacity_DocumentWidget_(d, iFalse); | 920 | updateSideOpacity_DocumentWidget_(d, iFalse); |
918 | updateOutline_DocumentWidget_(d); | 921 | updateOutline_DocumentWidget_(d); |
@@ -926,62 +929,72 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
926 | return iFalse; | 929 | return iFalse; |
927 | } | 930 | } |
928 | 931 | ||
929 | static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { | 932 | static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { |
930 | d->scrollY += offset; | 933 | iDocumentWidget *d = ptr; |
931 | if (d->scrollY < 0) { | 934 | // if (isFinished_Anim(&d-> isSmoothScrolling_DocumentWidget_(d)) { |
932 | d->scrollY = 0; | 935 | // return; /* was cancelled */ |
936 | // } | ||
937 | // const double elapsed = (double) elapsedSinceLastTicker_App() / 1000.0; | ||
938 | // int delta = d->smoothSpeed * elapsed * iSign(d->smoothScroll); | ||
939 | // if (iAbs(d->smoothScroll) <= iAbs(delta)) { | ||
940 | // if (d->smoothContinue) { | ||
941 | // d->smoothScroll += d->smoothLastOffset; | ||
942 | // } | ||
943 | // else { | ||
944 | // delta = d->smoothScroll; | ||
945 | // } | ||
946 | // } | ||
947 | updateVisible_DocumentWidget_(d); | ||
948 | refresh_Widget(d); | ||
949 | // scroll_DocumentWidget_(d, delta); | ||
950 | // d->smoothScroll -= delta; | ||
951 | if (!isFinished_Anim(&d->scrollY)) { | ||
952 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); | ||
953 | } | ||
954 | } | ||
955 | |||
956 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { | ||
957 | int destY = targetValue_Anim(&d->scrollY) + offset; | ||
958 | // d->scrollY += offset; | ||
959 | if (destY < 0) { | ||
960 | destY = 0; | ||
933 | } | 961 | } |
934 | const int scrollMax = scrollMax_DocumentWidget_(d); | 962 | const int scrollMax = scrollMax_DocumentWidget_(d); |
935 | if (scrollMax > 0) { | 963 | if (scrollMax > 0) { |
936 | d->scrollY = iMin(d->scrollY, scrollMax); | 964 | destY = iMin(destY, scrollMax); |
937 | } | 965 | } |
938 | else { | 966 | else { |
939 | d->scrollY = 0; | 967 | destY = 0; |
940 | } | 968 | } |
969 | setValueEased_Anim(&d->scrollY, destY, duration); | ||
941 | updateVisible_DocumentWidget_(d); | 970 | updateVisible_DocumentWidget_(d); |
942 | refresh_Widget(as_Widget(d)); | 971 | refresh_Widget(as_Widget(d)); |
943 | } | ||
944 | |||
945 | static iBool isSmoothScrolling_DocumentWidget_(const iDocumentWidget *d) { | ||
946 | return d->smoothScroll != 0; | ||
947 | } | ||
948 | 972 | ||
949 | static void doScroll_DocumentWidget_(iAny *ptr) { | 973 | // if (speed == 0) { |
950 | iDocumentWidget *d = ptr; | 974 | // scroll_DocumentWidget_(d, offset); |
951 | if (!isSmoothScrolling_DocumentWidget_(d)) { | 975 | // return; |
952 | return; /* was cancelled */ | 976 | // } |
953 | } | 977 | // d->smoothSpeed = speed; |
954 | const double elapsed = (double) elapsedSinceLastTicker_App() / 1000.0; | 978 | // d->smoothScroll += offset; |
955 | int delta = d->smoothSpeed * elapsed * iSign(d->smoothScroll); | 979 | // d->smoothLastOffset = offset; |
956 | if (iAbs(d->smoothScroll) <= iAbs(delta)) { | 980 | if (duration > 0) { |
957 | if (d->smoothContinue) { | 981 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
958 | d->smoothScroll += d->smoothLastOffset; | 982 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); |
959 | } | ||
960 | else { | ||
961 | delta = d->smoothScroll; | ||
962 | } | ||
963 | } | ||
964 | scroll_DocumentWidget_(d, delta); | ||
965 | d->smoothScroll -= delta; | ||
966 | if (isSmoothScrolling_DocumentWidget_(d)) { | ||
967 | addTicker_App(doScroll_DocumentWidget_, d); | ||
968 | } | 983 | } |
969 | } | 984 | } |
970 | 985 | ||
971 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int speed) { | 986 | static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { |
972 | if (speed == 0) { | 987 | smoothScroll_DocumentWidget_(d, offset, 0 /* instant */); |
973 | scroll_DocumentWidget_(d, offset); | ||
974 | return; | ||
975 | } | ||
976 | d->smoothSpeed = speed; | ||
977 | d->smoothScroll += offset; | ||
978 | d->smoothLastOffset = offset; | ||
979 | addTicker_App(doScroll_DocumentWidget_, d); | ||
980 | } | 988 | } |
981 | 989 | ||
990 | //static iBool isSmoothScrolling_DocumentWidget_(const iDocumentWidget *d) { | ||
991 | // return d->smoothScroll != 0; | ||
992 | //} | ||
993 | |||
982 | static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { | 994 | static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { |
983 | d->scrollY = documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 : | 995 | init_Anim(&d->scrollY, |
984 | lineHeight_Text(paragraph_FontId)); | 996 | documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 |
997 | : lineHeight_Text(paragraph_FontId))); | ||
985 | scroll_DocumentWidget_(d, 0); /* clamp it */ | 998 | scroll_DocumentWidget_(d, 0); /* clamp it */ |
986 | } | 999 | } |
987 | 1000 | ||
@@ -1016,8 +1029,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1016 | break; | 1029 | break; |
1017 | } | 1030 | } |
1018 | case categorySuccess_GmStatusCode: | 1031 | case categorySuccess_GmStatusCode: |
1019 | d->scrollY = 0; | 1032 | init_Anim(&d->scrollY, 0); |
1020 | resetSmoothScroll_DocumentWidget_(d); | ||
1021 | reset_GmDocument(d->doc); /* new content incoming */ | 1033 | reset_GmDocument(d->doc); /* new content incoming */ |
1022 | updateDocument_DocumentWidget_(d, response_GmRequest(d->request), iTrue); | 1034 | updateDocument_DocumentWidget_(d, response_GmRequest(d->request), iTrue); |
1023 | break; | 1035 | break; |
@@ -1212,6 +1224,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1212 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { | 1224 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { |
1213 | const iGmRun *mid = middleRun_DocumentWidget_(d); | 1225 | const iGmRun *mid = middleRun_DocumentWidget_(d); |
1214 | const char *midLoc = (mid ? mid->text.start : NULL); | 1226 | const char *midLoc = (mid ? mid->text.start : NULL); |
1227 | /* Alt/Option key may be involved in window size changes. */ | ||
1228 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); | ||
1215 | setWidth_GmDocument( | 1229 | setWidth_GmDocument( |
1216 | d->doc, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d)); | 1230 | d->doc, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d)); |
1217 | scroll_DocumentWidget_(d, 0); | 1231 | scroll_DocumentWidget_(d, 0); |
@@ -1240,7 +1254,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1240 | updateSize_DocumentWidget(d); | 1254 | updateSize_DocumentWidget(d); |
1241 | } | 1255 | } |
1242 | else if (equal_Command(cmd, "tabs.changed")) { | 1256 | else if (equal_Command(cmd, "tabs.changed")) { |
1243 | d->showLinkNumbers = iFalse; | 1257 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); |
1244 | if (cmp_String(id_Widget(w), suffixPtr_Command(cmd, "id")) == 0) { | 1258 | if (cmp_String(id_Widget(w), suffixPtr_Command(cmd, "id")) == 0) { |
1245 | /* Set palette for our document. */ | 1259 | /* Set palette for our document. */ |
1246 | updateTheme_DocumentWidget_(d); | 1260 | updateTheme_DocumentWidget_(d); |
@@ -1345,8 +1359,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1345 | set_Block(&d->sourceContent, body_GmRequest(d->request)); | 1359 | set_Block(&d->sourceContent, body_GmRequest(d->request)); |
1346 | updateFetchProgress_DocumentWidget_(d); | 1360 | updateFetchProgress_DocumentWidget_(d); |
1347 | checkResponse_DocumentWidget_(d); | 1361 | checkResponse_DocumentWidget_(d); |
1348 | resetSmoothScroll_DocumentWidget_(d); | 1362 | init_Anim(&d->scrollY, d->initNormScrollY * size_GmDocument(d->doc).y); |
1349 | d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; | ||
1350 | d->state = ready_RequestState; | 1363 | d->state = ready_RequestState; |
1351 | /* The response may be cached. */ { | 1364 | /* The response may be cached. */ { |
1352 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && | 1365 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && |
@@ -1496,25 +1509,24 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1496 | return iTrue; | 1509 | return iTrue; |
1497 | } | 1510 | } |
1498 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { | 1511 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { |
1499 | d->scrollY = arg_Command(cmd); | 1512 | init_Anim(&d->scrollY, arg_Command(cmd)); |
1500 | resetSmoothScroll_DocumentWidget_(d); | ||
1501 | updateVisible_DocumentWidget_(d); | 1513 | updateVisible_DocumentWidget_(d); |
1502 | return iTrue; | 1514 | return iTrue; |
1503 | } | 1515 | } |
1504 | else if (equalWidget_Command(cmd, w, "scroll.page")) { | 1516 | else if (equalWidget_Command(cmd, w, "scroll.page")) { |
1505 | if (argLabel_Command(cmd, "repeat")) { | 1517 | if (argLabel_Command(cmd, "repeat")) { |
1506 | if (!d->smoothContinue) { | 1518 | // if (!d->smoothContinue) { |
1507 | d->smoothContinue = iTrue; | 1519 | // d->smoothContinue = iTrue; |
1508 | } | 1520 | // } |
1509 | else { | 1521 | // else { |
1510 | return iTrue; | 1522 | // return iTrue; |
1511 | } | 1523 | // } |
1512 | } | 1524 | } |
1513 | smoothScroll_DocumentWidget_(d, | 1525 | smoothScroll_DocumentWidget_(d, |
1514 | arg_Command(cmd) * | 1526 | arg_Command(cmd) * |
1515 | (0.5f * height_Rect(documentBounds_DocumentWidget_(d)) - | 1527 | (0.5f * height_Rect(documentBounds_DocumentWidget_(d)) - |
1516 | 0 * lineHeight_Text(paragraph_FontId)), | 1528 | 0 * lineHeight_Text(paragraph_FontId)), |
1517 | 25 * smoothSpeed_DocumentWidget_); | 1529 | smoothDuration_DocumentWidget_); |
1518 | return iTrue; | 1530 | return iTrue; |
1519 | } | 1531 | } |
1520 | else if (equal_Command(cmd, "document.goto") && document_App() == d) { | 1532 | else if (equal_Command(cmd, "document.goto") && document_App() == d) { |
@@ -1584,7 +1596,7 @@ static size_t visibleLinkOrdinal_DocumentWidget_(const iDocumentWidget *d, iGmLi | |||
1584 | 1596 | ||
1585 | static iRect playerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | 1597 | static iRect playerRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { |
1586 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 1598 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
1587 | return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -d->scrollY)); | 1599 | return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -value_Anim(&d->scrollY))); |
1588 | } | 1600 | } |
1589 | 1601 | ||
1590 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { | 1602 | static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { |
@@ -1707,7 +1719,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1707 | case SDLK_LALT: | 1719 | case SDLK_LALT: |
1708 | case SDLK_RALT: | 1720 | case SDLK_RALT: |
1709 | if (document_App() == d) { | 1721 | if (document_App() == d) { |
1710 | d->showLinkNumbers = iFalse; | 1722 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); |
1711 | invalidate_DocumentWidget_(d); | 1723 | invalidate_DocumentWidget_(d); |
1712 | refresh_Widget(w); | 1724 | refresh_Widget(w); |
1713 | } | 1725 | } |
@@ -1717,15 +1729,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1717 | case SDLK_SPACE: | 1729 | case SDLK_SPACE: |
1718 | case SDLK_UP: | 1730 | case SDLK_UP: |
1719 | case SDLK_DOWN: | 1731 | case SDLK_DOWN: |
1720 | d->smoothContinue = iFalse; | 1732 | // d->smoothContinue = iFalse; |
1721 | break; | 1733 | break; |
1722 | } | 1734 | } |
1723 | } | 1735 | } |
1724 | if (ev->type == SDL_KEYDOWN) { | 1736 | if (ev->type == SDL_KEYDOWN) { |
1725 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 1737 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
1726 | const int key = ev->key.keysym.sym; | 1738 | const int key = ev->key.keysym.sym; |
1727 | if (d->showLinkNumbers && ((key >= '1' && key <= '9') || | 1739 | if ((d->flags & showLinkNumbers_DocumentWidgetFlag) && |
1728 | (key >= 'a' && key <= 'z'))) { | 1740 | ((key >= '1' && key <= '9') || (key >= 'a' && key <= 'z'))) { |
1729 | const size_t ord = isdigit(key) ? key - SDLK_1 : (key - 'a' + 9); | 1741 | const size_t ord = isdigit(key) ? key - SDLK_1 : (key - 'a' + 9); |
1730 | iConstForEach(PtrArray, i, &d->visibleLinks) { | 1742 | iConstForEach(PtrArray, i, &d->visibleLinks) { |
1731 | const iGmRun *run = i.ptr; | 1743 | const iGmRun *run = i.ptr; |
@@ -1742,23 +1754,21 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1742 | case SDLK_LALT: | 1754 | case SDLK_LALT: |
1743 | case SDLK_RALT: | 1755 | case SDLK_RALT: |
1744 | if (document_App() == d) { | 1756 | if (document_App() == d) { |
1745 | d->showLinkNumbers = iTrue; | 1757 | iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iTrue); |
1746 | invalidate_DocumentWidget_(d); | 1758 | invalidate_DocumentWidget_(d); |
1747 | refresh_Widget(w); | 1759 | refresh_Widget(w); |
1748 | } | 1760 | } |
1749 | break; | 1761 | break; |
1750 | case SDLK_HOME: | 1762 | case SDLK_HOME: |
1751 | d->scrollY = 0; | 1763 | init_Anim(&d->scrollY, 0); |
1752 | invalidate_VisBuf(d->visBuf); | 1764 | invalidate_VisBuf(d->visBuf); |
1753 | resetSmoothScroll_DocumentWidget_(d); | ||
1754 | scroll_DocumentWidget_(d, 0); | 1765 | scroll_DocumentWidget_(d, 0); |
1755 | updateVisible_DocumentWidget_(d); | 1766 | updateVisible_DocumentWidget_(d); |
1756 | refresh_Widget(w); | 1767 | refresh_Widget(w); |
1757 | return iTrue; | 1768 | return iTrue; |
1758 | case SDLK_END: | 1769 | case SDLK_END: |
1759 | d->scrollY = scrollMax_DocumentWidget_(d); | 1770 | init_Anim(&d->scrollY, scrollMax_DocumentWidget_(d)); |
1760 | invalidate_VisBuf(d->visBuf); | 1771 | invalidate_VisBuf(d->visBuf); |
1761 | resetSmoothScroll_DocumentWidget_(d); | ||
1762 | scroll_DocumentWidget_(d, 0); | 1772 | scroll_DocumentWidget_(d, 0); |
1763 | updateVisible_DocumentWidget_(d); | 1773 | updateVisible_DocumentWidget_(d); |
1764 | refresh_Widget(w); | 1774 | refresh_Widget(w); |
@@ -1767,15 +1777,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1767 | case SDLK_DOWN: | 1777 | case SDLK_DOWN: |
1768 | if (mods == 0) { | 1778 | if (mods == 0) { |
1769 | if (ev->key.repeat) { | 1779 | if (ev->key.repeat) { |
1770 | if (!d->smoothContinue) { | 1780 | // if (!d->smoothContinue) { |
1771 | d->smoothContinue = iTrue; | 1781 | // d->smoothContinue = iTrue; |
1772 | } | 1782 | // } |
1773 | else return iTrue; | 1783 | // else return iTrue; |
1774 | } | 1784 | } |
1775 | smoothScroll_DocumentWidget_(d, | 1785 | smoothScroll_DocumentWidget_(d, |
1776 | 3 * lineHeight_Text(paragraph_FontId) * | 1786 | 3 * lineHeight_Text(paragraph_FontId) * |
1777 | (key == SDLK_UP ? -1 : 1), | 1787 | (key == SDLK_UP ? -1 : 1), |
1778 | gap_Text * smoothSpeed_DocumentWidget_); | 1788 | /*gap_Text * */smoothDuration_DocumentWidget_); |
1779 | return iTrue; | 1789 | return iTrue; |
1780 | } | 1790 | } |
1781 | break; | 1791 | break; |
@@ -1825,6 +1835,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1825 | } | 1835 | } |
1826 | #if defined (iPlatformApple) | 1836 | #if defined (iPlatformApple) |
1827 | /* Momentum scrolling. */ | 1837 | /* Momentum scrolling. */ |
1838 | stop_Anim(&d->scrollY); | ||
1828 | scroll_DocumentWidget_(d, -ev->wheel.y * get_Window()->pixelRatio * acceleration); | 1839 | scroll_DocumentWidget_(d, -ev->wheel.y * get_Window()->pixelRatio * acceleration); |
1829 | #else | 1840 | #else |
1830 | if (keyMods_Sym(SDL_GetModState()) == KMOD_PRIMARY) { | 1841 | if (keyMods_Sym(SDL_GetModState()) == KMOD_PRIMARY) { |
@@ -1834,14 +1845,14 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1834 | smoothScroll_DocumentWidget_( | 1845 | smoothScroll_DocumentWidget_( |
1835 | d, | 1846 | d, |
1836 | -3 * ev->wheel.y * lineHeight_Text(paragraph_FontId) * acceleration, | 1847 | -3 * ev->wheel.y * lineHeight_Text(paragraph_FontId) * acceleration, |
1837 | gap_Text * smoothSpeed_DocumentWidget_ + | 1848 | smoothDuration_DocumentWidget_); /* + |
1838 | (isSmoothScrolling_DocumentWidget_(d) ? d->smoothSpeed : 0)); | 1849 | (isSmoothScrolling_DocumentWidget_(d) ? d->smoothSpeed : 0)); */ |
1839 | #endif | 1850 | #endif |
1840 | d->noHoverWhileScrolling = iTrue; | 1851 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
1841 | return iTrue; | 1852 | return iTrue; |
1842 | } | 1853 | } |
1843 | else if (ev->type == SDL_MOUSEMOTION) { | 1854 | else if (ev->type == SDL_MOUSEMOTION) { |
1844 | d->noHoverWhileScrolling = iFalse; | 1855 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iFalse); |
1845 | if (isVisible_Widget(d->menu)) { | 1856 | if (isVisible_Widget(d->menu)) { |
1846 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | 1857 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); |
1847 | } | 1858 | } |
@@ -1926,7 +1937,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1926 | } | 1937 | } |
1927 | switch (processEvent_Click(&d->click, ev)) { | 1938 | switch (processEvent_Click(&d->click, ev)) { |
1928 | case started_ClickResult: | 1939 | case started_ClickResult: |
1929 | d->selecting = iFalse; | 1940 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iFalse); |
1930 | return iTrue; | 1941 | return iTrue; |
1931 | case drag_ClickResult: { | 1942 | case drag_ClickResult: { |
1932 | if (d->grabbedPlayer) { | 1943 | if (d->grabbedPlayer) { |
@@ -1940,9 +1951,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1940 | return iTrue; | 1951 | return iTrue; |
1941 | } | 1952 | } |
1942 | /* Begin selecting a range of text. */ | 1953 | /* Begin selecting a range of text. */ |
1943 | if (!d->selecting) { | 1954 | if (~d->flags & selecting_DocumentWidgetFlag) { |
1944 | setFocus_Widget(NULL); /* TODO: Focus this document? */ | 1955 | setFocus_Widget(NULL); /* TODO: Focus this document? */ |
1945 | d->selecting = iTrue; | 1956 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); |
1946 | d->selectMark.start = d->selectMark.end = | 1957 | d->selectMark.start = d->selectMark.end = |
1947 | sourceLoc_DocumentWidget_(d, d->click.startPos); | 1958 | sourceLoc_DocumentWidget_(d, d->click.startPos); |
1948 | refresh_Widget(w); | 1959 | refresh_Widget(w); |
@@ -2086,7 +2097,8 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol | |||
2086 | if (w > width_Rect(run->visBounds) - x) { | 2097 | if (w > width_Rect(run->visBounds) - x) { |
2087 | w = width_Rect(run->visBounds) - x; | 2098 | w = width_Rect(run->visBounds) - x; |
2088 | } | 2099 | } |
2089 | const iInt2 visPos = add_I2(run->bounds.pos, addY_I2(d->viewPos, -d->widget->scrollY)); | 2100 | const iInt2 visPos = |
2101 | add_I2(run->bounds.pos, addY_I2(d->viewPos, -value_Anim(&d->widget->scrollY))); | ||
2090 | fillRect_Paint(&d->paint, (iRect){ addX_I2(visPos, x), | 2102 | fillRect_Paint(&d->paint, (iRect){ addX_I2(visPos, x), |
2091 | init_I2(w, height_Rect(run->bounds)) }, color); | 2103 | init_I2(w, height_Rect(run->bounds)) }, color); |
2092 | } | 2104 | } |
@@ -2383,13 +2395,15 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | |||
2383 | collect_String(format_Time(&d->sourceTime, "Received at %I:%M %p\non %b %d, %Y")); | 2395 | collect_String(format_Time(&d->sourceTime, "Received at %I:%M %p\non %b %d, %Y")); |
2384 | const iInt2 size = advanceRange_Text(font, range_String(recv)); | 2396 | const iInt2 size = advanceRange_Text(font, range_String(recv)); |
2385 | if (size.x <= avail) { | 2397 | if (size.x <= avail) { |
2386 | drawString_Text(font, | 2398 | drawString_Text( |
2387 | add_I2(bottomLeft_Rect(bounds), | 2399 | font, |
2388 | init_I2(margin, | 2400 | add_I2( |
2389 | -margin + -size.y + | 2401 | bottomLeft_Rect(bounds), |
2390 | iMax(0, scrollMax_DocumentWidget_(d) - d->scrollY))), | 2402 | init_I2(margin, |
2391 | tmQuoteIcon_ColorId, | 2403 | -margin + -size.y + |
2392 | recv); | 2404 | iMax(0, scrollMax_DocumentWidget_(d) - value_Anim(&d->scrollY)))), |
2405 | tmQuoteIcon_ColorId, | ||
2406 | recv); | ||
2393 | } | 2407 | } |
2394 | } | 2408 | } |
2395 | /* Outline on the right side. */ | 2409 | /* Outline on the right side. */ |
@@ -2402,9 +2416,9 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | |||
2402 | const int scrollMax = scrollMax_DocumentWidget_(d); | 2416 | const int scrollMax = scrollMax_DocumentWidget_(d); |
2403 | const int outHeight = outlineHeight_DocumentWidget_(d); | 2417 | const int outHeight = outlineHeight_DocumentWidget_(d); |
2404 | const int oversize = outHeight - height_Rect(bounds) + topMargin + bottomMargin; | 2418 | const int oversize = outHeight - height_Rect(bounds) + topMargin + bottomMargin; |
2405 | const int scroll = | 2419 | const int scroll = (oversize > 0 && scrollMax > 0 |
2406 | (oversize > 0 && scrollMax > 0 ? oversize * d->scrollY / scrollMax_DocumentWidget_(d) | 2420 | ? oversize * value_Anim(&d->scrollY) / scrollMax_DocumentWidget_(d) |
2407 | : 0); | 2421 | : 0); |
2408 | iInt2 pos = | 2422 | iInt2 pos = |
2409 | add_I2(topRight_Rect(bounds), init_I2(-outWidth - width_Widget(d->scroll), topMargin)); | 2423 | add_I2(topRight_Rect(bounds), init_I2(-outWidth - width_Widget(d->scroll), topMargin)); |
2410 | /* Center short outlines vertically. */ | 2424 | /* Center short outlines vertically. */ |
@@ -2471,7 +2485,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
2471 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 2485 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
2472 | iDrawContext ctx = { | 2486 | iDrawContext ctx = { |
2473 | .widget = d, | 2487 | .widget = d, |
2474 | .showLinkNumbers = d->showLinkNumbers, | 2488 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, |
2475 | }; | 2489 | }; |
2476 | /* Currently visible region. */ | 2490 | /* Currently visible region. */ |
2477 | const iRangei vis = visibleRange_DocumentWidget_(d); | 2491 | const iRangei vis = visibleRange_DocumentWidget_(d); |
@@ -2524,7 +2538,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
2524 | clear_PtrSet(d->invalidRuns); | 2538 | clear_PtrSet(d->invalidRuns); |
2525 | } | 2539 | } |
2526 | setClip_Paint(&ctx.paint, bounds); | 2540 | setClip_Paint(&ctx.paint, bounds); |
2527 | const int yTop = docBounds.pos.y - d->scrollY; | 2541 | const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); |
2528 | draw_VisBuf(visBuf, init_I2(bounds.pos.x, yTop)); | 2542 | draw_VisBuf(visBuf, init_I2(bounds.pos.x, yTop)); |
2529 | /* Text markers. */ | 2543 | /* Text markers. */ |
2530 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { | 2544 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { |
diff --git a/src/ui/util.c b/src/ui/util.c index bef839dc..38124b22 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -135,8 +135,9 @@ iBool isFinished_Anim(const iAnim *d) { | |||
135 | } | 135 | } |
136 | 136 | ||
137 | void init_Anim(iAnim *d, float value) { | 137 | void init_Anim(iAnim *d, float value) { |
138 | d->due = d->when = frameTime_Window(get_Window()); | 138 | d->due = d->when = SDL_GetTicks(); // frameTime_Window(get_Window()); |
139 | d->from = d->to = value; | 139 | d->from = d->to = value; |
140 | d->flags = 0; | ||
140 | } | 141 | } |
141 | 142 | ||
142 | void setValue_Anim(iAnim *d, float to, uint32_t span) { | 143 | void setValue_Anim(iAnim *d, float to, uint32_t span) { |
@@ -149,6 +150,54 @@ void setValue_Anim(iAnim *d, float to, uint32_t span) { | |||
149 | } | 150 | } |
150 | } | 151 | } |
151 | 152 | ||
153 | iLocalDef float pos_Anim_(const iAnim *d, uint32_t now) { | ||
154 | return (float) (now - d->when) / (float) (d->due - d->when); | ||
155 | } | ||
156 | |||
157 | void setValueEased_Anim(iAnim *d, float to, uint32_t span) { | ||
158 | if (fabsf(to - d->to) <= 0.00001f) { | ||
159 | d->to = to; /* Pretty much unchanged. */ | ||
160 | return; | ||
161 | } | ||
162 | const uint32_t now = SDL_GetTicks(); | ||
163 | if (isFinished_Anim(d)) { | ||
164 | d->from = d->to; | ||
165 | d->when = now; | ||
166 | d->flags = easeBoth_AnimFlag; | ||
167 | } | ||
168 | else { | ||
169 | d->from = value_Anim(d); | ||
170 | d->when = frameTime_Window(get_Window()); /* to match the timing of value_Anim */ | ||
171 | d->flags = easeOut_AnimFlag; | ||
172 | } | ||
173 | d->to = to; | ||
174 | d->due = now + span; | ||
175 | } | ||
176 | |||
177 | void setFlags_Anim(iAnim *d, int flags, iBool set) { | ||
178 | iChangeFlags(d->flags, flags, set); | ||
179 | } | ||
180 | |||
181 | void stop_Anim(iAnim *d) { | ||
182 | d->from = d->to = value_Anim(d); | ||
183 | d->when = d->due = SDL_GetTicks(); | ||
184 | } | ||
185 | |||
186 | iLocalDef float easeIn_(float t) { | ||
187 | return t * t; | ||
188 | } | ||
189 | |||
190 | iLocalDef float easeOut_(float t) { | ||
191 | return t * (2.0f - t); | ||
192 | } | ||
193 | |||
194 | iLocalDef float easeBoth_(float t) { | ||
195 | if (t < 0.5f) { | ||
196 | return easeIn_(t * 2.0f) * 0.5f; | ||
197 | } | ||
198 | return 0.5f + easeOut_((t - 0.5f) * 2.0f) * 0.5f; | ||
199 | } | ||
200 | |||
152 | float value_Anim(const iAnim *d) { | 201 | float value_Anim(const iAnim *d) { |
153 | const uint32_t now = frameTime_Window(get_Window()); | 202 | const uint32_t now = frameTime_Window(get_Window()); |
154 | if (now >= d->due) { | 203 | if (now >= d->due) { |
@@ -157,8 +206,17 @@ float value_Anim(const iAnim *d) { | |||
157 | if (now <= d->when) { | 206 | if (now <= d->when) { |
158 | return d->from; | 207 | return d->from; |
159 | } | 208 | } |
160 | const float pos = (float) (now - d->when) / (float) (d->due - d->when); | 209 | float t = pos_Anim_(d, now); |
161 | return d->from * (1.0f - pos) + d->to * pos; | 210 | if ((d->flags & easeBoth_AnimFlag) == easeBoth_AnimFlag) { |
211 | t = easeBoth_(t); | ||
212 | } | ||
213 | else if (d->flags & easeIn_AnimFlag) { | ||
214 | t = easeIn_(t); | ||
215 | } | ||
216 | else if (d->flags & easeOut_AnimFlag) { | ||
217 | t = easeOut_(t); | ||
218 | } | ||
219 | return d->from * (1.0f - t) + d->to * t; | ||
162 | } | 220 | } |
163 | 221 | ||
164 | /*-----------------------------------------------------------------------------------------------*/ | 222 | /*-----------------------------------------------------------------------------------------------*/ |
diff --git a/src/ui/util.h b/src/ui/util.h index a33bf713..c342c095 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -68,15 +68,35 @@ iLocalDef iBool isOverlapping_Rangei(iRangei a, iRangei b) { | |||
68 | 68 | ||
69 | iDeclareType(Anim) | 69 | iDeclareType(Anim) |
70 | 70 | ||
71 | enum iAnimFlag { | ||
72 | indefinite_AnimFlag = iBit(1), /* does not end; must be linear */ | ||
73 | easeIn_AnimFlag = iBit(2), | ||
74 | easeOut_AnimFlag = iBit(3), | ||
75 | easeBoth_AnimFlag = easeIn_AnimFlag | easeOut_AnimFlag, | ||
76 | }; | ||
77 | |||
71 | struct Impl_Anim { | 78 | struct Impl_Anim { |
72 | float from, to; | 79 | float from, to; |
73 | uint32_t when, due; | 80 | uint32_t when, due; |
81 | int flags; | ||
74 | }; | 82 | }; |
75 | 83 | ||
76 | void init_Anim (iAnim *, float value); | 84 | void init_Anim (iAnim *, float value); |
77 | void setValue_Anim (iAnim *, float to, uint32_t span); | 85 | void setValue_Anim (iAnim *, float to, uint32_t span); |
78 | float value_Anim (const iAnim *); | 86 | void setValueLinear_Anim (iAnim *, float to, uint32_t span); |
79 | iBool isFinished_Anim (const iAnim *); | 87 | void setValueEased_Anim (iAnim *, float to, uint32_t span); |
88 | void setFlags_Anim (iAnim *, int flags, iBool set); | ||
89 | void stop_Anim (iAnim *); | ||
90 | |||
91 | iBool isFinished_Anim (const iAnim *); | ||
92 | float value_Anim (const iAnim *); | ||
93 | |||
94 | iLocalDef float targetValue_Anim(const iAnim *d) { | ||
95 | return d->to; | ||
96 | } | ||
97 | iLocalDef iBool isLinear_Anim(const iAnim *d) { | ||
98 | return (d->flags & (easeIn_AnimFlag | easeOut_AnimFlag)) == 0; | ||
99 | } | ||
80 | 100 | ||
81 | /*-----------------------------------------------------------------------------------------------*/ | 101 | /*-----------------------------------------------------------------------------------------------*/ |
82 | 102 | ||