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 | |
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')
-rw-r--r-- | src/gmdocument.c | 43 | ||||
-rw-r--r-- | src/gmdocument.h | 13 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 92 | ||||
-rw-r--r-- | src/ui/text.c | 11 |
4 files changed, 126 insertions, 33 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 2f3a006f..1ff085c7 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -304,6 +304,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
304 | iBool isPreformat = iFalse; | 304 | iBool isPreformat = iFalse; |
305 | iRangecc preAltText = iNullRange; | 305 | iRangecc preAltText = iNullRange; |
306 | int preFont = preformatted_FontId; | 306 | int preFont = preformatted_FontId; |
307 | uint16_t preId = 0; | ||
307 | iBool enableIndents = iFalse; | 308 | iBool enableIndents = iFalse; |
308 | iBool addSiteBanner = d->siteBannerEnabled; | 309 | iBool addSiteBanner = d->siteBannerEnabled; |
309 | enum iGmLineType prevType = text_GmLineType; | 310 | enum iGmLineType prevType = text_GmLineType; |
@@ -313,12 +314,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
313 | } | 314 | } |
314 | while (nextSplit_Rangecc(content, "\n", &contentLine)) { | 315 | while (nextSplit_Rangecc(content, "\n", &contentLine)) { |
315 | iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ | 316 | iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ |
316 | iGmRun run; | 317 | iGmRun run = { .color = white_ColorId }; |
317 | run.flags = 0; | ||
318 | run.color = white_ColorId; | ||
319 | run.linkId = 0; | ||
320 | run.imageId = 0; | ||
321 | run.audioId = 0; | ||
322 | enum iGmLineType type; | 318 | enum iGmLineType type; |
323 | int indent = 0; | 319 | int indent = 0; |
324 | /* Detect the type of the line. */ | 320 | /* Detect the type of the line. */ |
@@ -330,6 +326,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
330 | indent = indents[type]; | 326 | indent = indents[type]; |
331 | if (type == preformatted_GmLineType) { | 327 | if (type == preformatted_GmLineType) { |
332 | isPreformat = iTrue; | 328 | isPreformat = iTrue; |
329 | preId++; | ||
333 | preFont = preformatted_FontId; | 330 | preFont = preformatted_FontId; |
334 | /* Use a smaller font if the block contents are wide. */ | 331 | /* Use a smaller font if the block contents are wide. */ |
335 | if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x > | 332 | if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x > |
@@ -370,6 +367,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
370 | addSiteBanner = iFalse; /* overrides the banner */ | 367 | addSiteBanner = iFalse; /* overrides the banner */ |
371 | continue; | 368 | continue; |
372 | } | 369 | } |
370 | run.preId = preId; | ||
373 | run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); | 371 | run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); |
374 | indent = indents[type]; | 372 | indent = indents[type]; |
375 | } | 373 | } |
@@ -509,8 +507,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
509 | } | 507 | } |
510 | run.bounds.pos = addX_I2(pos, indent * gap_Text); | 508 | run.bounds.pos = addX_I2(pos, indent * gap_Text); |
511 | const char *contPos; | 509 | const char *contPos; |
512 | const int avail = d->size.x - run.bounds.pos.x; | 510 | const int avail = isPreformat ? 0 : (d->size.x - run.bounds.pos.x); |
513 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); | 511 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); |
512 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); | ||
514 | run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ | 513 | run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ |
515 | run.bounds.size.y = dims.y; | 514 | run.bounds.size.y = dims.y; |
516 | run.visBounds = run.bounds; | 515 | run.visBounds = run.bounds; |
@@ -596,6 +595,19 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
596 | prevType = type; | 595 | prevType = type; |
597 | } | 596 | } |
598 | d->size.y = pos.y; | 597 | d->size.y = pos.y; |
598 | /* Go over the preformatted blocks and mark them wide if at least one run is wide. */ { | ||
599 | iForEach(Array, i, &d->layout) { | ||
600 | iGmRun *run = i.value; | ||
601 | if (run->preId && run->flags & wide_GmRunFlag) { | ||
602 | iGmRunRange block = findPreformattedRange_GmDocument(d, run); | ||
603 | for (const iGmRun *j = block.start; j != block.end; j++) { | ||
604 | iConstCast(iGmRun *, j)->flags |= wide_GmRunFlag; | ||
605 | } | ||
606 | /* Skip to the end of the block. */ | ||
607 | i.pos = block.end - (const iGmRun *) constData_Array(&d->layout) - 1; | ||
608 | } | ||
609 | } | ||
610 | } | ||
599 | } | 611 | } |
600 | 612 | ||
601 | void init_GmDocument(iGmDocument *d) { | 613 | void init_GmDocument(iGmDocument *d) { |
@@ -1237,6 +1249,23 @@ iRangecc findTextBefore_GmDocument(const iGmDocument *d, const iString *text, co | |||
1237 | return found; | 1249 | return found; |
1238 | } | 1250 | } |
1239 | 1251 | ||
1252 | iGmRunRange findPreformattedRange_GmDocument(const iGmDocument *d, const iGmRun *run) { | ||
1253 | iAssert(run->preId); | ||
1254 | iGmRunRange range = { run, run }; | ||
1255 | /* Find the beginning. */ | ||
1256 | while (range.start > (const iGmRun *) constData_Array(&d->layout)) { | ||
1257 | const iGmRun *prev = range.start - 1; | ||
1258 | if (prev->preId != run->preId) break; | ||
1259 | range.start = prev; | ||
1260 | } | ||
1261 | /* Find the ending. */ | ||
1262 | while (range.end < (const iGmRun *) constEnd_Array(&d->layout)) { | ||
1263 | if (range.end->preId != run->preId) break; | ||
1264 | range.end++; | ||
1265 | } | ||
1266 | return range; | ||
1267 | } | ||
1268 | |||
1240 | const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { | 1269 | const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { |
1241 | /* TODO: Perf optimization likely needed; use a block map? */ | 1270 | /* TODO: Perf optimization likely needed; use a block map? */ |
1242 | const iGmRun *last = NULL; | 1271 | const iGmRun *last = NULL; |
diff --git a/src/gmdocument.h b/src/gmdocument.h index c2a4b272..6804d772 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -90,11 +90,19 @@ struct Impl_GmRun { | |||
90 | uint8_t flags; | 90 | uint8_t flags; |
91 | iRect bounds; /* used for hit testing, may extend to edges */ | 91 | iRect bounds; /* used for hit testing, may extend to edges */ |
92 | iRect visBounds; /* actual visual bounds */ | 92 | iRect visBounds; /* actual visual bounds */ |
93 | uint16_t preId; /* preformatted block ID (sequential) */ | ||
93 | iGmLinkId linkId; /* zero for non-links */ | 94 | iGmLinkId linkId; /* zero for non-links */ |
94 | uint16_t imageId; /* zero if not an image */ | 95 | uint16_t imageId; /* zero if not an image */ |
95 | uint16_t audioId; /* zero if not audio */ | 96 | uint16_t audioId; /* zero if not audio */ |
96 | }; | 97 | }; |
97 | 98 | ||
99 | iDeclareType(GmRunRange) | ||
100 | |||
101 | struct Impl_GmRunRange { | ||
102 | const iGmRun *start; | ||
103 | const iGmRun *end; | ||
104 | }; | ||
105 | |||
98 | const char * findLoc_GmRun (const iGmRun *, iInt2 pos); | 106 | const char * findLoc_GmRun (const iGmRun *, iInt2 pos); |
99 | 107 | ||
100 | iDeclareClass(GmDocument) | 108 | iDeclareClass(GmDocument) |
@@ -130,8 +138,9 @@ const iString * bannerText_GmDocument (const iGmDocument *); | |||
130 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ | 138 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ |
131 | const iString * source_GmDocument (const iGmDocument *); | 139 | const iString * source_GmDocument (const iGmDocument *); |
132 | 140 | ||
133 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); | 141 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
134 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 142 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); |
143 | iGmRunRange findPreformattedRange_GmDocument (const iGmDocument *, const iGmRun *run); | ||
135 | 144 | ||
136 | enum iGmLinkPart { | 145 | enum iGmLinkPart { |
137 | icon_GmLinkPart, | 146 | icon_GmLinkPart, |
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); |
diff --git a/src/ui/text.c b/src/ui/text.c index 0b8c98e7..e047bbce 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -165,6 +165,7 @@ static iText text_; | |||
165 | static void initFonts_Text_(iText *d) { | 165 | static void initFonts_Text_(iText *d) { |
166 | const float textSize = fontSize_UI * d->contentFontSize; | 166 | const float textSize = fontSize_UI * d->contentFontSize; |
167 | const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f; | 167 | const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f; |
168 | const float smallMonoSize = monoSize * 0.866f; | ||
168 | const iBlock *regularFont = &fontNunitoRegular_Embedded; | 169 | const iBlock *regularFont = &fontNunitoRegular_Embedded; |
169 | const iBlock *italicFont = &fontNunitoLightItalic_Embedded; | 170 | const iBlock *italicFont = &fontNunitoLightItalic_Embedded; |
170 | const iBlock *h12Font = &fontNunitoExtraBold_Embedded; | 171 | const iBlock *h12Font = &fontNunitoExtraBold_Embedded; |
@@ -218,7 +219,7 @@ static void initFonts_Text_(iText *d) { | |||
218 | /* content fonts */ | 219 | /* content fonts */ |
219 | { regularFont, textSize, scaling, symbols_FontId }, | 220 | { regularFont, textSize, scaling, symbols_FontId }, |
220 | { &fontFiraMonoRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 221 | { &fontFiraMonoRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
221 | { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, | 222 | { &fontFiraMonoRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
222 | { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId }, | 223 | { regularFont, textSize * 1.200f, scaling, mediumSymbols_FontId }, |
223 | { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId }, | 224 | { h3Font, textSize * 1.333f, h123Scaling, bigSymbols_FontId }, |
224 | { italicFont, textSize, scaling, symbols_FontId }, | 225 | { italicFont, textSize, scaling, symbols_FontId }, |
@@ -237,7 +238,7 @@ static void initFonts_Text_(iText *d) { | |||
237 | { &fontSymbola_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, | 238 | { &fontSymbola_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, |
238 | { &fontSymbola_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, | 239 | { &fontSymbola_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, |
239 | { &fontSymbola_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 240 | { &fontSymbola_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
240 | { &fontSymbola_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, | 241 | { &fontSymbola_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
241 | /* emoji fonts */ | 242 | /* emoji fonts */ |
242 | { &fontNotoEmojiRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, | 243 | { &fontNotoEmojiRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, |
243 | { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, | 244 | { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, 1.0f, defaultMediumSymbols_FontId }, |
@@ -248,10 +249,10 @@ static void initFonts_Text_(iText *d) { | |||
248 | { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, | 249 | { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, 1.0f, largeSymbols_FontId }, |
249 | { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, | 250 | { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, |
250 | { &fontNotoEmojiRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 251 | { &fontNotoEmojiRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
251 | { &fontNotoEmojiRegular_Embedded, monoSize * 0.750f, 1.0f, monospaceSmallSymbols_FontId }, | 252 | { &fontNotoEmojiRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
252 | /* japanese fonts */ | 253 | /* japanese fonts */ |
253 | { &fontNotoSansJPRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, | 254 | { &fontNotoSansJPRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, |
254 | { &fontNotoSansJPRegular_Embedded, monoSize * 0.750, 1.0f, monospaceSmallSymbols_FontId }, | 255 | { &fontNotoSansJPRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
255 | { &fontNotoSansJPRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 256 | { &fontNotoSansJPRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
256 | { &fontNotoSansJPRegular_Embedded, textSize, 1.0f, symbols_FontId }, | 257 | { &fontNotoSansJPRegular_Embedded, textSize, 1.0f, symbols_FontId }, |
257 | { &fontNotoSansJPRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, | 258 | { &fontNotoSansJPRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, |
@@ -260,7 +261,7 @@ static void initFonts_Text_(iText *d) { | |||
260 | { &fontNotoSansJPRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, | 261 | { &fontNotoSansJPRegular_Embedded, textSize * 2.000f, 1.0f, hugeSymbols_FontId }, |
261 | /* korean fonts */ | 262 | /* korean fonts */ |
262 | { &fontNanumGothicRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, | 263 | { &fontNanumGothicRegular_Embedded, fontSize_UI, 1.0f, defaultSymbols_FontId }, |
263 | { &fontNanumGothicRegular_Embedded, monoSize * 0.750, 1.0f, monospaceSmallSymbols_FontId }, | 264 | { &fontNanumGothicRegular_Embedded, smallMonoSize, 1.0f, monospaceSmallSymbols_FontId }, |
264 | { &fontNanumGothicRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, | 265 | { &fontNanumGothicRegular_Embedded, monoSize, 1.0f, monospaceSymbols_FontId }, |
265 | { &fontNanumGothicRegular_Embedded, textSize, 1.0f, symbols_FontId }, | 266 | { &fontNanumGothicRegular_Embedded, textSize, 1.0f, symbols_FontId }, |
266 | { &fontNanumGothicRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, | 267 | { &fontNanumGothicRegular_Embedded, textSize * 1.200f, 1.0f, mediumSymbols_FontId }, |