diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-28 14:23:18 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-11-28 14:23:18 +0200 |
commit | edf1c0bb8b112879433f2e31fd9750c30e2d5144 (patch) | |
tree | 9e7e3207e8e926ae1dfe47992e9055ba21fb16ed /src/ui/documentwidget.c | |
parent | 0ccabb2c8e0e894be520c621bca8023a6d5de2e2 (diff) |
Scrolling wide preformatted blocks horizontally
Not entirely glitch-free but should be good enough for now.
IssueID #44
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r-- | src/ui/documentwidget.c | 92 |
1 files changed, 73 insertions, 19 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 4de25b58..a1b26e7f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -157,6 +157,8 @@ struct Impl_DocumentWidget { | |||
157 | iRangecc foundMark; | 157 | iRangecc foundMark; |
158 | int pageMargin; | 158 | int pageMargin; |
159 | iPtrArray visibleLinks; | 159 | iPtrArray visibleLinks; |
160 | iPtrArray visibleWideRuns; /* scrollable blocks */ | ||
161 | iArray wideRunOffsets; | ||
160 | iPtrArray visiblePlayers; /* currently playing audio */ | 162 | iPtrArray visiblePlayers; /* currently playing audio */ |
161 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 163 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ |
162 | float grabbedStartVolume; | 164 | float grabbedStartVolume; |
@@ -218,6 +220,8 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
218 | init_Block(&d->sourceContent, 0); | 220 | init_Block(&d->sourceContent, 0); |
219 | iZap(d->sourceTime); | 221 | iZap(d->sourceTime); |
220 | init_PtrArray(&d->visibleLinks); | 222 | init_PtrArray(&d->visibleLinks); |
223 | init_PtrArray(&d->visibleWideRuns); | ||
224 | init_Array(&d->wideRunOffsets, sizeof(int)); | ||
221 | init_PtrArray(&d->visiblePlayers); | 225 | init_PtrArray(&d->visiblePlayers); |
222 | d->grabbedPlayer = NULL; | 226 | d->grabbedPlayer = NULL; |
223 | d->playerTimer = 0; | 227 | d->playerTimer = 0; |
@@ -256,7 +260,9 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
256 | if (d->playerTimer) { | 260 | if (d->playerTimer) { |
257 | SDL_RemoveTimer(d->playerTimer); | 261 | SDL_RemoveTimer(d->playerTimer); |
258 | } | 262 | } |
263 | deinit_Array(&d->wideRunOffsets); | ||
259 | deinit_PtrArray(&d->visiblePlayers); | 264 | deinit_PtrArray(&d->visiblePlayers); |
265 | deinit_PtrArray(&d->visibleWideRuns); | ||
260 | deinit_PtrArray(&d->visibleLinks); | 266 | deinit_PtrArray(&d->visibleLinks); |
261 | delete_Block(d->certFingerprint); | 267 | delete_Block(d->certFingerprint); |
262 | delete_String(d->certSubject); | 268 | delete_String(d->certSubject); |
@@ -317,20 +323,6 @@ static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) { | |||
317 | return moved_Rect(banner->visBounds, origin); | 323 | return moved_Rect(banner->visBounds, origin); |
318 | } | 324 | } |
319 | 325 | ||
320 | #if 0 | ||
321 | static int forceBreakWidth_DocumentWidget_(const iDocumentWidget *d) { | ||
322 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "gopher")) { | ||
323 | return documentWidth_DocumentWidget_(d); | ||
324 | } | ||
325 | if (forceLineWrap_App()) { | ||
326 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | ||
327 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
328 | return right_Rect(bounds) - left_Rect(docBounds) - gap_UI * d->pageMargin; | ||
329 | } | ||
330 | return 0; | ||
331 | } | ||
332 | #endif | ||
333 | |||
334 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | 326 | static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { |
335 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), | 327 | return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), |
336 | value_Anim(&d->scrollY)); | 328 | value_Anim(&d->scrollY)); |
@@ -351,6 +343,9 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
351 | } | 343 | } |
352 | d->lastVisibleRun = run; | 344 | d->lastVisibleRun = run; |
353 | } | 345 | } |
346 | if (run->preId && run->flags & wide_GmRunFlag) { | ||
347 | pushBack_PtrArray(&d->visibleWideRuns, run); | ||
348 | } | ||
354 | if (run->audioId) { | 349 | if (run->audioId) { |
355 | pushBack_PtrArray(&d->visiblePlayers, run); | 350 | pushBack_PtrArray(&d->visiblePlayers, run); |
356 | } | 351 | } |
@@ -538,6 +533,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
538 | value_Anim(&d->scrollY), | 533 | value_Anim(&d->scrollY), |
539 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); | 534 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); |
540 | clear_PtrArray(&d->visibleLinks); | 535 | clear_PtrArray(&d->visibleLinks); |
536 | clear_PtrArray(&d->visibleWideRuns); | ||
541 | clear_PtrArray(&d->visiblePlayers); | 537 | clear_PtrArray(&d->visiblePlayers); |
542 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); | 538 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); |
543 | /* Scan for visible runs. */ { | 539 | /* Scan for visible runs. */ { |
@@ -766,6 +762,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
766 | updateTheme_DocumentWidget_(d); | 762 | updateTheme_DocumentWidget_(d); |
767 | init_Anim(&d->scrollY, 0); | 763 | init_Anim(&d->scrollY, 0); |
768 | init_Anim(&d->sideOpacity, 0); | 764 | init_Anim(&d->sideOpacity, 0); |
765 | clear_Array(&d->wideRunOffsets); | ||
769 | d->state = ready_RequestState; | 766 | d->state = ready_RequestState; |
770 | } | 767 | } |
771 | 768 | ||
@@ -903,7 +900,6 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
903 | d->request = new_GmRequest(certs_App()); | 900 | d->request = new_GmRequest(certs_App()); |
904 | setUrl_GmRequest(d->request, d->mod.url); | 901 | setUrl_GmRequest(d->request, d->mod.url); |
905 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); | 902 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); |
906 | // iConnect(GmRequest, d->request, timeout, d, requestTimedOut_DocumentWidget_); | ||
907 | iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); | 903 | iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); |
908 | submit_GmRequest(d->request); | 904 | submit_GmRequest(d->request); |
909 | } | 905 | } |
@@ -955,6 +951,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | |||
955 | reset_GmDocument(d->doc); | 951 | reset_GmDocument(d->doc); |
956 | d->state = fetching_RequestState; | 952 | d->state = fetching_RequestState; |
957 | d->initNormScrollY = recent->normScrollY; | 953 | d->initNormScrollY = recent->normScrollY; |
954 | clear_Array(&d->wideRunOffsets); | ||
958 | /* Use the cached response data. */ | 955 | /* Use the cached response data. */ |
959 | updateTrust_DocumentWidget_(d, resp); | 956 | updateTrust_DocumentWidget_(d, resp); |
960 | d->sourceTime = resp->when; | 957 | d->sourceTime = resp->when; |
@@ -986,6 +983,9 @@ static void refreshWhileScrolling_DocumentWidget_(iAny *ptr) { | |||
986 | } | 983 | } |
987 | 984 | ||
988 | static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int duration) { | 985 | 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. */ | 989 | /* Get rid of link numbers when scrolling. */ |
990 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { | 990 | if (offset && d->flags & showLinkNumbers_DocumentWidgetFlag) { |
991 | d->flags &= ~showLinkNumbers_DocumentWidgetFlag; | 991 | d->flags &= ~showLinkNumbers_DocumentWidgetFlag; |
@@ -1030,6 +1030,39 @@ static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool ce | |||
1030 | scroll_DocumentWidget_(d, 0); /* clamp it */ | 1030 | scroll_DocumentWidget_(d, 0); /* clamp it */ |
1031 | } | 1031 | } |
1032 | 1032 | ||
1033 | static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, int delta) { | ||
1034 | if (delta == 0) { | ||
1035 | return; | ||
1036 | } | ||
1037 | const iInt2 docPos = documentPos_DocumentWidget_(d, mousePos); | ||
1038 | iConstForEach(PtrArray, i, &d->visibleWideRuns) { | ||
1039 | const iGmRun *run = i.ptr; | ||
1040 | if (docPos.y >= top_Rect(run->bounds) && docPos.y <= bottom_Rect(run->bounds)) { | ||
1041 | /* We can scroll this run. First find out how much is allowed. */ | ||
1042 | const iGmRunRange range = findPreformattedRange_GmDocument(d->doc, run); | ||
1043 | int maxWidth = 0; | ||
1044 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
1045 | maxWidth = iMax(maxWidth, width_Rect(r->visBounds)); | ||
1046 | } | ||
1047 | const int maxOffset = maxWidth - documentWidth_DocumentWidget_(d) + d->pageMargin * gap_UI; | ||
1048 | if (size_Array(&d->wideRunOffsets) <= run->preId) { | ||
1049 | resize_Array(&d->wideRunOffsets, run->preId + 1); | ||
1050 | } | ||
1051 | int *offset = at_Array(&d->wideRunOffsets, run->preId - 1); | ||
1052 | const int oldOffset = *offset; | ||
1053 | *offset = iClamp(*offset + delta, 0, maxOffset); | ||
1054 | /* Make sure the whole block gets redraw. */ | ||
1055 | if (oldOffset != *offset) { | ||
1056 | for (const iGmRun *r = range.start; r != range.end; r++) { | ||
1057 | insert_PtrSet(d->invalidRuns, r); | ||
1058 | } | ||
1059 | refresh_Widget(d); | ||
1060 | } | ||
1061 | break; | ||
1062 | } | ||
1063 | } | ||
1064 | } | ||
1065 | |||
1033 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 1066 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
1034 | if (!d->request) { | 1067 | if (!d->request) { |
1035 | return; | 1068 | return; |
@@ -1064,6 +1097,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1064 | case categorySuccess_GmStatusCode: | 1097 | case categorySuccess_GmStatusCode: |
1065 | init_Anim(&d->scrollY, 0); | 1098 | init_Anim(&d->scrollY, 0); |
1066 | reset_GmDocument(d->doc); /* new content incoming */ | 1099 | reset_GmDocument(d->doc); /* new content incoming */ |
1100 | clear_Array(&d->wideRunOffsets); | ||
1067 | updateDocument_DocumentWidget_(d, resp, iTrue); | 1101 | updateDocument_DocumentWidget_(d, resp, iTrue); |
1068 | break; | 1102 | break; |
1069 | case categoryRedirect_GmStatusCode: | 1103 | case categoryRedirect_GmStatusCode: |
@@ -2015,8 +2049,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2015 | } | 2049 | } |
2016 | else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { | 2050 | else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { |
2017 | float acceleration = 1.0f; | 2051 | float acceleration = 1.0f; |
2052 | const iInt2 mouseCoord = mouseCoord_Window(get_Window()); | ||
2018 | if (prefs_App()->hoverOutline && | 2053 | if (prefs_App()->hoverOutline && |
2019 | contains_Widget(constAs_Widget(d->scroll), mouseCoord_Window(get_Window()))) { | 2054 | contains_Widget(constAs_Widget(d->scroll), mouseCoord)) { |
2020 | const int outHeight = outlineHeight_DocumentWidget_(d); | 2055 | const int outHeight = outlineHeight_DocumentWidget_(d); |
2021 | if (outHeight > height_Rect(bounds_Widget(w))) { | 2056 | if (outHeight > height_Rect(bounds_Widget(w))) { |
2022 | acceleration = (float) size_GmDocument(d->doc).y / (float) outHeight; | 2057 | acceleration = (float) size_GmDocument(d->doc).y / (float) outHeight; |
@@ -2027,7 +2062,15 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2027 | which device is sending the event. */ | 2062 | which device is sending the event. */ |
2028 | if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ | 2063 | if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ |
2029 | stop_Anim(&d->scrollY); | 2064 | stop_Anim(&d->scrollY); |
2030 | scroll_DocumentWidget_(d, -ev->wheel.y * get_Window()->pixelRatio * acceleration); | 2065 | iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); |
2066 | if (iAbs(wheel.x) > iAbs(wheel.y)) { | ||
2067 | wheel.y = 0; | ||
2068 | } | ||
2069 | else { | ||
2070 | wheel.x = 0; | ||
2071 | } | ||
2072 | scroll_DocumentWidget_(d, -wheel.y * get_Window()->pixelRatio * acceleration); | ||
2073 | scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x * get_Window()->pixelRatio); | ||
2031 | } | 2074 | } |
2032 | else | 2075 | else |
2033 | #endif | 2076 | #endif |
@@ -2046,8 +2089,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2046 | d, | 2089 | d, |
2047 | -3 * amount * lineHeight_Text(paragraph_FontId) * acceleration, | 2090 | -3 * amount * lineHeight_Text(paragraph_FontId) * acceleration, |
2048 | smoothDuration_DocumentWidget_ * | 2091 | smoothDuration_DocumentWidget_ * |
2092 | /* accelerated speed for repeated wheelings */ | ||
2049 | (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); | 2093 | (!isFinished_Anim(&d->scrollY) && pos_Anim(&d->scrollY) < 0.25f ? 0.5f : 1.0f)); |
2050 | /* accelerated speed for repeated wheelings */ | 2094 | scrollWideBlock_DocumentWidget_( |
2095 | d, mouseCoord, ev->wheel.x * lineHeight_Text(paragraph_FontId)); | ||
2051 | } | 2096 | } |
2052 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); | 2097 | iChangeFlags(d->flags, noHoverWhileScrolling_DocumentWidgetFlag, iTrue); |
2053 | return iTrue; | 2098 | return iTrue; |
@@ -2399,7 +2444,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
2399 | const iBool isHover = | 2444 | const iBool isHover = |
2400 | (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && | 2445 | (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && |
2401 | ~run->flags & decoration_GmRunFlag); | 2446 | ~run->flags & decoration_GmRunFlag); |
2402 | const iInt2 visPos = add_I2(run->visBounds.pos, origin); | 2447 | iInt2 visPos = add_I2(run->visBounds.pos, origin); |
2448 | /* Preformatted runs can be scrolled. */ | ||
2449 | if (run->preId && run->flags & wide_GmRunFlag) { | ||
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 | } | ||
2403 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); | 2456 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackground_ColorId); |
2404 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { | 2457 | if (run->linkId && ~run->flags & decoration_GmRunFlag) { |
2405 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); | 2458 | fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); |
@@ -2962,6 +3015,7 @@ iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | |||
2962 | 3015 | ||
2963 | void updateSize_DocumentWidget(iDocumentWidget *d) { | 3016 | void updateSize_DocumentWidget(iDocumentWidget *d) { |
2964 | setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); | 3017 | setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); |
3018 | clear_Array(&d->wideRunOffsets); | ||
2965 | updateSideIconBuf_DocumentWidget_(d); | 3019 | updateSideIconBuf_DocumentWidget_(d); |
2966 | updateOutline_DocumentWidget_(d); | 3020 | updateOutline_DocumentWidget_(d); |
2967 | updateVisible_DocumentWidget_(d); | 3021 | updateVisible_DocumentWidget_(d); |