diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-28 16:22:12 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-28 16:22:12 +0200 |
commit | de1cbd837abc6a7dcfef6ed3ee357721cbc2907f (patch) | |
tree | 6795f9a08a2f89a0a79101ed5bb1d5260671f5da | |
parent | edf1c0bb8b112879433f2e31fd9750c30e2d5144 (diff) |
DocumentWidget: Horizontal scrolling improvements
Interaction with selection/found markers (will reset scrolling), and smooth horizontal scrolling with a mouse.
IssueID #44
-rw-r--r-- | src/ui/documentwidget.c | 95 |
1 files changed, 75 insertions, 20 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index a1b26e7f..78036ef1 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -159,6 +159,9 @@ struct Impl_DocumentWidget { | |||
159 | iPtrArray visibleLinks; | 159 | iPtrArray visibleLinks; |
160 | iPtrArray visibleWideRuns; /* scrollable blocks */ | 160 | iPtrArray visibleWideRuns; /* scrollable blocks */ |
161 | iArray wideRunOffsets; | 161 | iArray wideRunOffsets; |
162 | iAnim animWideRunOffset; | ||
163 | uint16_t animWideRunId; | ||
164 | iGmRunRange animWideRunRange; | ||
162 | iPtrArray visiblePlayers; /* currently playing audio */ | 165 | iPtrArray visiblePlayers; /* currently playing audio */ |
163 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 166 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ |
164 | float grabbedStartVolume; | 167 | float grabbedStartVolume; |
@@ -204,6 +207,8 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
204 | d->redirectCount = 0; | 207 | d->redirectCount = 0; |
205 | d->initNormScrollY = 0; | 208 | d->initNormScrollY = 0; |
206 | init_Anim(&d->scrollY, 0); | 209 | init_Anim(&d->scrollY, 0); |
210 | d->animWideRunId = 0; | ||
211 | init_Anim(&d->animWideRunOffset, 0); | ||
207 | d->selectMark = iNullRange; | 212 | d->selectMark = iNullRange; |
208 | d->foundMark = iNullRange; | 213 | d->foundMark = iNullRange; |
209 | d->pageMargin = 5; | 214 | d->pageMargin = 5; |
@@ -270,6 +275,13 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
270 | deinit_PersistentDocumentState(&d->mod); | 275 | deinit_PersistentDocumentState(&d->mod); |
271 | } | 276 | } |
272 | 277 | ||
278 | static void resetWideRuns_DocumentWidget_(iDocumentWidget *d) { | ||
279 | clear_Array(&d->wideRunOffsets); | ||
280 | d->animWideRunId = 0; | ||
281 | init_Anim(&d->animWideRunOffset, 0); | ||
282 | iZap(d->animWideRunRange); | ||
283 | } | ||
284 | |||
273 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | 285 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { |
274 | iDocumentWidget *d = obj; | 286 | iDocumentWidget *d = obj; |
275 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); | 287 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); |
@@ -386,6 +398,29 @@ static void invalidateVisibleLinks_DocumentWidget_(iDocumentWidget *d) { | |||
386 | } | 398 | } |
387 | } | 399 | } |
388 | 400 | ||
401 | static int runOffset_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { | ||
402 | if (run->preId && run->flags & wide_GmRunFlag) { | ||
403 | if (d->animWideRunId == run->preId) { | ||
404 | return -value_Anim(&d->animWideRunOffset); | ||
405 | } | ||
406 | const size_t numOffsets = size_Array(&d->wideRunOffsets); | ||
407 | const int *offsets = constData_Array(&d->wideRunOffsets); | ||
408 | if (run->preId <= numOffsets) { | ||
409 | return -offsets[run->preId - 1]; | ||
410 | } | ||
411 | } | ||
412 | return 0; | ||
413 | } | ||
414 | |||
415 | static void invalidateWideRunsWithNonzeroOffset_DocumentWidget_(iDocumentWidget *d) { | ||
416 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { | ||
417 | const iGmRun *run = i.ptr; | ||
418 | if (runOffset_DocumentWidget_(d, run)) { | ||
419 | insert_PtrSet(d->invalidRuns, run); | ||
420 | } | ||
421 | } | ||
422 | } | ||
423 | |||
389 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | 424 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { |
390 | const iWidget *w = constAs_Widget(d); | 425 | const iWidget *w = constAs_Widget(d); |
391 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 426 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
@@ -762,7 +797,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
762 | updateTheme_DocumentWidget_(d); | 797 | updateTheme_DocumentWidget_(d); |
763 | init_Anim(&d->scrollY, 0); | 798 | init_Anim(&d->scrollY, 0); |
764 | init_Anim(&d->sideOpacity, 0); | 799 | init_Anim(&d->sideOpacity, 0); |
765 | clear_Array(&d->wideRunOffsets); | 800 | resetWideRuns_DocumentWidget_(d); |
766 | d->state = ready_RequestState; | 801 | d->state = ready_RequestState; |
767 | } | 802 | } |
768 | 803 | ||
@@ -951,7 +986,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
951 | reset_GmDocument(d->doc); | 986 | reset_GmDocument(d->doc); |
952 | d->state = fetching_RequestState; | 987 | d->state = fetching_RequestState; |
953 | d->initNormScrollY = recent->normScrollY; | 988 | d->initNormScrollY = recent->normScrollY; |
954 | clear_Array(&d->wideRunOffsets); | 989 | resetWideRuns_DocumentWidget_(d); |
955 | /* Use the cached response data. */ | 990 | /* Use the cached response data. */ |
956 | updateTrust_DocumentWidget_(d, resp); | 991 | updateTrust_DocumentWidget_(d, resp); |
957 | d->sourceTime = resp->when; | 992 | d->sourceTime = resp->when; |
@@ -977,15 +1012,20 @@ static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { | |||
977 | iDocumentWidget *d = ptr; | 1012 | iDocumentWidget *d = ptr; |
978 | updateVisible_DocumentWidget_(d); | 1013 | updateVisible_DocumentWidget_(d); |
979 | refresh_Widget(d); | 1014 | refresh_Widget(d); |
980 | if (!isFinished_Anim(&d->scrollY)) { | 1015 | if (d->animWideRunId) { |
1016 | for (const iGmRun *r = d->animWideRunRange.start; r != d->animWideRunRange.end; r++) { | ||
1017 | insert_PtrSet(d->invalidRuns, r); | ||
1018 | } | ||
1019 | } | ||
1020 | if (isFinished_Anim(&d->animWideRunOffset)) { | ||
1021 | d->animWideRunId = 0; | ||
1022 | } | ||
1023 | if (!isFinished_Anim(&d->scrollY) || !isFinished_Anim(&d->animWideRunOffset)) { | ||
981 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); | 1024 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); |
982 | } | 1025 | } |
983 | } | 1026 | } |
984 | 1027 | ||
985 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { | 1028 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { |
986 | if (offset == 0) { | ||
987 | return; | ||
988 | } | ||
989 | /* Get rid of link numbers when scrolling. */ | 1029 | /* Get rid of link numbers when scrolling. */ |
990 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { | 1030 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { |
991 | d->flags &= ~showLinkNumbers_DocumentWidgetFlag; | 1031 | d->flags &= ~showLinkNumbers_DocumentWidgetFlag; |
@@ -1030,7 +1070,8 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool ce | |||
1030 | scroll_DocumentWidget_(d, 0); /* clamp it */ | 1070 | scroll_DocumentWidget_(d, 0); /* clamp it */ |
1031 | } | 1071 | } |
1032 | 1072 | ||
1033 | static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta) { | 1073 | static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta, |
1074 | int duration) { | ||
1034 | if (delta == 0) { | 1075 | if (delta == 0) { |
1035 | return; | 1076 | return; |
1036 | } | 1077 | } |
@@ -1057,6 +1098,21 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, | |||
1057 | insert_PtrSet(d->invalidRuns, r); | 1098 | insert_PtrSet(d->invalidRuns, r); |
1058 | } | 1099 | } |
1059 | refresh_Widget(d); | 1100 | refresh_Widget(d); |
1101 | d->selectMark = iNullRange; | ||
1102 | d->foundMark = iNullRange; | ||
1103 | } | ||
1104 | if (duration) { | ||
1105 | if (d->animWideRunId != run->preId || isFinished_Anim(&d->animWideRunOffset)) { | ||
1106 | d->animWideRunId = run->preId; | ||
1107 | init_Anim(&d->animWideRunOffset, oldOffset); | ||
1108 | } | ||
1109 | setValueEased_Anim(&d->animWideRunOffset, *offset, duration); | ||
1110 | d->animWideRunRange = range; | ||
1111 | addTicker_App(refreshWhileScrolling_DocumentWidget_, d); | ||
1112 | } | ||
1113 | else { | ||
1114 | d->animWideRunId = 0; | ||
1115 | init_Anim(&d->animWideRunOffset, 0); | ||
1060 | } | 1116 | } |
1061 | break; | 1117 | break; |
1062 | } | 1118 | } |
@@ -1097,7 +1153,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1097 | case categorySuccess_GmStatusCode: | 1153 | case categorySuccess_GmStatusCode: |
1098 | init_Anim(&d->scrollY, 0); | 1154 | init_Anim(&d->scrollY, 0); |
1099 | reset_GmDocument(d->doc); /* new content incoming */ | 1155 | reset_GmDocument(d->doc); /* new content incoming */ |
1100 | clear_Array(&d->wideRunOffsets); | 1156 | resetWideRuns_DocumentWidget_(d); |
1101 | updateDocument_DocumentWidget_(d, resp, iTrue); | 1157 | updateDocument_DocumentWidget_(d, resp, iTrue); |
1102 | break; | 1158 | break; |
1103 | case categoryRedirect_GmStatusCode: | 1159 | case categoryRedirect_GmStatusCode: |
@@ -1764,6 +1820,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1764 | } | 1820 | } |
1765 | } | 1821 | } |
1766 | } | 1822 | } |
1823 | invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); /* markers don't support offsets */ | ||
1824 | resetWideRuns_DocumentWidget_(d); | ||
1767 | refresh_Widget(w); | 1825 | refresh_Widget(w); |
1768 | return iTrue; | 1826 | return iTrue; |
1769 | } | 1827 | } |
@@ -2063,6 +2121,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2063 | if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ | 2121 | if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ |
2064 | stop_Anim(&d->scrollY); | 2122 | stop_Anim(&d->scrollY); |
2065 | iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); | 2123 | iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); |
2124 | /* Only scroll on one axis at a time. */ | ||
2066 | if (iAbs(wheel.x) > iAbs(wheel.y)) { | 2125 | if (iAbs(wheel.x) > iAbs(wheel.y)) { |
2067 | wheel.y = 0; | 2126 | wheel.y = 0; |
2068 | } | 2127 | } |
@@ -2070,7 +2129,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2070 | wheel.x = 0; | 2129 | wheel.x = 0; |
2071 | } | 2130 | } |
2072 | scroll_DocumentWidget_(d, -wheel.y * get_Window()->pixelRatio * acceleration); | 2131 | scroll_DocumentWidget_(d, -wheel.y * get_Window()->pixelRatio * acceleration); |
2073 | scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x * get_Window()->pixelRatio); | 2132 | scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x * get_Window()->pixelRatio, 0); |
2074 | } | 2133 | } |
2075 | else | 2134 | else |
2076 | #endif | 2135 | #endif |
@@ -2092,7 +2151,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2092 | /* accelerated speed for repeated wheelings */ | 2151 | /* accelerated speed for repeated wheelings */ |
2093 | (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); | 2152 | (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); |
2094 | scrollWideBlock_DocumentWidget_( | 2153 | scrollWideBlock_DocumentWidget_( |
2095 | d, mouseCoord, ev->wheel.x * lineHeight_Text(paragraph_FontId)); | 2154 | d, mouseCoord, ev->wheel.x * lineHeight_Text(paragraph_FontId) * 3, 167); |
2096 | } | 2155 | } |
2097 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); | 2156 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
2098 | return iTrue; | 2157 | return iTrue; |
@@ -2244,6 +2303,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2244 | /* Begin selecting a range of text. */ | 2303 | /* Begin selecting a range of text. */ |
2245 | if (~d->flags & selecting_DocumentWidgetFlag) { | 2304 | if (~d->flags & selecting_DocumentWidgetFlag) { |
2246 | setFocus_Widget(NULL); /* TODO: Focus this document? */ | 2305 | setFocus_Widget(NULL); /* TODO: Focus this document? */ |
2306 | invalidateWideRunsWithNonzeroOffset_DocumentWidget_(d); | ||
2307 | resetWideRuns_DocumentWidget_(d); /* Selections don't support horizontal scrolling. */ | ||
2247 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); | 2308 | iChangeFlags(d->flags, selecting_DocumentWidgetFlag, iTrue); |
2248 | d->selectMark.start = d->selectMark.end = | 2309 | d->selectMark.start = d->selectMark.end = |
2249 | sourceLoc_DocumentWidget_(d, d->click.startPos); | 2310 | sourceLoc_DocumentWidget_(d, d->click.startPos); |
@@ -2444,15 +2505,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
2444 | const iBool isHover = | 2505 | const iBool isHover = |
2445 | (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && | 2506 | (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && |
2446 | ~run->flags & decoration_GmRunFlag); | 2507 | ~run->flags & decoration_GmRunFlag); |
2447 | iInt2 visPos = add_I2(run->visBounds.pos, origin); | 2508 | const iInt2 visPos = addX_I2(add_I2(run->visBounds.pos, origin), |
2448 | /* Preformatted runs can be scrolled. */ | 2509 | /* Preformatted runs can be scrolled. */ |
2449 | if (run->preId && run->flags & wide_GmRunFlag) { | 2510 | runOffset_DocumentWidget_(d->widget, run)); |
2450 | const size_t numOffsets = size_Array(&d->widget->wideRunOffsets); | ||
2451 | const int *offsets = constData_Array(&d->widget->wideRunOffsets); | ||
2452 | if (run->preId <= numOffsets) { | ||
2453 | visPos.x -= offsets[run->preId - 1]; | ||
2454 | } | ||
2455 | } | ||
2456 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); | 2511 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); |
2457 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { | 2512 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { |
2458 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); | 2513 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); |
@@ -3015,7 +3070,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | |||
3015 | 3070 | ||
3016 | void updateSize_DocumentWidget(iDocumentWidget *d) { | 3071 | void updateSize_DocumentWidget(iDocumentWidget *d) { |
3017 | setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); | 3072 | setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); |
3018 | clear_Array(&d->wideRunOffsets); | 3073 | resetWideRuns_DocumentWidget_(d); |
3019 | updateSideIconBuf_DocumentWidget_(d); | 3074 | updateSideIconBuf_DocumentWidget_(d); |
3020 | updateOutline_DocumentWidget_(d); | 3075 | updateOutline_DocumentWidget_(d); |
3021 | updateVisible_DocumentWidget_(d); | 3076 | updateVisible_DocumentWidget_(d); |