diff options
-rw-r--r-- | src/gmdocument.c | 12 | ||||
-rw-r--r-- | src/gmdocument.h | 22 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 78 |
3 files changed, 78 insertions, 34 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 15d81603..f7016ba4 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -245,7 +245,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
245 | header2_FontId, | 245 | header2_FontId, |
246 | header3_FontId, | 246 | header3_FontId, |
247 | regular_FontId, | 247 | regular_FontId, |
248 | }; | 248 | }; |
249 | static const int colors[max_GmLineType] = { | 249 | static const int colors[max_GmLineType] = { |
250 | gray75_ColorId, | 250 | gray75_ColorId, |
251 | gray75_ColorId, | 251 | gray75_ColorId, |
@@ -255,7 +255,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
255 | white_ColorId, | 255 | white_ColorId, |
256 | white_ColorId, | 256 | white_ColorId, |
257 | white_ColorId, | 257 | white_ColorId, |
258 | }; | 258 | }; |
259 | static const int indents[max_GmLineType] = { | 259 | static const int indents[max_GmLineType] = { |
260 | 5, 10, 5, 10, 0, 0, 0, 5 | 260 | 5, 10, 5, 10, 0, 0, 0, 5 |
261 | }; | 261 | }; |
@@ -290,6 +290,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
290 | } | 290 | } |
291 | while (nextSplit_Rangecc(&content, "\n", &line)) { | 291 | while (nextSplit_Rangecc(&content, "\n", &line)) { |
292 | iGmRun run; | 292 | iGmRun run; |
293 | run.flags = 0; | ||
293 | run.color = white_ColorId; | 294 | run.color = white_ColorId; |
294 | run.linkId = 0; | 295 | run.linkId = 0; |
295 | run.imageId = 0; | 296 | run.imageId = 0; |
@@ -402,7 +403,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
402 | isFirstText = iFalse; | 403 | isFirstText = iFalse; |
403 | } | 404 | } |
404 | iRangecc runLine = line; | 405 | iRangecc runLine = line; |
405 | /* Create one or more runs for this line. */ | 406 | /* Create one or more text runs for this line. */ |
407 | run.flags |= startOfLine_GmRunFlag; | ||
408 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ | ||
406 | while (!isEmpty_Range(&runLine)) { | 409 | while (!isEmpty_Range(&runLine)) { |
407 | /* Little bit of breathing space between wrapped lines. */ | 410 | /* Little bit of breathing space between wrapped lines. */ |
408 | if ((type == text_GmLineType || type == quote_GmLineType || | 411 | if ((type == text_GmLineType || type == quote_GmLineType || |
@@ -427,10 +430,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
427 | contPos = runLine.end; | 430 | contPos = runLine.end; |
428 | } | 431 | } |
429 | pushBack_Array(&d->layout, &run); | 432 | pushBack_Array(&d->layout, &run); |
433 | run.flags &= ~startOfLine_GmRunFlag; | ||
430 | runLine.start = contPos; | 434 | runLine.start = contPos; |
431 | trimStart_Rangecc(&runLine); | 435 | trimStart_Rangecc(&runLine); |
432 | pos.y += lineHeight_Text(run.font); | 436 | pos.y += lineHeight_Text(run.font); |
433 | } | 437 | } |
438 | /* Flag the end of line, too. */ | ||
439 | ((iGmRun *) back_Array(&d->layout))->flags |= endOfLine_GmRunFlag; | ||
434 | /* Image content. */ | 440 | /* Image content. */ |
435 | if (type == link_GmLineType) { | 441 | if (type == link_GmLineType) { |
436 | const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); | 442 | const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 584efb5a..c353c733 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -12,14 +12,14 @@ iDeclareType(GmRun) | |||
12 | typedef uint16_t iGmLinkId; | 12 | typedef uint16_t iGmLinkId; |
13 | 13 | ||
14 | enum iGmLinkFlags { | 14 | enum iGmLinkFlags { |
15 | userFriendly_GmLinkFlag = 0x1, | 15 | userFriendly_GmLinkFlag = iBit(1), |
16 | remote_GmLinkFlag = 0x2, | 16 | remote_GmLinkFlag = iBit(2), |
17 | http_GmLinkFlag = 0x4, | 17 | http_GmLinkFlag = iBit(3), |
18 | gopher_GmLinkFlag = 0x8, | 18 | gopher_GmLinkFlag = iBit(4), |
19 | file_GmLinkFlag = 0x10, | 19 | file_GmLinkFlag = iBit(5), |
20 | imageFileExtension_GmLinkFlag = 0x20, | 20 | imageFileExtension_GmLinkFlag = iBit(6), |
21 | audioFileExtension_GmLinkFlag = 0x40, | 21 | audioFileExtension_GmLinkFlag = iBit(7), |
22 | content_GmLinkFlag = 0x80, /* content visible below */ | 22 | content_GmLinkFlag = iBit(8), /* content visible below */ |
23 | }; | 23 | }; |
24 | 24 | ||
25 | iDeclareType(GmImageInfo) | 25 | iDeclareType(GmImageInfo) |
@@ -30,10 +30,16 @@ struct Impl_GmImageInfo { | |||
30 | const char *mime; | 30 | const char *mime; |
31 | }; | 31 | }; |
32 | 32 | ||
33 | enum iGmRunFlags { | ||
34 | startOfLine_GmRunFlag = iBit(1), | ||
35 | endOfLine_GmRunFlag = iBit(2), | ||
36 | }; | ||
37 | |||
33 | struct Impl_GmRun { | 38 | struct Impl_GmRun { |
34 | iRangecc text; | 39 | iRangecc text; |
35 | uint8_t font; | 40 | uint8_t font; |
36 | uint8_t color; | 41 | uint8_t color; |
42 | uint8_t flags; | ||
37 | iRect bounds; /* used for hit testing, may extend to edges */ | 43 | iRect bounds; /* used for hit testing, may extend to edges */ |
38 | iRect visBounds; /* actual visual bounds */ | 44 | iRect visBounds; /* actual visual bounds */ |
39 | iGmLinkId linkId; /* zero for non-links */ | 45 | iGmLinkId linkId; /* zero for non-links */ |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 98d9eca5..e908f090 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -843,7 +843,8 @@ iDeclareType(DrawContext) | |||
843 | 843 | ||
844 | struct Impl_DrawContext { | 844 | struct Impl_DrawContext { |
845 | const iDocumentWidget *widget; | 845 | const iDocumentWidget *widget; |
846 | iRect bounds; | 846 | iRect widgetBounds; |
847 | iRect bounds; /* document area */ | ||
847 | iPaint paint; | 848 | iPaint paint; |
848 | iBool inSelectMark; | 849 | iBool inSelectMark; |
849 | iBool inFoundMark; | 850 | iBool inFoundMark; |
@@ -895,8 +896,24 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
895 | initRange_String(&text, run->text); | 896 | initRange_String(&text, run->text); |
896 | enum iColorId fg = run->color; | 897 | enum iColorId fg = run->color; |
897 | const iGmDocument *doc = d->widget->doc; | 898 | const iGmDocument *doc = d->widget->doc; |
899 | const iBool isHover = | ||
900 | run->linkId != 0 && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && | ||
901 | !isEmpty_Rect(run->bounds); | ||
902 | const iInt2 visPos = add_I2(run->visBounds.pos, origin); | ||
903 | /* Text markers. */ | ||
904 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); | ||
905 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); | ||
906 | if (run->linkId) { | ||
907 | fg = linkColor_GmDocument(doc, run->linkId); | ||
908 | if (isHover && ~linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) { | ||
909 | fg = white_ColorId; | ||
910 | } | ||
911 | } | ||
912 | drawString_Text(run->font, visPos, fg, &text); | ||
913 | deinit_String(&text); | ||
914 | /* Presentation of links. */ | ||
898 | if (run->linkId) { | 915 | if (run->linkId) { |
899 | /* TODO: Visualize an ongoing media request. */ | 916 | /* TODO: Show status of an ongoing media request. */ |
900 | const int flags = linkFlags_GmDocument(doc, run->linkId); | 917 | const int flags = linkFlags_GmDocument(doc, run->linkId); |
901 | if (flags & content_GmLinkFlag) { | 918 | if (flags & content_GmLinkFlag) { |
902 | fg = linkColor_GmDocument(doc, run->linkId); | 919 | fg = linkColor_GmDocument(doc, run->linkId); |
@@ -909,7 +926,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
909 | info.mime, info.size.x, info.size.y, info.numBytes / 1.0e6f); | 926 | info.mime, info.size.x, info.size.y, info.numBytes / 1.0e6f); |
910 | if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { | 927 | if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { |
911 | appendFormat_String( | 928 | appendFormat_String( |
912 | &text, " %s\u2715", run == d->widget->hoverLink ? white_ColorEscape : ""); | 929 | &text, " %s\u2715", isHover ? white_ColorEscape : ""); |
913 | } | 930 | } |
914 | drawAlign_Text(default_FontId, | 931 | drawAlign_Text(default_FontId, |
915 | add_I2(topRight_Rect(run->bounds), origin), | 932 | add_I2(topRight_Rect(run->bounds), origin), |
@@ -919,10 +936,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
919 | deinit_String(&text); | 936 | deinit_String(&text); |
920 | } | 937 | } |
921 | } | 938 | } |
922 | else if (run == d->widget->hoverLink) { | 939 | else if (isHover) { |
923 | const iGmLinkId linkId = d->widget->hoverLink->linkId; | 940 | const iGmLinkId linkId = d->widget->hoverLink->linkId; |
924 | const iString * url = linkUrl_GmDocument(doc, linkId); | 941 | const iString * url = linkUrl_GmDocument(doc, linkId); |
925 | const int flags = linkFlags_GmDocument(doc, linkId); | 942 | const int flags = linkFlags_GmDocument(doc, linkId); |
926 | iUrl parts; | 943 | iUrl parts; |
927 | init_Url(&parts, url); | 944 | init_Url(&parts, url); |
928 | const iString *host = collect_String(newRange_String(parts.host)); | 945 | const iString *host = collect_String(newRange_String(parts.host)); |
@@ -931,27 +948,37 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
931 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | 948 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; |
932 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | 949 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; |
933 | iRect linkRect = moved_Rect(run->visBounds, origin); | 950 | iRect linkRect = moved_Rect(run->visBounds, origin); |
934 | if (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || showHost) { | 951 | if (run->flags & endOfLine_GmRunFlag && |
952 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || | ||
953 | showHost)) { | ||
954 | iString str; | ||
955 | init_String(&str); | ||
956 | format_String(&str, " \u2014%s%s%s\r%c%s", | ||
957 | showHost ? " " : "", | ||
958 | showHost ? cstr_String(host) : "", | ||
959 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
960 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), | ||
961 | showImage ? " View Image \U0001f5bc" | ||
962 | : showAudio ? " Play Audio \U0001f3b5" : ""); | ||
963 | const iInt2 textSize = measure_Text(default_FontId, cstr_String(&str)); | ||
964 | int tx = topRight_Rect(linkRect).x; | ||
965 | const char *msg = cstr_String(&str); | ||
966 | if (tx + textSize.x > right_Rect(d->widgetBounds)) { | ||
967 | tx = right_Rect(d->widgetBounds) - textSize.x; | ||
968 | fillRect_Paint(&d->paint, (iRect){ init_I2(tx, top_Rect(linkRect)), textSize }, | ||
969 | black_ColorId); | ||
970 | msg += 4; /* skip the space and dash */ | ||
971 | } | ||
935 | drawAlign_Text(default_FontId, | 972 | drawAlign_Text(default_FontId, |
936 | topRight_Rect(linkRect), | 973 | init_I2(tx, top_Rect(linkRect)), |
937 | fg - 1, | 974 | fg - 1, |
938 | left_Alignment, | 975 | left_Alignment, |
939 | " \u2014%s%s%s\r%c%s", | 976 | "%s", |
940 | showHost ? " " : "", | 977 | msg); |
941 | showHost ? cstr_String(host) : "", | 978 | deinit_String(&str); |
942 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
943 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), | ||
944 | showImage ? " View Image \U0001f5bc" | ||
945 | : showAudio ? " Play Audio \U0001f3b5" : ""); | ||
946 | } | 979 | } |
947 | } | 980 | } |
948 | } | 981 | } |
949 | const iInt2 visPos = add_I2(run->visBounds.pos, origin); | ||
950 | /* Text markers. */ | ||
951 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); | ||
952 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); | ||
953 | drawString_Text(run->font, visPos, fg, &text); | ||
954 | deinit_String(&text); | ||
955 | 982 | ||
956 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | 983 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); |
957 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); | 984 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); |
@@ -961,7 +988,12 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
961 | const iWidget *w = constAs_Widget(d); | 988 | const iWidget *w = constAs_Widget(d); |
962 | const iRect bounds = bounds_Widget(w); | 989 | const iRect bounds = bounds_Widget(w); |
963 | draw_Widget(w); | 990 | draw_Widget(w); |
964 | iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) }; | 991 | iDrawContext ctx = { |
992 | .widget = d, | ||
993 | .widgetBounds = /* omit scrollbar width */ | ||
994 | adjusted_Rect(bounds, zero_I2(), init_I2(-constAs_Widget(d->scroll)->rect.size.x, 0)), | ||
995 | .bounds = documentBounds_DocumentWidget_(d) | ||
996 | }; | ||
965 | init_Paint(&ctx.paint); | 997 | init_Paint(&ctx.paint); |
966 | fillRect_Paint(&ctx.paint, bounds, gray15_ColorId); | 998 | fillRect_Paint(&ctx.paint, bounds, gray15_ColorId); |
967 | setClip_Paint(&ctx.paint, bounds); | 999 | setClip_Paint(&ctx.paint, bounds); |