summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gmdocument.c89
-rw-r--r--src/gmdocument.h11
-rw-r--r--src/ui/color.c25
-rw-r--r--src/ui/color.h5
-rw-r--r--src/ui/documentwidget.c97
-rw-r--r--src/ui/window.c6
6 files changed, 178 insertions, 55 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index f8998c17..4a3f8e06 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -33,12 +33,16 @@ iDefineTypeConstruction(GmLink)
33iDeclareType(GmImage) 33iDeclareType(GmImage)
34 34
35struct Impl_GmImage { 35struct Impl_GmImage {
36 iInt2 size; 36 iInt2 size;
37 size_t numBytes;
38 iString mime;
39 iGmLinkId linkId;
37 SDL_Texture *texture; 40 SDL_Texture *texture;
38 iGmLinkId linkId;
39}; 41};
40 42
41void init_GmImage(iGmImage *d, const iBlock *data) { 43void init_GmImage(iGmImage *d, const iBlock *data) {
44 init_String(&d->mime);
45 d->numBytes = size_Block(data);
42 uint8_t *imgData = stbi_load_from_memory( 46 uint8_t *imgData = stbi_load_from_memory(
43 constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); 47 constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4);
44 if (!imgData) { 48 if (!imgData) {
@@ -60,6 +64,7 @@ void init_GmImage(iGmImage *d, const iBlock *data) {
60 64
61void deinit_GmImage(iGmImage *d) { 65void deinit_GmImage(iGmImage *d) {
62 SDL_DestroyTexture(d->texture); 66 SDL_DestroyTexture(d->texture);
67 deinit_String(&d->mime);
63} 68}
64 69
65iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) 70iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data)
@@ -379,6 +384,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
379 if (link->flags & remote_GmLinkFlag) { 384 if (link->flags & remote_GmLinkFlag) {
380 run.visBounds.pos.x -= gap_UI / 2; 385 run.visBounds.pos.x -= gap_UI / 2;
381 } 386 }
387
382 run.color = linkColor_GmDocument(d, run.linkId); 388 run.color = linkColor_GmDocument(d, run.linkId);
383 pushBack_Array(&d->layout, &run); 389 pushBack_Array(&d->layout, &run);
384 } 390 }
@@ -429,18 +435,29 @@ static void doLayout_GmDocument_(iGmDocument *d) {
429 if (type == link_GmLineType) { 435 if (type == link_GmLineType) {
430 const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); 436 const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId);
431 if (imgIndex != iInvalidPos) { 437 if (imgIndex != iInvalidPos) {
438 ((iGmLink *) at_PtrArray(&d->links, run.linkId - 1))->flags |= content_GmLinkFlag;
432 const iGmImage *img = constAt_PtrArray(&d->images, imgIndex); 439 const iGmImage *img = constAt_PtrArray(&d->images, imgIndex);
440 const int margin = 0.5f * lineHeight_Text(paragraph_FontId);
441 pos.y += margin;
433 run.bounds.pos = pos; 442 run.bounds.pos = pos;
434 run.bounds.size.x = d->size.x; 443 run.bounds.size.x = d->size.x;
435 const float aspect = (float) img->size.y / (float) img->size.x; 444 const float aspect = (float) img->size.y / (float) img->size.x;
436 run.bounds.size.y = d->size.x * aspect; 445 run.bounds.size.y = d->size.x * aspect;
437 run.visBounds = run.bounds; /* TODO: limit max height? */ 446 run.visBounds = run.bounds;
447 const iInt2 maxSize = mulf_I2(img->size, get_Window()->pixelRatio);
448 if (width_Rect(run.visBounds) > maxSize.x) {
449 /* Don't scale the image up. */
450 run.visBounds.size.y = run.visBounds.size.y * maxSize.x / width_Rect(run.visBounds);
451 run.visBounds.size.x = img->size.x;
452 run.visBounds.pos.x = run.bounds.size.x / 2 - width_Rect(run.visBounds) / 2;
453 run.bounds.size.y = run.visBounds.size.y;
454 }
438 run.text = iNullRange; 455 run.text = iNullRange;
439 run.font = 0; 456 run.font = 0;
440 run.color = 0; 457 run.color = 0;
441 run.imageId = imgIndex + 1; 458 run.imageId = imgIndex + 1;
442 pushBack_Array(&d->layout, &run); 459 pushBack_Array(&d->layout, &run);
443 pos.y += run.bounds.size.y; 460 pos.y += run.bounds.size.y + margin;
444 } 461 }
445 } 462 }
446 prevType = type; 463 prevType = type;
@@ -559,17 +576,26 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) {
559} 576}
560 577
561void setImage_GmDocument(iGmDocument *d, iGmLinkId linkId, const iString *mime, const iBlock *data) { 578void setImage_GmDocument(iGmDocument *d, iGmLinkId linkId, const iString *mime, const iBlock *data) {
562 /* TODO: check if we know this MIME type */ 579 if (!mime || !data) {
563 /* Load the image. */ { 580 iGmImage *img;
564 iGmImage *img = new_GmImage(data); 581 if (take_PtrArray(&d->images, findLinkImage_GmDocument_(d, linkId), (void **) &img)) {
565 img->linkId = linkId; /* TODO: use a hash? */
566 if (img->texture) {
567 pushBack_PtrArray(&d->images, img);
568 }
569 else {
570 delete_GmImage(img); 582 delete_GmImage(img);
571 } 583 }
572 } 584 }
585 else {
586 /* TODO: check if we know this MIME type */
587 /* Load the image. */ {
588 iGmImage *img = new_GmImage(data);
589 img->linkId = linkId; /* TODO: use a hash? */
590 set_String(&img->mime, mime);
591 if (img->texture) {
592 pushBack_PtrArray(&d->images, img);
593 }
594 else {
595 delete_GmImage(img);
596 }
597 }
598 }
573 doLayout_GmDocument_(d); 599 doLayout_GmDocument_(d);
574} 600}
575 601
@@ -649,25 +675,34 @@ const iGmRun *findRunAtLoc_GmDocument(const iGmDocument *d, const char *textCStr
649 return NULL; 675 return NULL;
650} 676}
651 677
652const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 678static const iGmLink *link_GmDocument_(const iGmDocument *d, iGmLinkId id) {
653 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { 679 if (id > 0 && id <= size_PtrArray(&d->links)) {
654 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); 680 return constAt_PtrArray(&d->links, id - 1);
655 return &link->url;
656 } 681 }
657 return NULL; 682 return NULL;
658} 683}
659 684
685const iString *linkUrl_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
686 const iGmLink *link = link_GmDocument_(d, linkId);
687 return link ? &link->url : NULL;
688}
689
660int linkFlags_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 690int linkFlags_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
661 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { 691 const iGmLink *link = link_GmDocument_(d, linkId);
662 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); 692 return link ? link->flags : 0;
663 return link->flags; 693}
694
695uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
696 size_t index = findLinkImage_GmDocument_(d, linkId);
697 if (index != iInvalidPos) {
698 return index + 1;
664 } 699 }
665 return 0; 700 return 0;
666} 701}
667 702
668enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 703enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) {
669 if (linkId > 0 && linkId <= size_PtrArray(&d->links)) { 704 const iGmLink *link = link_GmDocument_(d, linkId);
670 const iGmLink *link = constAt_PtrArray(&d->links, linkId - 1); 705 if (link) {
671 return link->flags & http_GmLinkFlag 706 return link->flags & http_GmLinkFlag
672 ? orange_ColorId 707 ? orange_ColorId
673 : link->flags & gopher_GmLinkFlag ? blue_ColorId : cyan_ColorId; 708 : link->flags & gopher_GmLinkFlag ? blue_ColorId : cyan_ColorId;
@@ -688,6 +723,18 @@ SDL_Texture *imageTexture_GmDocument(const iGmDocument *d, uint16_t imageId) {
688 return NULL; 723 return NULL;
689} 724}
690 725
726void imageInfo_GmDocument(const iGmDocument *d, uint16_t imageId, iGmImageInfo *info_out) {
727 if (imageId > 0 && imageId <= size_PtrArray(&d->images)) {
728 const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1);
729 info_out->size = img->size;
730 info_out->numBytes = img->numBytes;
731 info_out->mime = cstr_String(&img->mime);
732 }
733 else {
734 iZap(*info_out);
735 }
736}
737
691const iString *title_GmDocument(const iGmDocument *d) { 738const iString *title_GmDocument(const iGmDocument *d) {
692 return &d->title; 739 return &d->title;
693} 740}
diff --git a/src/gmdocument.h b/src/gmdocument.h
index c619c47b..584efb5a 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -19,6 +19,15 @@ enum iGmLinkFlags {
19 file_GmLinkFlag = 0x10, 19 file_GmLinkFlag = 0x10,
20 imageFileExtension_GmLinkFlag = 0x20, 20 imageFileExtension_GmLinkFlag = 0x20,
21 audioFileExtension_GmLinkFlag = 0x40, 21 audioFileExtension_GmLinkFlag = 0x40,
22 content_GmLinkFlag = 0x80, /* content visible below */
23};
24
25iDeclareType(GmImageInfo)
26
27struct Impl_GmImageInfo {
28 iInt2 size;
29 size_t numBytes;
30 const char *mime;
22}; 31};
23 32
24struct Impl_GmRun { 33struct Impl_GmRun {
@@ -61,9 +70,11 @@ const iGmRun * findRun_GmDocument (const iGmDocument *, iInt2 pos);
61const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos); 70const char * findLoc_GmDocument (const iGmDocument *, iInt2 pos);
62const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); 71const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc);
63const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); 72const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId);
73uint16_t linkImage_GmDocument (const iGmDocument *, iGmLinkId linkId);
64int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); 74int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId);
65enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); 75enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId);
66iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); 76iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId);
67const iString * title_GmDocument (const iGmDocument *); 77const iString * title_GmDocument (const iGmDocument *);
68 78
69SDL_Texture * imageTexture_GmDocument (const iGmDocument *, uint16_t imageId); 79SDL_Texture * imageTexture_GmDocument (const iGmDocument *, uint16_t imageId);
80void imageInfo_GmDocument (const iGmDocument *, uint16_t imageId, iGmImageInfo *info_out);
diff --git a/src/ui/color.c b/src/ui/color.c
index 330dab1d..46d8a71f 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -30,6 +30,31 @@ iColor get_Color(int color) {
30 return *clr; 30 return *clr;
31} 31}
32 32
33const char *escape_Color(int color) {
34 static const char *esc[] = {
35 black_ColorEscape,
36 gray15_ColorEscape,
37 gray25_ColorEscape,
38 gray50_ColorEscape,
39 gray75_ColorEscape,
40 gray88_ColorEscape,
41 white_ColorEscape,
42 brown_ColorEscape,
43 orange_ColorEscape,
44 teal_ColorEscape,
45 cyan_ColorEscape,
46 yellow_ColorEscape,
47 red_ColorEscape,
48 magenta_ColorEscape,
49 blue_ColorEscape,
50 green_ColorEscape,
51 };
52 if (color >= 0 && color < max_ColorId) {
53 return esc[color];
54 }
55 return white_ColorEscape;
56}
57
33iColor ansi_Color(iRangecc escapeSequence, int fallback) { 58iColor ansi_Color(iRangecc escapeSequence, int fallback) {
34 iColor clr = get_Color(fallback); 59 iColor clr = get_Color(fallback);
35 for (const char *ch = escapeSequence.start; ch < escapeSequence.end; ch++) { 60 for (const char *ch = escapeSequence.start; ch < escapeSequence.end; ch++) {
diff --git a/src/ui/color.h b/src/ui/color.h
index d8b1f4d1..01e49c9c 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -49,5 +49,6 @@ struct Impl_Color {
49 uint8_t r, g, b, a; 49 uint8_t r, g, b, a;
50}; 50};
51 51
52iColor get_Color (int color); 52iColor get_Color (int color);
53iColor ansi_Color (iRangecc escapeSequence, int fallback); 53iColor ansi_Color (iRangecc escapeSequence, int fallback);
54const char * escape_Color (int color);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index cffd284f..f63e98bf 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -497,13 +497,15 @@ static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d,
497 return NULL; 497 return NULL;
498} 498}
499 499
500static void requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { 500static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) {
501 if (!findMediaRequest_DocumentWidget_(d, linkId)) { 501 if (!findMediaRequest_DocumentWidget_(d, linkId)) {
502 pushBack_ObjectList( 502 pushBack_ObjectList(
503 d->media, 503 d->media,
504 iClob(new_MediaRequest( 504 iClob(new_MediaRequest(
505 d, linkId, absoluteUrl_DocumentWidget_(d, linkUrl_GmDocument(d->doc, linkId))))); 505 d, linkId, absoluteUrl_DocumentWidget_(d, linkUrl_GmDocument(d->doc, linkId)))));
506 return iTrue;
506 } 507 }
508 return iFalse;
507} 509}
508 510
509static iBool handleMediaEvent_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 511static iBool handleMediaEvent_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -777,7 +779,26 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
777 iAssert(linkId); 779 iAssert(linkId);
778 /* Media links are opened inline by default. */ 780 /* Media links are opened inline by default. */
779 if (isMediaLink_GmDocument(d->doc, linkId)) { 781 if (isMediaLink_GmDocument(d->doc, linkId)) {
780 requestMedia_DocumentWidget_(d, linkId); 782 if (!requestMedia_DocumentWidget_(d, linkId)) {
783 if (linkFlags_GmDocument(d->doc, linkId) & content_GmLinkFlag) {
784 setImage_GmDocument(d->doc, linkId, NULL, NULL);
785 d->hoverLink = NULL;
786 updateVisible_DocumentWidget_(d);
787 refresh_Widget(w);
788 return iTrue;
789 }
790 else {
791 /* Show the existing content again if we have it. */
792 iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId);
793 if (req) {
794 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req),
795 body_GmRequest(req->req));
796 updateVisible_DocumentWidget_(d);
797 refresh_Widget(w);
798 return iTrue;
799 }
800 }
801 }
781 } 802 }
782 else { 803 else {
783 postCommandf_App("open url:%s", 804 postCommandf_App("open url:%s",
@@ -840,8 +861,8 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
840} 861}
841 862
842static void drawRun_DrawContext_(void *context, const iGmRun *run) { 863static void drawRun_DrawContext_(void *context, const iGmRun *run) {
843 iDrawContext *d = context; 864 iDrawContext *d = context;
844 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); 865 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY);
845 if (run->imageId) { 866 if (run->imageId) {
846 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); 867 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId);
847 if (tex) { 868 if (tex) {
@@ -854,32 +875,50 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
854 iString text; 875 iString text;
855 /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ 876 /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */
856 initRange_String(&text, run->text); 877 initRange_String(&text, run->text);
857 enum iColorId fg = run->color; 878 enum iColorId fg = run->color;
858 if (run == d->widget->hoverLink) { 879 const iGmDocument *doc = d->widget->doc;
859 const iGmDocument *doc = d->widget->doc; 880 if (run->linkId) {
860 const iGmLinkId linkId = d->widget->hoverLink->linkId; 881 /* TODO: Visualize an ongoing media request. */
861 const iString * url = linkUrl_GmDocument(doc, linkId); 882 const int flags = linkFlags_GmDocument(doc, run->linkId);
862 const int flags = linkFlags_GmDocument(doc, linkId); 883 if (flags & content_GmLinkFlag) {
863 iUrl parts; 884 fg = linkColor_GmDocument(doc, run->linkId);
864 init_Url(&parts, url); 885 if (!isEmpty_Rect(run->bounds)) {
865 const iString *host = collect_String(newRange_String(parts.host)); 886 iGmImageInfo info;
866 fg = linkColor_GmDocument(doc, linkId); 887 imageInfo_GmDocument(doc, linkImage_GmDocument(doc, run->linkId), &info);
867 const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); 888 drawAlign_Text(default_FontId,
868 const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; 889 add_I2(topRight_Rect(run->bounds), origin),
869 const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; 890 fg,
870 if (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || showHost) { 891 right_Alignment,
892 "%s \u2014 %d x %d \u2014 %.1fMB %s \u2715",
893 info.mime, info.size.x, info.size.y, info.numBytes / 1.0e6f,
894 run == d->widget->hoverLink ? white_ColorEscape : "");
895 }
896 }
897 else if (run == d->widget->hoverLink) {
898 const iGmLinkId linkId = d->widget->hoverLink->linkId;
899 const iString * url = linkUrl_GmDocument(doc, linkId);
900 const int flags = linkFlags_GmDocument(doc, linkId);
901 iUrl parts;
902 init_Url(&parts, url);
903 const iString *host = collect_String(newRange_String(parts.host));
904 fg = linkColor_GmDocument(doc, linkId);
905 const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag);
906 const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0;
907 const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0;
871 iRect linkRect = moved_Rect(run->visBounds, origin); 908 iRect linkRect = moved_Rect(run->visBounds, origin);
872 drawAlign_Text(default_FontId, 909 if (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || showHost) {
873 topRight_Rect(linkRect), 910 drawAlign_Text(default_FontId,
874 fg - 1, 911 topRight_Rect(linkRect),
875 left_Alignment, 912 fg - 1,
876 " \u2014%s%s%s\r%c%s", 913 left_Alignment,
877 showHost ? " " : "", 914 " \u2014%s%s%s\r%c%s",
878 showHost ? cstr_String(host) : "", 915 showHost ? " " : "",
879 showHost && (showImage || showAudio) ? " \u2014" : "", 916 showHost ? cstr_String(host) : "",
880 showImage || showAudio ? '0' + fg : ('0' + fg - 1), 917 showHost && (showImage || showAudio) ? " \u2014" : "",
881 showImage ? " View Image \U0001f5bc" 918 showImage || showAudio ? '0' + fg : ('0' + fg - 1),
882 : showAudio ? " Play Audio \U0001f3b5" : ""); 919 showImage ? " View Image \U0001f5bc"
920 : showAudio ? " Play Audio \U0001f3b5" : "");
921 }
883 } 922 }
884 } 923 }
885 const iInt2 visPos = add_I2(run->visBounds.pos, origin); 924 const iInt2 visPos = add_I2(run->visBounds.pos, origin);
diff --git a/src/ui/window.c b/src/ui/window.c
index a7291320..feb917ff 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -204,9 +204,9 @@ static void setupUserInterface_Window(iWindow *d) {
204 iInputWidget *input = new_InputWidget(0); 204 iInputWidget *input = new_InputWidget(0);
205 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), 205 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag),
206 "find.input"); 206 "find.input");
207 addChild_Widget(searchBar, iClob(new_LabelWidget(" \U0001f86b ", 'g', KMOD_PRIMARY, "find.next"))); 207 addChild_Widget(searchBar, iClob(new_LabelWidget(" \U0001f86b ", 'g', KMOD_PRIMARY, "find.next")));
208 addChild_Widget(searchBar, iClob(new_LabelWidget(" \U0001f869 ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); 208 addChild_Widget(searchBar, iClob(new_LabelWidget(" \U0001f869 ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev")));
209 addChild_Widget(searchBar, iClob(new_LabelWidget("\u00d7", SDLK_ESCAPE, 0, "find.close"))); 209 addChild_Widget(searchBar, iClob(new_LabelWidget("\u2715", SDLK_ESCAPE, 0, "find.close")));
210 } 210 }
211 211
212#if 0 212#if 0