diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-28 22:04:24 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-03-28 22:04:24 +0300 |
commit | de3463f5d9ac3a8b7c0f3e23ff11ade2b19fdb68 (patch) | |
tree | 195d147125afb9db3a4b9775849aea30017dd876 /src | |
parent | 3a881ec009c9c8b6030d2f9bdf404423108e3019 (diff) |
Folding preformatted blocks and showing alt text
The alt text of preformatted blocks is shown on mouse hover.
The blocks can be clicked to toggle folding.
IssueID #180
Diffstat (limited to 'src')
-rw-r--r-- | src/gmdocument.c | 137 | ||||
-rw-r--r-- | src/gmdocument.h | 20 | ||||
-rw-r--r-- | src/ui/color.h | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 119 |
4 files changed, 215 insertions, 64 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index f1471f0f..874a117e 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "gmdocument.h" | 23 | #include "gmdocument.h" |
24 | #include "gmutil.h" | 24 | #include "gmutil.h" |
25 | #include "lang.h" | ||
25 | #include "ui/color.h" | 26 | #include "ui/color.h" |
26 | #include "ui/text.h" | 27 | #include "ui/text.h" |
27 | #include "ui/metrics.h" | 28 | #include "ui/metrics.h" |
@@ -82,6 +83,7 @@ struct Impl_GmDocument { | |||
82 | iString bannerText; | 83 | iString bannerText; |
83 | iString title; /* the first top-level title */ | 84 | iString title; /* the first top-level title */ |
84 | iArray headings; | 85 | iArray headings; |
86 | iArray preMeta; /* metadata about preformatted blocks */ | ||
85 | uint32_t themeSeed; | 87 | uint32_t themeSeed; |
86 | iChar siteIcon; | 88 | iChar siteIcon; |
87 | iMedia * media; | 89 | iMedia * media; |
@@ -143,19 +145,21 @@ static int lastVisibleRunBottom_GmDocument_(const iGmDocument *d) { | |||
143 | return 0; | 145 | return 0; |
144 | } | 146 | } |
145 | 147 | ||
146 | static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font) { | 148 | static iInt2 measurePreformattedBlock_GmDocument_(const iGmDocument *d, const char *start, int font, |
149 | iRangecc *contents, const char **endPos) { | ||
147 | const iRangecc content = { start, constEnd_String(&d->source) }; | 150 | const iRangecc content = { start, constEnd_String(&d->source) }; |
148 | iRangecc line = iNullRange; | 151 | iRangecc line = iNullRange; |
149 | nextSplit_Rangecc(content, "\n", &line); | 152 | nextSplit_Rangecc(content, "\n", &line); |
150 | iAssert(startsWith_Rangecc(line, "```")); | 153 | iAssert(startsWith_Rangecc(line, "```")); |
151 | iRangecc preBlock = { line.end + 1, line.end + 1 }; | 154 | *contents = (iRangecc){ line.end + 1, line.end + 1 }; |
152 | while (nextSplit_Rangecc(content, "\n", &line)) { | 155 | while (nextSplit_Rangecc(content, "\n", &line)) { |
153 | if (startsWith_Rangecc(line, "```")) { | 156 | if (startsWith_Rangecc(line, "```")) { |
157 | if (endPos) *endPos = line.end; | ||
154 | break; | 158 | break; |
155 | } | 159 | } |
156 | preBlock.end = line.end; | 160 | contents->end = line.end; |
157 | } | 161 | } |
158 | return measureRange_Text(font, preBlock); | 162 | return measureRange_Text(font, *contents); |
159 | } | 163 | } |
160 | 164 | ||
161 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { | 165 | static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { |
@@ -279,8 +283,8 @@ static iBool isForcedMonospace_GmDocument_(const iGmDocument *d) { | |||
279 | return iFalse; | 283 | return iFalse; |
280 | } | 284 | } |
281 | 285 | ||
282 | static void linkContentLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, | 286 | static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo *mediaInfo, |
283 | uint16_t linkId) { | 287 | uint16_t linkId) { |
284 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); | 288 | iGmLink *link = at_PtrArray(&d->links, linkId - 1); |
285 | link->flags |= content_GmLinkFlag; | 289 | link->flags |= content_GmLinkFlag; |
286 | if (mediaInfo && mediaInfo->isPermanent) { | 290 | if (mediaInfo && mediaInfo->isPermanent) { |
@@ -370,6 +374,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
370 | clear_Array(&d->layout); | 374 | clear_Array(&d->layout); |
371 | clearLinks_GmDocument_(d); | 375 | clearLinks_GmDocument_(d); |
372 | clear_Array(&d->headings); | 376 | clear_Array(&d->headings); |
377 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ | ||
378 | clear_Array(&d->preMeta); | ||
373 | clear_String(&d->title); | 379 | clear_String(&d->title); |
374 | clear_String(&d->bannerText); | 380 | clear_String(&d->bannerText); |
375 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { | 381 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { |
@@ -381,7 +387,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
381 | iBool isFirstText = prefs->bigFirstParagraph; | 387 | iBool isFirstText = prefs->bigFirstParagraph; |
382 | iBool addQuoteIcon = prefs->quoteIcon; | 388 | iBool addQuoteIcon = prefs->quoteIcon; |
383 | iBool isPreformat = iFalse; | 389 | iBool isPreformat = iFalse; |
384 | iRangecc preAltText = iNullRange; /* TODO: alt text is being ignored */ | ||
385 | int preFont = preformatted_FontId; | 390 | int preFont = preformatted_FontId; |
386 | uint16_t preId = 0; | 391 | uint16_t preId = 0; |
387 | iBool enableIndents = iFalse; | 392 | iBool enableIndents = iFalse; |
@@ -408,17 +413,26 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
408 | } | 413 | } |
409 | indent = indents[type]; | 414 | indent = indents[type]; |
410 | if (type == preformatted_GmLineType) { | 415 | if (type == preformatted_GmLineType) { |
416 | /* Begin a new preformatted block. */ | ||
411 | isPreformat = iTrue; | 417 | isPreformat = iTrue; |
412 | preId++; | 418 | const size_t preIndex = preId++; |
413 | preFont = preformatted_FontId; | 419 | preFont = preformatted_FontId; |
414 | /* Use a smaller font if the block contents are wide. */ | 420 | /* Use a smaller font if the block contents are wide. */ |
415 | if (measurePreformattedBlock_GmDocument_(d, line.start, preFont).x > | 421 | iGmPreMeta meta = { .bounds = line }; |
422 | meta.pixelRect.size = measurePreformattedBlock_GmDocument_( | ||
423 | d, line.start, preFont, &meta.contents, &meta.bounds.end); | ||
424 | if (meta.pixelRect.size.x > | ||
416 | d->size.x /*- indents[preformatted_GmLineType] * gap_Text*/) { | 425 | d->size.x /*- indents[preformatted_GmLineType] * gap_Text*/) { |
417 | preFont = preformattedSmall_FontId; | 426 | preFont = preformattedSmall_FontId; |
418 | } | 427 | } |
419 | trimLine_Rangecc(&line, type, isNormalized); | 428 | trimLine_Rangecc(&line, type, isNormalized); |
420 | preAltText = line; | 429 | meta.altText = line; /* without the ``` */ |
421 | /* TODO: store and link the alt text to this run */ | 430 | /* Reuse previous state. */ |
431 | if (preIndex < size_Array(oldPreMeta)) { | ||
432 | meta.flags = constValue_Array(oldPreMeta, preIndex, iGmPreMeta).flags & | ||
433 | folded_GmPreMetaFlag; | ||
434 | } | ||
435 | pushBack_Array(&d->preMeta, &meta); | ||
422 | continue; | 436 | continue; |
423 | } | 437 | } |
424 | else if (type == link_GmLineType) { | 438 | else if (type == link_GmLineType) { |
@@ -446,7 +460,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
446 | if (d->format == gemini_GmDocumentFormat && | 460 | if (d->format == gemini_GmDocumentFormat && |
447 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { | 461 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { |
448 | isPreformat = iFalse; | 462 | isPreformat = iFalse; |
449 | preAltText = iNullRange; | ||
450 | addSiteBanner = iFalse; /* overrides the banner */ | 463 | addSiteBanner = iFalse; /* overrides the banner */ |
451 | continue; | 464 | continue; |
452 | } | 465 | } |
@@ -523,6 +536,30 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
523 | pos.y += required - delta; | 536 | pos.y += required - delta; |
524 | } | 537 | } |
525 | } | 538 | } |
539 | /* Folded blocks are represented by a single run with the alt text. */ | ||
540 | if (isPreformat) { | ||
541 | const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); | ||
542 | if (meta->flags & folded_GmPreMetaFlag) { | ||
543 | const iBool isBlank = isEmpty_Range(&meta->altText); | ||
544 | iGmRun altText = { .font = paragraph_FontId, | ||
545 | .flags = (isBlank ? decoration_GmRunFlag : 0) | altText_GmRunFlag }; | ||
546 | const iInt2 margin = preRunMargin_GmDocument(d, 0); | ||
547 | altText.color = tmQuote_ColorId; | ||
548 | altText.text = isBlank ? range_Lang(range_CStr("doc.pre.nocaption")) | ||
549 | : meta->altText; | ||
550 | iInt2 size = advanceWrapRange_Text(altText.font, d->size.x - 2 * margin.x, | ||
551 | altText.text); | ||
552 | altText.bounds = altText.visBounds = init_Rect(pos.x, pos.y, d->size.x, | ||
553 | size.y + 2 * margin.y); | ||
554 | altText.preId = preId; | ||
555 | pushBack_Array(&d->layout, &altText); | ||
556 | pos.y += height_Rect(altText.bounds); | ||
557 | contentLine = meta->bounds; /* Skip the whole thing. */ | ||
558 | isPreformat = iFalse; | ||
559 | prevType = preformatted_GmLineType; | ||
560 | continue; | ||
561 | } | ||
562 | } | ||
526 | /* Save the document title (first high-level heading). */ | 563 | /* Save the document title (first high-level heading). */ |
527 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && | 564 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && |
528 | isEmpty_String(&d->title)) { | 565 | isEmpty_String(&d->title)) { |
@@ -632,14 +669,27 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
632 | type == quote_GmLineType ? 4 : 0); | 669 | type == quote_GmLineType ? 4 : 0); |
633 | const iBool isWordWrapped = | 670 | const iBool isWordWrapped = |
634 | (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); | 671 | (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); |
672 | if (isPreformat) { | ||
673 | /* Remember the top left coordinates of the block (first line of block). */ | ||
674 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); | ||
675 | if (~meta->flags & topLeft_GmPreMetaFlag) { | ||
676 | meta->pixelRect.pos = pos; //, indent * gap_Text); | ||
677 | meta->flags |= topLeft_GmPreMetaFlag; | ||
678 | } | ||
679 | /* Collapse indentation if too wide. */ | ||
680 | if (width_Rect(meta->pixelRect) > d->size.x - (indent + rightMargin) * gap_Text) { | ||
681 | indent = 0; | ||
682 | } | ||
683 | } | ||
635 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ | 684 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ |
636 | while (!isEmpty_Range(&runLine)) { | 685 | while (!isEmpty_Range(&runLine)) { |
637 | run.bounds.pos = addX_I2(pos, indent * gap_Text); | 686 | run.bounds.pos = addX_I2(pos, indent * gap_Text); |
638 | const int avail = isWordWrapped ? d->size.x - run.bounds.pos.x - rightMargin * gap_Text : 0; | 687 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; |
688 | const int avail = isWordWrapped ? wrapAvail : 0; | ||
639 | const char *contPos; | 689 | const char *contPos; |
640 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); | 690 | const iInt2 dims = tryAdvance_Text(run.font, runLine, avail, &contPos); |
641 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); | 691 | iChangeFlags(run.flags, wide_GmRunFlag, (isPreformat && dims.x > d->size.x)); |
642 | run.bounds.size.x = iMax(avail, dims.x); /* Extends to the right edge for selection. */ | 692 | run.bounds.size.x = iMax(wrapAvail, dims.x); /* Extends to the right edge for selection. */ |
643 | run.bounds.size.y = dims.y; | 693 | run.bounds.size.y = dims.y; |
644 | run.visBounds = run.bounds; | 694 | run.visBounds = run.bounds; |
645 | run.visBounds.size.x = dims.x; | 695 | run.visBounds.size.x = dims.x; |
@@ -671,7 +721,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
671 | iGmMediaInfo img; | 721 | iGmMediaInfo img; |
672 | imageInfo_Media(d->media, imageId, &img); | 722 | imageInfo_Media(d->media, imageId, &img); |
673 | const iInt2 imgSize = imageSize_Media(d->media, imageId); | 723 | const iInt2 imgSize = imageSize_Media(d->media, imageId); |
674 | linkContentLaidOut_GmDocument_(d, &img, run.linkId); | 724 | linkContentWasLaidOut_GmDocument_(d, &img, run.linkId); |
675 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 725 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
676 | pos.y += margin; | 726 | pos.y += margin; |
677 | run.bounds.pos = pos; | 727 | run.bounds.pos = pos; |
@@ -699,7 +749,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
699 | else if (audioId) { | 749 | else if (audioId) { |
700 | iGmMediaInfo info; | 750 | iGmMediaInfo info; |
701 | audioInfo_Media(d->media, audioId, &info); | 751 | audioInfo_Media(d->media, audioId, &info); |
702 | linkContentLaidOut_GmDocument_(d, &info, run.linkId); | 752 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); |
703 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 753 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
704 | pos.y += margin; | 754 | pos.y += margin; |
705 | run.bounds.pos = pos; | 755 | run.bounds.pos = pos; |
@@ -716,7 +766,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
716 | else if (downloadId) { | 766 | else if (downloadId) { |
717 | iGmMediaInfo info; | 767 | iGmMediaInfo info; |
718 | downloadInfo_Media(d->media, downloadId, &info); | 768 | downloadInfo_Media(d->media, downloadId, &info); |
719 | linkContentLaidOut_GmDocument_(d, &info, run.linkId); | 769 | linkContentWasLaidOut_GmDocument_(d, &info, run.linkId); |
720 | const int margin = lineHeight_Text(paragraph_FontId) / 2; | 770 | const int margin = lineHeight_Text(paragraph_FontId) / 2; |
721 | pos.y += margin; | 771 | pos.y += margin; |
722 | run.bounds.pos = pos; | 772 | run.bounds.pos = pos; |
@@ -773,6 +823,7 @@ void init_GmDocument(iGmDocument *d) { | |||
773 | init_String(&d->bannerText); | 823 | init_String(&d->bannerText); |
774 | init_String(&d->title); | 824 | init_String(&d->title); |
775 | init_Array(&d->headings, sizeof(iGmHeading)); | 825 | init_Array(&d->headings, sizeof(iGmHeading)); |
826 | init_Array(&d->preMeta, sizeof(iGmPreMeta)); | ||
776 | d->themeSeed = 0; | 827 | d->themeSeed = 0; |
777 | d->siteIcon = 0; | 828 | d->siteIcon = 0; |
778 | d->media = new_Media(); | 829 | d->media = new_Media(); |
@@ -784,6 +835,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
784 | deinit_String(&d->title); | 835 | deinit_String(&d->title); |
785 | clearLinks_GmDocument_(d); | 836 | clearLinks_GmDocument_(d); |
786 | deinit_PtrArray(&d->links); | 837 | deinit_PtrArray(&d->links); |
838 | deinit_Array(&d->preMeta); | ||
787 | deinit_Array(&d->headings); | 839 | deinit_Array(&d->headings); |
788 | deinit_Array(&d->layout); | 840 | deinit_Array(&d->layout); |
789 | deinit_String(&d->localHost); | 841 | deinit_String(&d->localHost); |
@@ -804,6 +856,7 @@ void reset_GmDocument(iGmDocument *d) { | |||
804 | clearLinks_GmDocument_(d); | 856 | clearLinks_GmDocument_(d); |
805 | clear_Array(&d->layout); | 857 | clear_Array(&d->layout); |
806 | clear_Array(&d->headings); | 858 | clear_Array(&d->headings); |
859 | clear_Array(&d->preMeta); | ||
807 | clear_String(&d->url); | 860 | clear_String(&d->url); |
808 | clear_String(&d->localHost); | 861 | clear_String(&d->localHost); |
809 | d->themeSeed = 0; | 862 | d->themeSeed = 0; |
@@ -815,6 +868,8 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | |||
815 | set_Color(tmBannerSideTitle_ColorId, | 868 | set_Color(tmBannerSideTitle_ColorId, |
816 | mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId), | 869 | mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId), |
817 | theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); | 870 | theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); |
871 | set_Color(tmAltTextBackground_ColorId, mix_Color(get_Color(tmQuoteIcon_ColorId), | ||
872 | get_Color(tmBackground_ColorId), 0.85f)); | ||
818 | if (theme == colorfulDark_GmDocumentTheme) { | 873 | if (theme == colorfulDark_GmDocumentTheme) { |
819 | /* Ensure paragraph text and link text aren't too similarly colored. */ | 874 | /* Ensure paragraph text and link text aren't too similarly colored. */ |
820 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { | 875 | if (delta_Color(get_Color(tmLinkText_ColorId), get_Color(tmParagraph_ColorId)) < 100) { |
@@ -824,35 +879,6 @@ static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | |||
824 | } | 879 | } |
825 | set_Color(tmLinkCustomIconVisited_ColorId, | 880 | set_Color(tmLinkCustomIconVisited_ColorId, |
826 | mix_Color(get_Color(tmLinkIconVisited_ColorId), get_Color(tmLinkIcon_ColorId), 0.5f)); | 881 | mix_Color(get_Color(tmLinkIconVisited_ColorId), get_Color(tmLinkIcon_ColorId), 0.5f)); |
827 | #if 0 | ||
828 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(white_ColorId)); | ||
829 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(black_ColorId)); | ||
830 | switch (theme) { | ||
831 | case colorfulDark_GmDocumentTheme: | ||
832 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(tmBannerTitle_ColorId)); | ||
833 | if (equal_Color(get_Color(tmOutlineHeadingAbove_ColorId), | ||
834 | get_Color(tmOutlineHeadingBelow_ColorId))) { | ||
835 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(tmHeading3_ColorId)); | ||
836 | } | ||
837 | break; | ||
838 | case colorfulLight_GmDocumentTheme: | ||
839 | case sepia_GmDocumentTheme: | ||
840 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(black_ColorId)); | ||
841 | set_Color(tmOutlineHeadingBelow_ColorId, mix_Color(get_Color(tmBackground_ColorId), get_Color(black_ColorId), 0.6f)); | ||
842 | break; | ||
843 | case gray_GmDocumentTheme: | ||
844 | set_Color(tmOutlineHeadingBelow_ColorId, get_Color(gray75_ColorId)); | ||
845 | break; | ||
846 | case white_GmDocumentTheme: | ||
847 | set_Color(tmOutlineHeadingBelow_ColorId, mix_Color(get_Color(tmBannerIcon_ColorId), get_Color(white_ColorId), 0.6f)); | ||
848 | break; | ||
849 | case highContrast_GmDocumentTheme: | ||
850 | set_Color(tmOutlineHeadingAbove_ColorId, get_Color(black_ColorId)); | ||
851 | break; | ||
852 | default: | ||
853 | break; | ||
854 | } | ||
855 | #endif | ||
856 | } | 882 | } |
857 | 883 | ||
858 | static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { | 884 | static void updateIconBasedOnUrl_GmDocument_(iGmDocument *d) { |
@@ -1406,6 +1432,20 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | |||
1406 | setWidth_GmDocument(d, width); /* re-do layout */ | 1432 | setWidth_GmDocument(d, width); /* re-do layout */ |
1407 | } | 1433 | } |
1408 | 1434 | ||
1435 | void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { | ||
1436 | if (preId > 0 && preId <= size_Array(&d->preMeta)) { | ||
1437 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); | ||
1438 | meta->flags ^= folded_GmPreMetaFlag; | ||
1439 | } | ||
1440 | } | ||
1441 | |||
1442 | const iGmPreMeta *preMeta_GmDocument(const iGmDocument *d, uint16_t preId) { | ||
1443 | if (preId > 0 && preId <= size_Array(&d->preMeta)) { | ||
1444 | return constAt_Array(&d->preMeta, preId - 1); | ||
1445 | } | ||
1446 | return NULL; | ||
1447 | } | ||
1448 | |||
1409 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | 1449 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, |
1410 | void *context) { | 1450 | void *context) { |
1411 | iBool isInside = iFalse; | 1451 | iBool isInside = iFalse; |
@@ -1684,4 +1724,9 @@ iRangecc findLoc_GmRun(const iGmRun *d, iInt2 pos) { | |||
1684 | return loc; | 1724 | return loc; |
1685 | } | 1725 | } |
1686 | 1726 | ||
1727 | iInt2 preRunMargin_GmDocument(const iGmDocument *d, uint16_t preId) { | ||
1728 | iUnused(d, preId); | ||
1729 | return init_I2(3 * gap_Text, 2 * gap_Text); | ||
1730 | } | ||
1731 | |||
1687 | iDefineClass(GmDocument) | 1732 | iDefineClass(GmDocument) |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 57c19e9a..14c5c85d 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | #include <the_Foundation/time.h> | 32 | #include <the_Foundation/time.h> |
33 | 33 | ||
34 | iDeclareType(GmHeading) | 34 | iDeclareType(GmHeading) |
35 | iDeclareType(GmPreMeta) | ||
35 | iDeclareType(GmRun) | 36 | iDeclareType(GmRun) |
36 | 37 | ||
37 | enum iGmLineType { | 38 | enum iGmLineType { |
@@ -89,6 +90,20 @@ struct Impl_GmHeading { | |||
89 | int level; /* 0, 1, 2 */ | 90 | int level; /* 0, 1, 2 */ |
90 | }; | 91 | }; |
91 | 92 | ||
93 | enum iGmPreMetaFlag { | ||
94 | folded_GmPreMetaFlag = 0x1, | ||
95 | topLeft_GmPreMetaFlag = 0x2, | ||
96 | }; | ||
97 | |||
98 | struct Impl_GmPreMeta { | ||
99 | iRangecc bounds; /* including ``` markers */ | ||
100 | iRangecc altText; /* range in source */ | ||
101 | iRangecc contents; /* just the content lines */ | ||
102 | int flags; | ||
103 | /* TODO: refactor old code to incorporate wide scroll handling here */ | ||
104 | iRect pixelRect; | ||
105 | }; | ||
106 | |||
92 | enum iGmRunFlags { | 107 | enum iGmRunFlags { |
93 | decoration_GmRunFlag = iBit(1), /* not part of the source */ | 108 | decoration_GmRunFlag = iBit(1), /* not part of the source */ |
94 | startOfLine_GmRunFlag = iBit(2), | 109 | startOfLine_GmRunFlag = iBit(2), |
@@ -97,6 +112,7 @@ enum iGmRunFlags { | |||
97 | quoteBorder_GmRunFlag = iBit(5), | 112 | quoteBorder_GmRunFlag = iBit(5), |
98 | wide_GmRunFlag = iBit(6), /* horizontally scrollable */ | 113 | wide_GmRunFlag = iBit(6), /* horizontally scrollable */ |
99 | footer_GmRunFlag = iBit(7), | 114 | footer_GmRunFlag = iBit(7), |
115 | altText_GmRunFlag = iBit(8), | ||
100 | }; | 116 | }; |
101 | 117 | ||
102 | enum iGmRunMediaType { | 118 | enum iGmRunMediaType { |
@@ -150,6 +166,7 @@ void setWidth_GmDocument (iGmDocument *, int width); | |||
150 | void redoLayout_GmDocument (iGmDocument *); | 166 | void redoLayout_GmDocument (iGmDocument *); |
151 | void setUrl_GmDocument (iGmDocument *, const iString *url); | 167 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
152 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 168 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); |
169 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); | ||
153 | 170 | ||
154 | void reset_GmDocument (iGmDocument *); /* free images */ | 171 | void reset_GmDocument (iGmDocument *); /* free images */ |
155 | 172 | ||
@@ -194,4 +211,5 @@ const iTime * linkTime_GmDocument (const iGmDocument *, iGmLinkId linkId); | |||
194 | iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); | 211 | iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); |
195 | const iString * title_GmDocument (const iGmDocument *); | 212 | const iString * title_GmDocument (const iGmDocument *); |
196 | iChar siteIcon_GmDocument (const iGmDocument *); | 213 | iChar siteIcon_GmDocument (const iGmDocument *); |
197 | 214 | const iGmPreMeta *preMeta_GmDocument (const iGmDocument *, uint16_t preId); | |
215 | iInt2 preRunMargin_GmDocument (const iGmDocument *, uint16_t preId); | ||
diff --git a/src/ui/color.h b/src/ui/color.h index 057ca42d..cd2f95d3 100644 --- a/src/ui/color.h +++ b/src/ui/color.h | |||
@@ -115,7 +115,8 @@ enum iColorId { | |||
115 | uiTextAppTitle_ColorId, | 115 | uiTextAppTitle_ColorId, |
116 | uiBackgroundSidebar_ColorId, | 116 | uiBackgroundSidebar_ColorId, |
117 | uiBackgroundMenu_ColorId, | 117 | uiBackgroundMenu_ColorId, |
118 | tmLinkCustomIconVisited_ColorId, | 118 | tmLinkCustomIconVisited_ColorId, /* derived from other theme colors */ |
119 | tmAltTextBackground_ColorId, /* derived from other theme colors */ | ||
119 | 120 | ||
120 | /* content theme colors */ | 121 | /* content theme colors */ |
121 | tmFirst_ColorId, | 122 | tmFirst_ColorId, |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6bb16a93..93297c70 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -202,7 +202,8 @@ struct Impl_DocumentWidget { | |||
202 | iRangecc foundMark; | 202 | iRangecc foundMark; |
203 | int pageMargin; | 203 | int pageMargin; |
204 | iPtrArray visibleLinks; | 204 | iPtrArray visibleLinks; |
205 | iPtrArray visibleWideRuns; /* scrollable blocks */ | 205 | iPtrArray visiblePre; |
206 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ | ||
206 | iArray wideRunOffsets; | 207 | iArray wideRunOffsets; |
207 | iAnim animWideRunOffset; | 208 | iAnim animWideRunOffset; |
208 | uint16_t animWideRunId; | 209 | uint16_t animWideRunId; |
@@ -211,6 +212,8 @@ struct Impl_DocumentWidget { | |||
211 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 212 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ |
212 | float grabbedStartVolume; | 213 | float grabbedStartVolume; |
213 | int mediaTimer; | 214 | int mediaTimer; |
215 | const iGmRun * hoverPre; /* for clicking */ | ||
216 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
214 | const iGmRun * hoverLink; | 217 | const iGmRun * hoverLink; |
215 | const iGmRun * contextLink; | 218 | const iGmRun * contextLink; |
216 | const iGmRun * firstVisibleRun; | 219 | const iGmRun * firstVisibleRun; |
@@ -220,6 +223,7 @@ struct Impl_DocumentWidget { | |||
220 | float initNormScrollY; | 223 | float initNormScrollY; |
221 | iAnim scrollY; | 224 | iAnim scrollY; |
222 | iAnim sideOpacity; | 225 | iAnim sideOpacity; |
226 | iAnim altTextOpacity; | ||
223 | iScrollWidget *scroll; | 227 | iScrollWidget *scroll; |
224 | iWidget * menu; | 228 | iWidget * menu; |
225 | iWidget * playerMenu; | 229 | iWidget * playerMenu; |
@@ -260,6 +264,8 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
260 | d->selectMark = iNullRange; | 264 | d->selectMark = iNullRange; |
261 | d->foundMark = iNullRange; | 265 | d->foundMark = iNullRange; |
262 | d->pageMargin = 5; | 266 | d->pageMargin = 5; |
267 | d->hoverPre = NULL; | ||
268 | d->hoverAltPre = NULL; | ||
263 | d->hoverLink = NULL; | 269 | d->hoverLink = NULL; |
264 | d->contextLink = NULL; | 270 | d->contextLink = NULL; |
265 | d->firstVisibleRun = NULL; | 271 | d->firstVisibleRun = NULL; |
@@ -267,12 +273,14 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
267 | d->visBuf = new_VisBuf(); | 273 | d->visBuf = new_VisBuf(); |
268 | d->invalidRuns = new_PtrSet(); | 274 | d->invalidRuns = new_PtrSet(); |
269 | init_Anim(&d->sideOpacity, 0); | 275 | init_Anim(&d->sideOpacity, 0); |
276 | init_Anim(&d->altTextOpacity, 0); | ||
270 | d->sourceStatus = none_GmStatusCode; | 277 | d->sourceStatus = none_GmStatusCode; |
271 | init_String(&d->sourceHeader); | 278 | init_String(&d->sourceHeader); |
272 | init_String(&d->sourceMime); | 279 | init_String(&d->sourceMime); |
273 | init_Block(&d->sourceContent, 0); | 280 | init_Block(&d->sourceContent, 0); |
274 | iZap(d->sourceTime); | 281 | iZap(d->sourceTime); |
275 | init_PtrArray(&d->visibleLinks); | 282 | init_PtrArray(&d->visibleLinks); |
283 | init_PtrArray(&d->visiblePre); | ||
276 | init_PtrArray(&d->visibleWideRuns); | 284 | init_PtrArray(&d->visibleWideRuns); |
277 | init_Array(&d->wideRunOffsets, sizeof(int)); | 285 | init_Array(&d->wideRunOffsets, sizeof(int)); |
278 | init_PtrArray(&d->visibleMedia); | 286 | init_PtrArray(&d->visibleMedia); |
@@ -322,6 +330,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
322 | deinit_Array(&d->wideRunOffsets); | 330 | deinit_Array(&d->wideRunOffsets); |
323 | deinit_PtrArray(&d->visibleMedia); | 331 | deinit_PtrArray(&d->visibleMedia); |
324 | deinit_PtrArray(&d->visibleWideRuns); | 332 | deinit_PtrArray(&d->visibleWideRuns); |
333 | deinit_PtrArray(&d->visiblePre); | ||
325 | deinit_PtrArray(&d->visibleLinks); | 334 | deinit_PtrArray(&d->visibleLinks); |
326 | delete_Block(d->certFingerprint); | 335 | delete_Block(d->certFingerprint); |
327 | delete_String(d->certSubject); | 336 | delete_String(d->certSubject); |
@@ -419,8 +428,11 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { | |||
419 | } | 428 | } |
420 | d->lastVisibleRun = run; | 429 | d->lastVisibleRun = run; |
421 | } | 430 | } |
422 | if (run->preId && run->flags & wide_GmRunFlag) { | 431 | if (run->preId) { |
423 | pushBack_PtrArray(&d->visibleWideRuns, run); | 432 | pushBack_PtrArray(&d->visiblePre, run); |
433 | if (run->flags & wide_GmRunFlag) { | ||
434 | pushBack_PtrArray(&d->visibleWideRuns, run); | ||
435 | } | ||
424 | } | 436 | } |
425 | if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { | 437 | if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { |
426 | iAssert(run->mediaId); | 438 | iAssert(run->mediaId); |
@@ -501,10 +513,18 @@ static void invalidateWideRunsWithNonzeroOffset_DocumentWidget_(iDocumentWidget | |||
501 | } | 513 | } |
502 | } | 514 | } |
503 | 515 | ||
516 | static void animate_DocumentWidget_(void *ticker) { | ||
517 | iDocumentWidget *d = ticker; | ||
518 | if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->altTextOpacity)) { | ||
519 | addTicker_App(animate_DocumentWidget_, d); | ||
520 | } | ||
521 | } | ||
522 | |||
504 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | 523 | static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { |
505 | const iWidget *w = constAs_Widget(d); | 524 | const iWidget *w = constAs_Widget(d); |
506 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 525 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
507 | const iGmRun * oldHoverLink = d->hoverLink; | 526 | const iGmRun * oldHoverLink = d->hoverLink; |
527 | d->hoverPre = NULL; | ||
508 | d->hoverLink = NULL; | 528 | d->hoverLink = NULL; |
509 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), value_Anim(&d->scrollY)); | 529 | const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), value_Anim(&d->scrollY)); |
510 | if (isHover_Widget(w) && (~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) && | 530 | if (isHover_Widget(w) && (~d->flags & noHoverWhileScrolling_DocumentWidgetFlag) && |
@@ -525,11 +545,31 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
525 | if (d->hoverLink) { | 545 | if (d->hoverLink) { |
526 | invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); | 546 | invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); |
527 | } | 547 | } |
528 | refresh_Widget(as_Widget(d)); | 548 | refresh_Widget(w); |
549 | } | ||
550 | if (isHover_Widget(w)) { | ||
551 | iConstForEach(PtrArray, j, &d->visiblePre) { | ||
552 | const iGmRun *run = j.ptr; | ||
553 | if (contains_Rect(run->bounds, hoverPos)) { | ||
554 | d->hoverPre = run; | ||
555 | d->hoverAltPre = run; | ||
556 | break; | ||
557 | } | ||
558 | } | ||
559 | } | ||
560 | if (!d->hoverPre && targetValue_Anim(&d->altTextOpacity) > 0.5f) { | ||
561 | setValue_Anim(&d->altTextOpacity, 0.0f, 300); | ||
562 | animate_DocumentWidget_(d); | ||
563 | refresh_Widget(w); | ||
564 | } | ||
565 | else if (d->hoverPre && targetValue_Anim(&d->altTextOpacity) < 0.5f) { | ||
566 | setValue_Anim(&d->altTextOpacity, 1.0f, 0); | ||
567 | refresh_Widget(w); | ||
529 | } | 568 | } |
530 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { | 569 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { |
531 | setCursor_Window(get_Window(), | 570 | setCursor_Window(get_Window(), |
532 | d->hoverLink ? SDL_SYSTEM_CURSOR_HAND : SDL_SYSTEM_CURSOR_IBEAM); | 571 | d->hoverLink || d->hoverPre ? SDL_SYSTEM_CURSOR_HAND |
572 | : SDL_SYSTEM_CURSOR_IBEAM); | ||
533 | if (d->hoverLink && | 573 | if (d->hoverLink && |
534 | linkFlags_GmDocument(d->doc, d->hoverLink->linkId) & permanent_GmLinkFlag) { | 574 | linkFlags_GmDocument(d->doc, d->hoverLink->linkId) & permanent_GmLinkFlag) { |
535 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); /* not dismissable */ | 575 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); /* not dismissable */ |
@@ -537,13 +577,6 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
537 | } | 577 | } |
538 | } | 578 | } |
539 | 579 | ||
540 | static void animate_DocumentWidget_(void *ticker) { | ||
541 | iDocumentWidget *d = ticker; | ||
542 | if (!isFinished_Anim(&d->sideOpacity)) { | ||
543 | addTicker_App(animate_DocumentWidget_, d); | ||
544 | } | ||
545 | } | ||
546 | |||
547 | static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { | 580 | static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { |
548 | float opacity = 0.0f; | 581 | float opacity = 0.0f; |
549 | const iGmRun *banner = siteBanner_GmDocument(d->doc); | 582 | const iGmRun *banner = siteBanner_GmDocument(d->doc); |
@@ -649,6 +682,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
649 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); | 682 | docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); |
650 | clear_PtrArray(&d->visibleLinks); | 683 | clear_PtrArray(&d->visibleLinks); |
651 | clear_PtrArray(&d->visibleWideRuns); | 684 | clear_PtrArray(&d->visibleWideRuns); |
685 | clear_PtrArray(&d->visiblePre); | ||
652 | clear_PtrArray(&d->visibleMedia); | 686 | clear_PtrArray(&d->visibleMedia); |
653 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); | 687 | const iRangecc oldHeading = currentHeading_DocumentWidget_(d); |
654 | /* Scan for visible runs. */ { | 688 | /* Scan for visible runs. */ { |
@@ -765,6 +799,8 @@ static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) { | |||
765 | static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { | 799 | static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { |
766 | d->foundMark = iNullRange; | 800 | d->foundMark = iNullRange; |
767 | d->selectMark = iNullRange; | 801 | d->selectMark = iNullRange; |
802 | d->hoverPre = NULL; | ||
803 | d->hoverAltPre = NULL; | ||
768 | d->hoverLink = NULL; | 804 | d->hoverLink = NULL; |
769 | d->contextLink = NULL; | 805 | d->contextLink = NULL; |
770 | d->firstVisibleRun = NULL; | 806 | d->firstVisibleRun = NULL; |
@@ -1199,6 +1235,18 @@ static void scrollWideBlock_DocumentWidget_(iDocumentWidget *d, iInt2 mousePos, | |||
1199 | } | 1235 | } |
1200 | } | 1236 | } |
1201 | 1237 | ||
1238 | static void togglePreFold_DocumentWidget_(iDocumentWidget *d, uint16_t preId) { | ||
1239 | d->hoverPre = NULL; | ||
1240 | d->hoverAltPre = NULL; | ||
1241 | d->selectMark = iNullRange; | ||
1242 | foldPre_GmDocument(d->doc, preId); | ||
1243 | redoLayout_GmDocument(d->doc); | ||
1244 | scroll_DocumentWidget_(d, 0); | ||
1245 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); | ||
1246 | invalidate_DocumentWidget_(d); | ||
1247 | refresh_Widget(as_Widget(d)); | ||
1248 | } | ||
1249 | |||
1202 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 1250 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
1203 | if (!d->request) { | 1251 | if (!d->request) { |
1204 | return; | 1252 | return; |
@@ -2681,6 +2729,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
2681 | } | 2729 | } |
2682 | if (!isMoved_Click(&d->click)) { | 2730 | if (!isMoved_Click(&d->click)) { |
2683 | setFocus_Widget(NULL); | 2731 | setFocus_Widget(NULL); |
2732 | if (d->hoverPre) { | ||
2733 | togglePreFold_DocumentWidget_(d, d->hoverPre->preId); | ||
2734 | return iTrue; | ||
2735 | } | ||
2684 | if (d->hoverLink) { | 2736 | if (d->hoverLink) { |
2685 | const iGmLinkId linkId = d->hoverLink->linkId; | 2737 | const iGmLinkId linkId = d->hoverLink->linkId; |
2686 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | 2738 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); |
@@ -2994,7 +3046,14 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
2994 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ | 3046 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ |
2995 | } | 3047 | } |
2996 | } | 3048 | } |
2997 | if (run->flags & siteBanner_GmRunFlag) { | 3049 | if (run->flags & altText_GmRunFlag) { |
3050 | const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); | ||
3051 | fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmAltTextBackground_ColorId); | ||
3052 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); | ||
3053 | drawWrapRange_Text(run->font, add_I2(visPos, margin), | ||
3054 | run->visBounds.size.x - 2 * margin.x, run->color, run->text); | ||
3055 | } | ||
3056 | else if (run->flags & siteBanner_GmRunFlag) { | ||
2998 | /* Banner background. */ | 3057 | /* Banner background. */ |
2999 | iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds), | 3058 | iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds), |
3000 | init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), | 3059 | init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), |
@@ -3157,8 +3216,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3157 | } | 3216 | } |
3158 | } | 3217 | } |
3159 | } | 3218 | } |
3160 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | 3219 | drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); |
3161 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); | 3220 | drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); |
3162 | } | 3221 | } |
3163 | 3222 | ||
3164 | static int drawSideRect_(iPaint *p, iRect rect) { | 3223 | static int drawSideRect_(iPaint *p, iRect rect) { |
@@ -3392,7 +3451,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3392 | hasSiteBanner_GmDocument(d->doc) ? tmBannerBackground_ColorId | 3451 | hasSiteBanner_GmDocument(d->doc) ? tmBannerBackground_ColorId |
3393 | : tmBackground_ColorId); | 3452 | : tmBackground_ColorId); |
3394 | } | 3453 | } |
3395 | const int yBottom = yTop + size_GmDocument(d->doc).y; | 3454 | const int yBottom = yTop + size_GmDocument(d->doc).y + 1; |
3396 | if (yBottom < bottom_Rect(bounds)) { | 3455 | if (yBottom < bottom_Rect(bounds)) { |
3397 | fillRect_Paint(&ctx.paint, | 3456 | fillRect_Paint(&ctx.paint, |
3398 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), | 3457 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), |
@@ -3412,6 +3471,34 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
3412 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); | 3471 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); |
3413 | } | 3472 | } |
3414 | draw_Widget(w); | 3473 | draw_Widget(w); |
3474 | /* Alt text. */ | ||
3475 | const float altTextOpacity = value_Anim(&d->altTextOpacity); | ||
3476 | if (d->hoverAltPre && altTextOpacity > 0) { | ||
3477 | const iGmPreMeta *meta = preMeta_GmDocument(d->doc, d->hoverAltPre->preId); | ||
3478 | if (meta->flags & topLeft_GmPreMetaFlag && ~meta->flags & decoration_GmRunFlag && | ||
3479 | !isEmpty_Range(&meta->altText)) { | ||
3480 | const int margin = 2 * gap_UI; | ||
3481 | const int altFont = uiLabel_FontId; | ||
3482 | const int wrap = docBounds.size.x - 2 * margin; | ||
3483 | iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), | ||
3484 | -value_Anim(&d->scrollY)); | ||
3485 | const iInt2 textSize = advanceWrapRange_Text(altFont, wrap, meta->altText); | ||
3486 | pos.y -= textSize.y + gap_UI; | ||
3487 | pos.y = iMax(pos.y, top_Rect(bounds)); | ||
3488 | const iRect altRect = { pos, init_I2(docBounds.size.x, textSize.y) }; | ||
3489 | ctx.paint.alpha = altTextOpacity * 255; | ||
3490 | if (altTextOpacity < 1) { | ||
3491 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
3492 | } | ||
3493 | fillRect_Paint(&ctx.paint, altRect, tmAltTextBackground_ColorId); | ||
3494 | drawRect_Paint(&ctx.paint, altRect, tmQuoteIcon_ColorId); | ||
3495 | setOpacity_Text(altTextOpacity); | ||
3496 | drawWrapRange_Text(altFont, addX_I2(pos, margin), wrap, | ||
3497 | tmQuote_ColorId, meta->altText); | ||
3498 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
3499 | setOpacity_Text(1.0f); | ||
3500 | } | ||
3501 | } | ||
3415 | } | 3502 | } |
3416 | 3503 | ||
3417 | /*----------------------------------------------------------------------------------------------*/ | 3504 | /*----------------------------------------------------------------------------------------------*/ |