From 8700c039dc04b4c9f22584d4e901ed372442b0f4 Mon Sep 17 00:00:00 2001 From: Jaakko Keränen Date: Tue, 22 Sep 2020 11:37:15 +0300 Subject: 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. --- src/gmdocument.c | 22 ++++++++++++- src/gmdocument.h | 2 ++ src/gmrequest.c | 6 ++++ src/gmrequest.h | 1 + src/ui/color.h | 5 +++ src/ui/documentwidget.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 120 insertions(+), 4 deletions(-) (limited to 'src') 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 { iInt2 size; iArray layout; /* contents of source, laid out in document space */ iPtrArray links; + iString bannerText; iString title; /* the first top-level title */ iArray headings; iPtrArray images; /* persistent across layouts, references links by ID */ @@ -336,6 +337,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { clearLinks_GmDocument_(d); clear_Array(&d->headings); clear_String(&d->title); + clear_String(&d->bannerText); if (d->size.x <= 0 || isEmpty_String(&d->source)) { return; } @@ -419,6 +421,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { addSiteBanner = iFalse; const iRangecc bannerText = urlHost_String(&d->url); if (!isEmpty_Range(&bannerText)) { + setRange_String(&d->bannerText, bannerText); iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; banner.bounds = zero_Rect(); banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); @@ -618,6 +621,7 @@ void init_GmDocument(iGmDocument *d) { d->size = zero_I2(); init_Array(&d->layout, sizeof(iGmRun)); init_PtrArray(&d->links); + init_String(&d->bannerText); init_String(&d->title); init_Array(&d->headings, sizeof(iGmHeading)); init_PtrArray(&d->images); @@ -626,6 +630,7 @@ void init_GmDocument(iGmDocument *d) { } void deinit_GmDocument(iGmDocument *d) { + deinit_String(&d->bannerText); deinit_String(&d->title); clearLinks_GmDocument_(d); deinit_PtrArray(&d->links); @@ -903,8 +908,12 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { setHsl_Color(i, color); } } + /* Derived colors. */ set_Color(tmQuoteIcon_ColorId, mix_Color(get_Color(tmQuote_ColorId), get_Color(tmBackground_ColorId), 0.55f)); + set_Color(tmBannerSideTitle_ColorId, + mix_Color(get_Color(tmBannerTitle_ColorId), get_Color(tmBackground_ColorId), + theme == colorfulDark_GmDocumentTheme ? 0.55f : 0)); /* Special exceptions. */ if (seed) { if (equal_CStr(cstr_Block(seed), "gemini.circumlunar.space")) { @@ -1047,11 +1056,22 @@ iInt2 size_GmDocument(const iGmDocument *d) { } iBool hasSiteBanner_GmDocument(const iGmDocument *d) { + return siteBanner_GmDocument(d) != NULL; +} + +const iGmRun *siteBanner_GmDocument(const iGmDocument *d) { if (isEmpty_Array(&d->layout)) { return iFalse; } const iGmRun *first = constFront_Array(&d->layout); - return (first->flags & siteBanner_GmRunFlag) != 0; + if (first->flags & siteBanner_GmRunFlag) { + return first; + } + return NULL; +} + +const iString *bannerText_GmDocument(const iGmDocument *d) { + return &d->bannerText; } const 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 *); void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); iInt2 size_GmDocument (const iGmDocument *); +const iGmRun * siteBanner_GmDocument (const iGmDocument *); iBool hasSiteBanner_GmDocument (const iGmDocument *); +const iString * bannerText_GmDocument (const iGmDocument *); const iArray * headings_GmDocument (const iGmDocument *); const iString * source_GmDocument (const iGmDocument *); 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) { d->certFlags = 0; iZap(d->certValidUntil); init_String(&d->certSubject); + iZap(d->when); } void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { @@ -53,6 +54,7 @@ void initCopy_GmResponse(iGmResponse *d, const iGmResponse *other) { d->certFlags = other->certFlags; d->certValidUntil = other->certValidUntil; initCopy_String(&d->certSubject, &other->certSubject); + d->when = other->when; } void deinit_GmResponse(iGmResponse *d) { @@ -68,6 +70,7 @@ void clear_GmResponse(iGmResponse *d) { d->certFlags = 0; iZap(d->certValidUntil); clear_String(&d->certSubject); + iZap(d->when); } iGmResponse *copy_GmResponse(const iGmResponse *d) { @@ -83,6 +86,7 @@ void serialize_GmResponse(const iGmResponse *d, iStream *outs) { write32_Stream(outs, d->certFlags); serialize_Date(&d->certValidUntil, outs); serialize_String(&d->certSubject, outs); + /* TODO: Include the timestamp. */ } void deserialize_GmResponse(iGmResponse *d, iStream *ins) { @@ -270,6 +274,7 @@ static void readIncoming_GmRequest_(iAnyObject *obj) { restartTimeout_GmRequest_(d); notifyUpdate = iTrue; } + initCurrent_Time(&d->resp.when); delete_Block(data); unlock_Mutex(&d->mutex); if (notifyUpdate) { @@ -287,6 +292,7 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { iBlock *data = readAll_TlsRequest(d->req); iAssert(isEmpty_Block(data)); delete_Block(data); + initCurrent_Time(&d->resp.when); } SDL_RemoveTimer(d->timeoutId); 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 { int certFlags; iDate certValidUntil; iString certSubject; + iTime when; }; iDeclareTypeConstruction(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 { tmBannerBackground_ColorId, tmBannerTitle_ColorId, tmBannerIcon_ColorId, + tmBannerSideTitle_ColorId, tmInlineContentMetadata_ColorId, tmBadLink_ColorId, @@ -193,6 +194,10 @@ struct Impl_Color { uint8_t r, g, b, a; }; +iLocalDef iBool equal_Color(const iColor a, const iColor b) { + return memcmp(&a, &b, sizeof(a)) == 0; +} + struct Impl_HSLColor { float hue, sat, lum, a; }; 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 { iObjectList * media; iString sourceMime; iBlock sourceContent; /* original content as received, for saving */ + iTime sourceTime; iGmDocument * doc; int certFlags; iDate certExpiry; @@ -168,6 +169,7 @@ struct Impl_DocumentWidget { int smoothSpeed; int smoothLastOffset; iBool smoothContinue; + iAnim sideOpacity; iWidget * menu; iVisBuf * visBuf; iPtrSet * invalidRuns; @@ -207,6 +209,7 @@ void init_DocumentWidget(iDocumentWidget *d) { d->showLinkNumbers = iFalse; d->visBuf = new_VisBuf(); d->invalidRuns = new_PtrSet(); + init_Anim(&d->sideOpacity, 0); init_String(&d->sourceMime); init_Block(&d->sourceContent, 0); init_PtrArray(&d->visibleLinks); @@ -374,6 +377,23 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { } } +static void animate_DocumentWidget_(void *ticker) { + iDocumentWidget *d = ticker; + if (!isFinished_Anim(&d->sideOpacity)) { + addTicker_App(animate_DocumentWidget_, d); + } +} + +static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d) { + float opacity = 0.0f; + const iGmRun *banner = siteBanner_GmDocument(d->doc); + if (banner && bottom_Rect(banner->visBounds) < d->scrollY) { + opacity = 1.0f; + } + setValue_Anim(&d->sideOpacity, opacity, opacity < 0.5f ? 166 : 333); + animate_DocumentWidget_(d); +} + static void updateVisible_DocumentWidget_(iDocumentWidget *d) { const iRangei visRange = visibleRange_DocumentWidget_(d); const iRect bounds = bounds_Widget(as_Widget(d)); @@ -385,6 +405,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { clear_PtrArray(&d->visibleLinks); render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); + updateSideOpacity_DocumentWidget_(d); /* Remember scroll positions of recently visited pages. */ { iRecentUrl *recent = mostRecentUrl_History(d->mod.history); if (recent && docSize && d->state == ready_RequestState) { @@ -537,6 +558,7 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode setSource_DocumentWidget_(d, src); resetSmoothScroll_DocumentWidget_(d); d->scrollY = 0; + init_Anim(&d->sideOpacity, 0); d->state = ready_RequestState; } @@ -578,7 +600,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse updateTheme_DocumentWidget_(d); } clear_String(&d->sourceMime); -// set_Block(&d->sourceContent, &response->body); + d->sourceTime = response->when; initBlock_String(&str, &response->body); if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { /* Check the MIME type. */ @@ -1706,6 +1728,11 @@ struct Impl_DrawContext { iBool showLinkNumbers; }; +static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) { + return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc)) + : range_String(d->titleUser); +} + static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, iRangecc mark, iBool *isInside) { if (mark.start > mark.end) { @@ -1802,8 +1829,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { drawRange_Text(run->font, bpos, tmBannerTitle_ColorId, - isEmpty_String(d->widget->titleUser) ? run->text - : range_String(d->widget->titleUser)); + bannerText_DocumentWidget_(d->widget)); +// isEmpty_String(d->widget->titleUser) ? run->text +// : range_String(d->widget->titleUser)); deinit_String(&bannerText); } else { @@ -1936,6 +1964,59 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { // drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); } +static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { + const iWidget *w = constAs_Widget(d); + const iRect bounds = bounds_Widget(w); + const iRect docBounds = documentBounds_DocumentWidget_(d); + const int margin = gap_UI * d->pageMargin; + const iGmRun * banner = siteBanner_GmDocument(d->doc); + float opacity = value_Anim(&d->sideOpacity); + const int minBannerSize = lineHeight_Text(banner_FontId) * 2; + const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin; + if (avail > minBannerSize) { + if (banner && opacity > 0) { + setOpacity_Text(opacity); + SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); + const iChar icon = siteIcon_GmDocument(d->doc); + iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) }; + iPaint p; + init_Paint(&p); + p.alpha = opacity * 255; + int offset = iMax(0, bottom_Rect(banner->visBounds) - d->scrollY); + rect.pos.y += offset; + if (equal_Color(get_Color(tmBannerBackground_ColorId), get_Color(tmBackground_ColorId))) { + drawRectThickness_Paint(&p, rect, gap_UI / 2, tmBannerIcon_ColorId); + } + else { + fillRect_Paint(&p, rect, tmBannerBackground_ColorId); + } + iString str; + initUnicodeN_String(&str, &icon, 1); + drawCentered_Text(banner_FontId, rect, iTrue, tmBannerIcon_ColorId, "%s", cstr_String(&str)); + if (avail >= minBannerSize * 2) { + const char *endp; + iRangecc text = bannerText_DocumentWidget_(d); + iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text); + const int font = banner_FontId; + while (!isEmpty_Range(&text)) { + tryAdvance_Text(font, text, avail - 2 * margin, &endp); + drawRange_Text(font, pos, tmBannerSideTitle_ColorId, (iRangecc){ text.start, endp }); + text.start = endp; + pos.y += lineHeight_Text(font); + } + } + setOpacity_Text(1.0f); + SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); + } + /* Update date. */ + drawString_Text(default_FontId, + add_I2(bottomLeft_Rect(bounds), + init_I2(margin, -margin + -2 * lineHeight_Text(default_FontId))), + tmQuoteIcon_ColorId, + collect_String(format_Time(&d->sourceTime, "Received\n%H:%M %b %d, %Y"))); + } +} + static void draw_DocumentWidget_(const iDocumentWidget *d) { const iWidget *w = constAs_Widget(d); const iRect bounds = bounds_Widget(w); @@ -2025,6 +2106,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), tmBackground_ColorId); } + drawSideElements_DocumentWidget_(d); draw_Widget(w); } -- cgit v1.2.3