summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-09-22 11:37:15 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-09-22 11:37:15 +0300
commit8700c039dc04b4c9f22584d4e901ed372442b0f4 (patch)
treec89a1c5cda9baa3204b46df0162f5b1ba62add04
parentc6182b6b4158a3227546ae55895b72a326db19fb (diff)
DocumentWidget: Drawing side elements
The banner appears on the left, if there is room in the margin. Also added a document timestamp in the bottom to see when the data was received.
-rw-r--r--src/gmdocument.c22
-rw-r--r--src/gmdocument.h2
-rw-r--r--src/gmrequest.c6
-rw-r--r--src/gmrequest.h1
-rw-r--r--src/ui/color.h5
-rw-r--r--src/ui/documentwidget.c88
6 files changed, 120 insertions, 4 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 4414b04f..0d104dde 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -110,6 +110,7 @@ struct Impl_GmDocument {
110 iInt2 size; 110 iInt2 size;
111 iArray layout; /* contents of source, laid out in document space */ 111 iArray layout; /* contents of source, laid out in document space */
112 iPtrArray links; 112 iPtrArray links;
113 iString bannerText;
113 iString title; /* the first top-level title */ 114 iString title; /* the first top-level title */
114 iArray headings; 115 iArray headings;
115 iPtrArray images; /* persistent across layouts, references links by ID */ 116 iPtrArray images; /* persistent across layouts, references links by ID */
@@ -336,6 +337,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
336 clearLinks_GmDocument_(d); 337 clearLinks_GmDocument_(d);
337 clear_Array(&d->headings); 338 clear_Array(&d->headings);
338 clear_String(&d->title); 339 clear_String(&d->title);
340 clear_String(&d->bannerText);
339 if (d->size.x <= 0 || isEmpty_String(&d->source)) { 341 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
340 return; 342 return;
341 } 343 }
@@ -419,6 +421,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
419 addSiteBanner = iFalse; 421 addSiteBanner = iFalse;
420 const iRangecc bannerText = urlHost_String(&d->url); 422 const iRangecc bannerText = urlHost_String(&d->url);
421 if (!isEmpty_Range(&bannerText)) { 423 if (!isEmpty_Range(&bannerText)) {
424 setRange_String(&d->bannerText, bannerText);
422 iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; 425 iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag };
423 banner.bounds = zero_Rect(); 426 banner.bounds = zero_Rect();
424 banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); 427 banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2);
@@ -618,6 +621,7 @@ void init_GmDocument(iGmDocument *d) {
618 d->size = zero_I2(); 621 d->size = zero_I2();
619 init_Array(&d->layout, sizeof(iGmRun)); 622 init_Array(&d->layout, sizeof(iGmRun));
620 init_PtrArray(&d->links); 623 init_PtrArray(&d->links);
624 init_String(&d->bannerText);
621 init_String(&d->title); 625 init_String(&d->title);
622 init_Array(&d->headings, sizeof(iGmHeading)); 626 init_Array(&d->headings, sizeof(iGmHeading));
623 init_PtrArray(&d->images); 627 init_PtrArray(&d->images);
@@ -626,6 +630,7 @@ void init_GmDocument(iGmDocument *d) {
626} 630}
627 631
628void deinit_GmDocument(iGmDocument *d) { 632void deinit_GmDocument(iGmDocument *d) {
633 deinit_String(&d->bannerText);
629 deinit_String(&d->title); 634 deinit_String(&d->title);
630 clearLinks_GmDocument_(d); 635 clearLinks_GmDocument_(d);
631 deinit_PtrArray(&d->links); 636 deinit_PtrArray(&d->links);
@@ -903,8 +908,12 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
903 setHsl_Color(i, color); 908 setHsl_Color(i, color);
904 } 909 }
905 } 910 }
911 /* Derived colors. */
906 set_Color(tmQuoteIcon_ColorId, 912 set_Color(tmQuoteIcon_ColorId,
907 mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f)); 913 mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f));
914 set_Color(tmBannerSideTitle_ColorId,
915 mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId),
916 theme == colorfulDark_GmDocumentTheme ? 0.55f : 0));
908 /* Special exceptions. */ 917 /* Special exceptions. */
909 if (seed) { 918 if (seed) {
910 if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) { 919 if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) {
@@ -1047,11 +1056,22 @@ iInt2 size_GmDocument(const iGmDocument *d) {
1047} 1056}
1048 1057
1049iBool hasSiteBanner_GmDocument(const iGmDocument *d) { 1058iBool hasSiteBanner_GmDocument(const iGmDocument *d) {
1059 return siteBanner_GmDocument(d) != NULL;
1060}
1061
1062const iGmRun *siteBanner_GmDocument(const iGmDocument *d) {
1050 if (isEmpty_Array(&d->layout)) { 1063 if (isEmpty_Array(&d->layout)) {
1051 return iFalse; 1064 return iFalse;
1052 } 1065 }
1053 const iGmRun *first = constFront_Array(&d->layout); 1066 const iGmRun *first = constFront_Array(&d->layout);
1054 return (first->flags & siteBanner_GmRunFlag) != 0; 1067 if (first->flags & siteBanner_GmRunFlag) {
1068 return first;
1069 }
1070 return NULL;
1071}
1072
1073const iString *bannerText_GmDocument(const iGmDocument *d) {
1074 return &d->bannerText;
1055} 1075}
1056 1076
1057const iArray *headings_GmDocument(const iGmDocument *d) { 1077const iArray *headings_GmDocument(const iGmDocument *d) {
diff --git a/src/gmdocument.h b/src/gmdocument.h
index b16df677..ec47258a 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -115,7 +115,9 @@ typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
115void render_GmDocument (const iGmDocument *, iRangei visRangeY, 115void render_GmDocument (const iGmDocument *, iRangei visRangeY,
116 iGmDocumentRenderFunc render, void *); 116 iGmDocumentRenderFunc render, void *);
117iInt2 size_GmDocument (const iGmDocument *); 117iInt2 size_GmDocument (const iGmDocument *);
118const iGmRun * siteBanner_GmDocument (const iGmDocument *);
118iBool hasSiteBanner_GmDocument (const iGmDocument *); 119iBool hasSiteBanner_GmDocument (const iGmDocument *);
120const iString * bannerText_GmDocument (const iGmDocument *);
119const iArray * headings_GmDocument (const iGmDocument *); 121const iArray * headings_GmDocument (const iGmDocument *);
120const iString * source_GmDocument (const iGmDocument *); 122const iString * source_GmDocument (const iGmDocument *);
121 123
diff --git a/src/gmrequest.c b/src/gmrequest.c
index 43cc874a..b667c82a 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -44,6 +44,7 @@ void init_GmResponse(iGmResponse *d) {
44 d->certFlags = 0; 44 d->certFlags = 0;
45 iZap(d->certValidUntil); 45 iZap(d->certValidUntil);
46 init_String(&d->certSubject); 46 init_String(&d->certSubject);
47 iZap(d->when);
47} 48}
48 49
49void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { 50void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) {
@@ -53,6 +54,7 @@ void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) {
53 d->certFlags = other->certFlags; 54 d->certFlags = other->certFlags;
54 d->certValidUntil = other->certValidUntil; 55 d->certValidUntil = other->certValidUntil;
55 initCopy_String(&d->certSubject, &other->certSubject); 56 initCopy_String(&d->certSubject, &other->certSubject);
57 d->when = other->when;
56} 58}
57 59
58void deinit_GmResponse(iGmResponse *d) { 60void deinit_GmResponse(iGmResponse *d) {
@@ -68,6 +70,7 @@ void clear_GmResponse(iGmResponse *d) {
68 d->certFlags = 0; 70 d->certFlags = 0;
69 iZap(d->certValidUntil); 71 iZap(d->certValidUntil);
70 clear_String(&d->certSubject); 72 clear_String(&d->certSubject);
73 iZap(d->when);
71} 74}
72 75
73iGmResponse *copy_GmResponse(const iGmResponse *d) { 76iGmResponse *copy_GmResponse(const iGmResponse *d) {
@@ -83,6 +86,7 @@ void serialize_GmResponse(const iGmResponse *d, iStream *outs) {
83 write32_Stream(outs, d->certFlags); 86 write32_Stream(outs, d->certFlags);
84 serialize_Date(&d->certValidUntil, outs); 87 serialize_Date(&d->certValidUntil, outs);
85 serialize_String(&d->certSubject, outs); 88 serialize_String(&d->certSubject, outs);
89 /* TODO: Include the timestamp. */
86} 90}
87 91
88void deserialize_GmResponse(iGmResponse *d, iStream *ins) { 92void deserialize_GmResponse(iGmResponse *d, iStream *ins) {
@@ -270,6 +274,7 @@ static void readIncoming_GmRequest_(iAnyObject *obj) {
270 restartTimeout_GmRequest_(d); 274 restartTimeout_GmRequest_(d);
271 notifyUpdate = iTrue; 275 notifyUpdate = iTrue;
272 } 276 }
277 initCurrent_Time(&d->resp.when);
273 delete_Block(data); 278 delete_Block(data);
274 unlock_Mutex(&d->mutex); 279 unlock_Mutex(&d->mutex);
275 if (notifyUpdate) { 280 if (notifyUpdate) {
@@ -287,6 +292,7 @@ static void requestFinished_GmRequest_(iAnyObject *obj) {
287 iBlock *data = readAll_TlsRequest(d->req); 292 iBlock *data = readAll_TlsRequest(d->req);
288 iAssert(isEmpty_Block(data)); 293 iAssert(isEmpty_Block(data));
289 delete_Block(data); 294 delete_Block(data);
295 initCurrent_Time(&d->resp.when);
290 } 296 }
291 SDL_RemoveTimer(d->timeoutId); 297 SDL_RemoveTimer(d->timeoutId);
292 d->timeoutId = 0; 298 d->timeoutId = 0;
diff --git a/src/gmrequest.h b/src/gmrequest.h
index 2f1a4261..4d413430 100644
--- a/src/gmrequest.h
+++ b/src/gmrequest.h
@@ -44,6 +44,7 @@ struct Impl_GmResponse {
44 int certFlags; 44 int certFlags;
45 iDate certValidUntil; 45 iDate certValidUntil;
46 iString certSubject; 46 iString certSubject;
47 iTime when;
47}; 48};
48 49
49iDeclareTypeConstruction(GmResponse) 50iDeclareTypeConstruction(GmResponse)
diff --git a/src/ui/color.h b/src/ui/color.h
index 51d3370f..0f534272 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -117,6 +117,7 @@ enum iColorId {
117 tmBannerBackground_ColorId, 117 tmBannerBackground_ColorId,
118 tmBannerTitle_ColorId, 118 tmBannerTitle_ColorId,
119 tmBannerIcon_ColorId, 119 tmBannerIcon_ColorId,
120 tmBannerSideTitle_ColorId,
120 tmInlineContentMetadata_ColorId, 121 tmInlineContentMetadata_ColorId,
121 tmBadLink_ColorId, 122 tmBadLink_ColorId,
122 123
@@ -193,6 +194,10 @@ struct Impl_Color {
193 uint8_t r, g, b, a; 194 uint8_t r, g, b, a;
194}; 195};
195 196
197iLocalDef iBool equal_Color(const iColor a, const iColor b) {
198 return memcmp(&a, &b, sizeof(a)) == 0;
199}
200
196struct Impl_HSLColor { 201struct Impl_HSLColor {
197 float hue, sat, lum, a; 202 float hue, sat, lum, a;
198}; 203};
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 2fc5c548..351d1e62 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -146,6 +146,7 @@ struct Impl_DocumentWidget {
146 iObjectList * media; 146 iObjectList * media;
147 iString sourceMime; 147 iString sourceMime;
148 iBlock sourceContent; /* original content as received, for saving */ 148 iBlock sourceContent; /* original content as received, for saving */
149 iTime sourceTime;
149 iGmDocument * doc; 150 iGmDocument * doc;
150 int certFlags; 151 int certFlags;
151 iDate certExpiry; 152 iDate certExpiry;
@@ -168,6 +169,7 @@ struct Impl_DocumentWidget {
168 int smoothSpeed; 169 int smoothSpeed;
169 int smoothLastOffset; 170 int smoothLastOffset;
170 iBool smoothContinue; 171 iBool smoothContinue;
172 iAnim sideOpacity;
171 iWidget * menu; 173 iWidget * menu;
172 iVisBuf * visBuf; 174 iVisBuf * visBuf;
173 iPtrSet * invalidRuns; 175 iPtrSet * invalidRuns;
@@ -207,6 +209,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
207 d->showLinkNumbers = iFalse; 209 d->showLinkNumbers = iFalse;
208 d->visBuf = new_VisBuf(); 210 d->visBuf = new_VisBuf();
209 d->invalidRuns = new_PtrSet(); 211 d->invalidRuns = new_PtrSet();
212 init_Anim(&d->sideOpacity, 0);
210 init_String(&d->sourceMime); 213 init_String(&d->sourceMime);
211 init_Block(&d->sourceContent, 0); 214 init_Block(&d->sourceContent, 0);
212 init_PtrArray(&d->visibleLinks); 215 init_PtrArray(&d->visibleLinks);
@@ -374,6 +377,23 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
374 } 377 }
375} 378}
376 379
380static void animate_DocumentWidget_(void *ticker) {
381 iDocumentWidget *d = ticker;
382 if (!isFinished_Anim(&d->sideOpacity)) {
383 addTicker_App(animate_DocumentWidget_, d);
384 }
385}
386
387static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d) {
388 float opacity = 0.0f;
389 const iGmRun *banner = siteBanner_GmDocument(d->doc);
390 if (banner && bottom_Rect(banner->visBounds) < d->scrollY) {
391 opacity = 1.0f;
392 }
393 setValue_Anim(&d->sideOpacity, opacity, opacity < 0.5f ? 166 : 333);
394 animate_DocumentWidget_(d);
395}
396
377static void updateVisible_DocumentWidget_(iDocumentWidget *d) { 397static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
378 const iRangei visRange = visibleRange_DocumentWidget_(d); 398 const iRangei visRange = visibleRange_DocumentWidget_(d);
379 const iRect bounds = bounds_Widget(as_Widget(d)); 399 const iRect bounds = bounds_Widget(as_Widget(d));
@@ -385,6 +405,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
385 clear_PtrArray(&d->visibleLinks); 405 clear_PtrArray(&d->visibleLinks);
386 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); 406 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d);
387 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); 407 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
408 updateSideOpacity_DocumentWidget_(d);
388 /* Remember scroll positions of recently visited pages. */ { 409 /* Remember scroll positions of recently visited pages. */ {
389 iRecentUrl *recent = mostRecentUrl_History(d->mod.history); 410 iRecentUrl *recent = mostRecentUrl_History(d->mod.history);
390 if (recent && docSize && d->state == ready_RequestState) { 411 if (recent && docSize && d->state == ready_RequestState) {
@@ -537,6 +558,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
537 setSource_DocumentWidget_(d, src); 558 setSource_DocumentWidget_(d, src);
538 resetSmoothScroll_DocumentWidget_(d); 559 resetSmoothScroll_DocumentWidget_(d);
539 d->scrollY = 0; 560 d->scrollY = 0;
561 init_Anim(&d->sideOpacity, 0);
540 d->state = ready_RequestState; 562 d->state = ready_RequestState;
541} 563}
542 564
@@ -578,7 +600,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
578 updateTheme_DocumentWidget_(d); 600 updateTheme_DocumentWidget_(d);
579 } 601 }
580 clear_String(&d->sourceMime); 602 clear_String(&d->sourceMime);
581// set_Block(&d->sourceContent, &response->body); 603 d->sourceTime = response->when;
582 initBlock_String(&str, &response->body); 604 initBlock_String(&str, &response->body);
583 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { 605 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) {
584 /* Check the MIME type. */ 606 /* Check the MIME type. */
@@ -1706,6 +1728,11 @@ struct Impl_DrawContext {
1706 iBool showLinkNumbers; 1728 iBool showLinkNumbers;
1707}; 1729};
1708 1730
1731static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) {
1732 return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc))
1733 : range_String(d->titleUser);
1734}
1735
1709static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, 1736static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color,
1710 iRangecc mark, iBool *isInside) { 1737 iRangecc mark, iBool *isInside) {
1711 if (mark.start > mark.end) { 1738 if (mark.start > mark.end) {
@@ -1802,8 +1829,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1802 drawRange_Text(run->font, 1829 drawRange_Text(run->font,
1803 bpos, 1830 bpos,
1804 tmBannerTitle_ColorId, 1831 tmBannerTitle_ColorId,
1805 isEmpty_String(d->widget->titleUser) ? run->text 1832 bannerText_DocumentWidget_(d->widget));
1806 : range_String(d->widget->titleUser)); 1833// isEmpty_String(d->widget->titleUser) ? run->text
1834// : range_String(d->widget->titleUser));
1807 deinit_String(&bannerText); 1835 deinit_String(&bannerText);
1808 } 1836 }
1809 else { 1837 else {
@@ -1936,6 +1964,59 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1936// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 1964// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
1937} 1965}
1938 1966
1967static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
1968 const iWidget *w = constAs_Widget(d);
1969 const iRect bounds = bounds_Widget(w);
1970 const iRect docBounds = documentBounds_DocumentWidget_(d);
1971 const int margin = gap_UI * d->pageMargin;
1972 const iGmRun * banner = siteBanner_GmDocument(d->doc);
1973 float opacity = value_Anim(&d->sideOpacity);
1974 const int minBannerSize = lineHeight_Text(banner_FontId) * 2;
1975 const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin;
1976 if (avail > minBannerSize) {
1977 if (banner && opacity > 0) {
1978 setOpacity_Text(opacity);
1979 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
1980 const iChar icon = siteIcon_GmDocument(d->doc);
1981 iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) };
1982 iPaint p;
1983 init_Paint(&p);
1984 p.alpha = opacity * 255;
1985 int offset = iMax(0, bottom_Rect(banner->visBounds) - d->scrollY);
1986 rect.pos.y += offset;
1987 if (equal_Color(get_Color(tmBannerBackground_ColorId), get_Color(tmBackground_ColorId))) {
1988 drawRectThickness_Paint(&p, rect, gap_UI / 2, tmBannerIcon_ColorId);
1989 }
1990 else {
1991 fillRect_Paint(&p, rect, tmBannerBackground_ColorId);
1992 }
1993 iString str;
1994 initUnicodeN_String(&str, &icon, 1);
1995 drawCentered_Text(banner_FontId, rect, iTrue, tmBannerIcon_ColorId, "%s", cstr_String(&str));
1996 if (avail >= minBannerSize * 2) {
1997 const char *endp;
1998 iRangecc text = bannerText_DocumentWidget_(d);
1999 iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text);
2000 const int font = banner_FontId;
2001 while (!isEmpty_Range(&text)) {
2002 tryAdvance_Text(font, text, avail - 2 * margin, &endp);
2003 drawRange_Text(font, pos, tmBannerSideTitle_ColorId, (iRangecc){ text.start, endp });
2004 text.start = endp;
2005 pos.y += lineHeight_Text(font);
2006 }
2007 }
2008 setOpacity_Text(1.0f);
2009 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
2010 }
2011 /* Update date. */
2012 drawString_Text(default_FontId,
2013 add_I2(bottomLeft_Rect(bounds),
2014 init_I2(margin, -margin + -2 * lineHeight_Text(default_FontId))),
2015 tmQuoteIcon_ColorId,
2016 collect_String(format_Time(&d->sourceTime, "Received\n%H:%M %b %d, %Y")));
2017 }
2018}
2019
1939static void draw_DocumentWidget_(const iDocumentWidget *d) { 2020static void draw_DocumentWidget_(const iDocumentWidget *d) {
1940 const iWidget *w = constAs_Widget(d); 2021 const iWidget *w = constAs_Widget(d);
1941 const iRect bounds = bounds_Widget(w); 2022 const iRect bounds = bounds_Widget(w);
@@ -2025,6 +2106,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
2025 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), 2106 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom),
2026 tmBackground_ColorId); 2107 tmBackground_ColorId);
2027 } 2108 }
2109 drawSideElements_DocumentWidget_(d);
2028 draw_Widget(w); 2110 draw_Widget(w);
2029} 2111}
2030 2112