diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-12-18 16:51:04 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-12-18 16:51:04 +0200 |
commit | 5f7709f0b84e74fde847cdcf02c41990ef037daa (patch) | |
tree | 97ff5e11469c0cc957839005e86eb0fae751faa3 /src | |
parent | ccde1781a07ab418050efdfb00d9231291ce52d8 (diff) |
LinkInfo: Improved link metadata popup
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/documentwidget.c | 50 | ||||
-rw-r--r-- | src/ui/linkinfo.c | 150 | ||||
-rw-r--r-- | src/ui/linkinfo.h | 13 |
3 files changed, 200 insertions, 13 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index afd0070f..1870efd6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -41,6 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
41 | #include "inputwidget.h" | 41 | #include "inputwidget.h" |
42 | #include "keys.h" | 42 | #include "keys.h" |
43 | #include "labelwidget.h" | 43 | #include "labelwidget.h" |
44 | #include "linkinfo.h" | ||
44 | #include "media.h" | 45 | #include "media.h" |
45 | #include "paint.h" | 46 | #include "paint.h" |
46 | #include "periodic.h" | 47 | #include "periodic.h" |
@@ -161,10 +162,10 @@ enum iDrawBufsFlag { | |||
161 | }; | 162 | }; |
162 | 163 | ||
163 | struct Impl_DrawBufs { | 164 | struct Impl_DrawBufs { |
164 | int flags; | 165 | int flags; |
165 | SDL_Texture * sideIconBuf; | 166 | SDL_Texture *sideIconBuf; |
166 | iTextBuf * timestampBuf; | 167 | iTextBuf *timestampBuf; |
167 | uint32_t lastRenderTime; | 168 | uint32_t lastRenderTime; |
168 | }; | 169 | }; |
169 | 170 | ||
170 | static void init_DrawBufs(iDrawBufs *d) { | 171 | static void init_DrawBufs(iDrawBufs *d) { |
@@ -234,10 +235,11 @@ enum iDocumentWidgetFlag { | |||
234 | fromCache_DocumentWidgetFlag = iBit(16), /* don't write anything to cache */ | 235 | fromCache_DocumentWidgetFlag = iBit(16), /* don't write anything to cache */ |
235 | animationPlaceholder_DocumentWidgetFlag = iBit(17), /* avoid slow operations */ | 236 | animationPlaceholder_DocumentWidgetFlag = iBit(17), /* avoid slow operations */ |
236 | invalidationPending_DocumentWidgetFlag = iBit(18), /* invalidate as soon as convenient */ | 237 | invalidationPending_DocumentWidgetFlag = iBit(18), /* invalidate as soon as convenient */ |
237 | leftWheelSwipe_DocumentWidgetFlag = iBit(19), /* swipe state flags are used on desktop */ | 238 | leftWheelSwipe_DocumentWidgetFlag = iBit(19), /* swipe state flags are used on desktop */ |
238 | rightWheelSwipe_DocumentWidgetFlag = iBit(20), | 239 | rightWheelSwipe_DocumentWidgetFlag = iBit(20), |
239 | eitherWheelSwipe_DocumentWidgetFlag = leftWheelSwipe_DocumentWidgetFlag | rightWheelSwipe_DocumentWidgetFlag, | 240 | eitherWheelSwipe_DocumentWidgetFlag = leftWheelSwipe_DocumentWidgetFlag | |
240 | // wheelSwipeFinished_DocumentWidgetFlag = iBit(21), | 241 | rightWheelSwipe_DocumentWidgetFlag, |
242 | // wheelSwipeFinished_DocumentWidgetFlag = iBit(21), | ||
241 | }; | 243 | }; |
242 | 244 | ||
243 | enum iDocumentLinkOrdinalMode { | 245 | enum iDocumentLinkOrdinalMode { |
@@ -322,6 +324,7 @@ struct Impl_DocumentWidget { | |||
322 | iVisBufMeta * visBufMeta; | 324 | iVisBufMeta * visBufMeta; |
323 | iGmRunRange renderRuns; | 325 | iGmRunRange renderRuns; |
324 | iPtrSet * invalidRuns; | 326 | iPtrSet * invalidRuns; |
327 | iLinkInfo * linkInfo; | ||
325 | 328 | ||
326 | /* Widget structure: */ | 329 | /* Widget structure: */ |
327 | iScrollWidget *scroll; | 330 | iScrollWidget *scroll; |
@@ -413,6 +416,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
413 | init_String(&d->pendingGotoHeading); | 416 | init_String(&d->pendingGotoHeading); |
414 | init_String(&d->linePrecedingLink); | 417 | init_String(&d->linePrecedingLink); |
415 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 418 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
419 | d->linkInfo = (deviceType_App() == desktop_AppDeviceType ? new_LinkInfo() : NULL); | ||
416 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 420 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
417 | d->menu = NULL; /* created when clicking */ | 421 | d->menu = NULL; /* created when clicking */ |
418 | d->playerMenu = NULL; | 422 | d->playerMenu = NULL; |
@@ -457,6 +461,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
457 | delete_VisBuf(d->visBuf); | 461 | delete_VisBuf(d->visBuf); |
458 | free(d->visBufMeta); | 462 | free(d->visBufMeta); |
459 | delete_PtrSet(d->invalidRuns); | 463 | delete_PtrSet(d->invalidRuns); |
464 | delete_LinkInfo(d->linkInfo); | ||
460 | iRelease(d->media); | 465 | iRelease(d->media); |
461 | iRelease(d->request); | 466 | iRelease(d->request); |
462 | delete_Gempub(d->sourceGempub); | 467 | delete_Gempub(d->sourceGempub); |
@@ -737,7 +742,8 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse); | |||
737 | static void animate_DocumentWidget_(void *ticker) { | 742 | static void animate_DocumentWidget_(void *ticker) { |
738 | iDocumentWidget *d = ticker; | 743 | iDocumentWidget *d = ticker; |
739 | refresh_Widget(d); | 744 | refresh_Widget(d); |
740 | if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->altTextOpacity)) { | 745 | if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->altTextOpacity) || |
746 | (d->linkInfo && !isFinished_Anim(&d->linkInfo->opacity))) { | ||
741 | addTicker_App(animate_DocumentWidget_, d); | 747 | addTicker_App(animate_DocumentWidget_, d); |
742 | } | 748 | } |
743 | } | 749 | } |
@@ -789,6 +795,10 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
789 | if (d->hoverLink) { | 795 | if (d->hoverLink) { |
790 | invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); | 796 | invalidateLink_DocumentWidget_(d, d->hoverLink->linkId); |
791 | } | 797 | } |
798 | if (update_LinkInfo(d->linkInfo, d->doc, d->hoverLink ? d->hoverLink->linkId : 0, | ||
799 | width_Widget(w))) { | ||
800 | animate_DocumentWidget_(d); | ||
801 | } | ||
792 | refresh_Widget(w); | 802 | refresh_Widget(w); |
793 | } | 803 | } |
794 | /* Hovering over preformatted blocks. */ | 804 | /* Hovering over preformatted blocks. */ |
@@ -4923,6 +4933,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
4923 | (float) bodySize_GmRequest(mr->req) / 1.0e6f); | 4933 | (float) bodySize_GmRequest(mr->req) / 1.0e6f); |
4924 | } | 4934 | } |
4925 | } | 4935 | } |
4936 | #if 0 | ||
4926 | else if (isHover) { | 4937 | else if (isHover) { |
4927 | /* TODO: Make this a dynamic overlay, not part of the VisBuf content. */ | 4938 | /* TODO: Make this a dynamic overlay, not part of the VisBuf content. */ |
4928 | const iGmLinkId linkId = d->widget->hoverLink->linkId; | 4939 | const iGmLinkId linkId = d->widget->hoverLink->linkId; |
@@ -5002,6 +5013,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
5002 | deinit_String(&str); | 5013 | deinit_String(&str); |
5003 | } | 5014 | } |
5004 | } | 5015 | } |
5016 | #endif | ||
5005 | } | 5017 | } |
5006 | if (0) { | 5018 | if (0) { |
5007 | drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | 5019 | drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); |
@@ -5446,6 +5458,25 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
5446 | } | 5458 | } |
5447 | unsetClip_Paint(&ctx.paint); | 5459 | unsetClip_Paint(&ctx.paint); |
5448 | drawSideElements_DocumentWidget_(d); | 5460 | drawSideElements_DocumentWidget_(d); |
5461 | if (deviceType_App() == desktop_AppDeviceType && prefs_App()->hoverLink && d->linkInfo) { | ||
5462 | const int pad = gap_UI; | ||
5463 | update_LinkInfo(d->linkInfo, | ||
5464 | d->doc, | ||
5465 | d->hoverLink ? d->hoverLink->linkId : 0, | ||
5466 | width_Rect(bounds) - 2 * pad); | ||
5467 | const iInt2 infoSize = size_LinkInfo(d->linkInfo); | ||
5468 | iInt2 infoPos = add_I2(bottomLeft_Rect(bounds), init_I2(pad, -infoSize.y - pad)); | ||
5469 | if (d->hoverLink) { | ||
5470 | const iRect runRect = runRect_DocumentWidget_(d, d->hoverLink); | ||
5471 | d->linkInfo->isAltPos = | ||
5472 | (bottom_Rect(runRect) >= infoPos.y - lineHeight_Text(paragraph_FontId)); | ||
5473 | } | ||
5474 | if (d->linkInfo->isAltPos) { | ||
5475 | infoPos.y = top_Rect(bounds) + pad; | ||
5476 | } | ||
5477 | draw_LinkInfo(d->linkInfo, infoPos); | ||
5478 | } | ||
5479 | #if 0 | ||
5449 | if (prefs_App()->hoverLink && d->hoverLink) { | 5480 | if (prefs_App()->hoverLink && d->hoverLink) { |
5450 | const int font = uiLabel_FontId; | 5481 | const int font = uiLabel_FontId; |
5451 | const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); | 5482 | const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId)); |
@@ -5455,6 +5486,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
5455 | fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); | 5486 | fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId); |
5456 | drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl); | 5487 | drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl); |
5457 | } | 5488 | } |
5489 | #endif | ||
5458 | } | 5490 | } |
5459 | if (colorTheme_App() == pureWhite_ColorTheme) { | 5491 | if (colorTheme_App() == pureWhite_ColorTheme) { |
5460 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); | 5492 | drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); |
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c index e20c183c..72abea1a 100644 --- a/src/ui/linkinfo.c +++ b/src/ui/linkinfo.c | |||
@@ -21,12 +21,23 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "linkinfo.h" | 23 | #include "linkinfo.h" |
24 | #include "metrics.h" | ||
25 | #include "paint.h" | ||
26 | #include "../gmcerts.h" | ||
27 | #include "../app.h" | ||
24 | 28 | ||
25 | #define hPad_LinkInfo_ (2 * gap_UI) | 29 | #include <SDL_render.h> |
26 | #define vPad_LinkInfo_ (1 * gap_UI) | 30 | |
31 | iDefineTypeConstruction(LinkInfo) | ||
32 | |||
33 | #define minWidth_LinkInfo_ (40 * gap_UI) | ||
34 | #define hPad_LinkInfo_ (2 * gap_UI) | ||
35 | #define vPad_LinkInfo_ (1 * gap_UI) | ||
27 | 36 | ||
28 | void init_LinkInfo(iLinkInfo *d) { | 37 | void init_LinkInfo(iLinkInfo *d) { |
29 | d->buf = NULL; | 38 | d->buf = NULL; |
39 | init_Anim(&d->opacity, 0.0f); | ||
40 | d->isAltPos = iFalse; | ||
30 | } | 41 | } |
31 | 42 | ||
32 | void deinit_LinkInfo(iLinkInfo *d) { | 43 | void deinit_LinkInfo(iLinkInfo *d) { |
@@ -37,5 +48,136 @@ iInt2 size_LinkInfo(const iLinkInfo *d) { | |||
37 | if (!d->buf) { | 48 | if (!d->buf) { |
38 | return zero_I2(); | 49 | return zero_I2(); |
39 | } | 50 | } |
40 | return add_I2(d->buf->size, init_I2(2 * hPad_LinkInfo_, 2 * vPad_LinkInfo_))); | 51 | return add_I2(d->buf->size, init_I2(2 * hPad_LinkInfo_, 2 * vPad_LinkInfo_)); |
52 | } | ||
53 | |||
54 | iBool update_LinkInfo(iLinkInfo *d, const iGmDocument *doc, iGmLinkId linkId, int maxWidth) { | ||
55 | if (!d) { | ||
56 | return iFalse; | ||
57 | } | ||
58 | if (d->linkId != linkId || d->maxWidth != maxWidth) { | ||
59 | d->linkId = linkId; | ||
60 | d->maxWidth = maxWidth; | ||
61 | invalidate_LinkInfo(d); | ||
62 | if (linkId) { | ||
63 | /* Measure and draw. */ | ||
64 | if (targetValue_Anim(&d->opacity) < 1) { | ||
65 | setValue_Anim(&d->opacity, 1, 75); | ||
66 | } | ||
67 | const int avail = iMax(minWidth_LinkInfo_, maxWidth) - 2 * hPad_LinkInfo_; | ||
68 | const iString *url = linkUrl_GmDocument(doc, linkId); | ||
69 | iUrl parts; | ||
70 | init_Url(&parts, url); | ||
71 | const int flags = linkFlags_GmDocument(doc, linkId); | ||
72 | const enum iGmLinkScheme scheme = scheme_GmLinkFlag(flags); | ||
73 | const iBool showHost = (flags & humanReadable_GmLinkFlag && | ||
74 | (!isEmpty_Range(&parts.host) || | ||
75 | scheme == mailto_GmLinkScheme)); | ||
76 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | ||
77 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | ||
78 | // int fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); | ||
79 | iString str; | ||
80 | init_String(&str); | ||
81 | /* Identity that will be used. */ | ||
82 | const iGmIdentity *ident = identityForUrl_GmCerts(certs_App(), url); | ||
83 | if (ident) { | ||
84 | appendFormat_String(&str, person_Icon " %s", | ||
85 | //escape_Color(tmBannerItemTitle_ColorId), | ||
86 | cstr_String(name_GmIdentity(ident))); | ||
87 | } /* Show scheme and host. */ | ||
88 | if ((showHost || | ||
89 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag))) && | ||
90 | scheme != mailto_GmLinkScheme) { | ||
91 | if (!isEmpty_String(&str)) { | ||
92 | appendCStr_String(&str, "\n"); | ||
93 | } | ||
94 | if (showHost && scheme != gemini_GmLinkScheme) { | ||
95 | append_String( | ||
96 | &str, collect_String(upper_String(collectNewRange_String(parts.scheme)))); | ||
97 | appendCStr_String(&str, " \u2014 "); | ||
98 | } | ||
99 | if (showHost) { | ||
100 | appendFormat_String(&str, "\x1b[1m%s\x1b[0m", cstr_Rangecc(parts.host)); | ||
101 | } | ||
102 | if (showImage || showAudio) { | ||
103 | appendFormat_String( | ||
104 | &str, | ||
105 | "%s%s%s", | ||
106 | // showHost ? (scheme == mailto_GmLinkScheme ? cstr_String(url) | ||
107 | // : scheme != gemini_GmLinkScheme | ||
108 | // ? format_CStr("%s \u2014 %s", | ||
109 | // cstrCollect_String(upper_String( | ||
110 | // collectNewRange_String(parts.scheme))), | ||
111 | // cstr_Rangecc(parts.host)) | ||
112 | // : cstr_Rangecc(parts.host)) | ||
113 | // : "", | ||
114 | // showHost ? format_CStr("\x1b[1m%s\x1b[0m", cstr_Rangecc(parts.host)) : "", | ||
115 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
116 | showImage || showAudio | ||
117 | ? "" | ||
118 | : "", // escape_Color(linkColor_GmDocument(doc, linkId, domain_GmLinkPart)), | ||
119 | showImage || showAudio | ||
120 | ? format_CStr(showImage ? photo_Icon " %s " : "\U0001f3b5 %s", | ||
121 | cstr_Lang(showImage ? "link.hint.image" : "link.hint.audio")) | ||
122 | : ""); | ||
123 | } | ||
124 | } | ||
125 | if (flags & visited_GmLinkFlag) { | ||
126 | iDate date; | ||
127 | init_Date(&date, linkTime_GmDocument(doc, linkId)); | ||
128 | if (!isEmpty_String(&str)) { | ||
129 | appendCStr_String(&str, " \u2014 "); | ||
130 | } | ||
131 | // appendCStr_String(&str, escape_Color(tmQuoteIcon_ColorId)); | ||
132 | iString *dateStr = format_Date(&date, "%b %d"); | ||
133 | append_String(&str, dateStr); | ||
134 | delete_String(dateStr); | ||
135 | } | ||
136 | if (!isEmpty_String(&str)) { | ||
137 | appendCStr_String(&str, "\n"); | ||
138 | } | ||
139 | appendRange_String(&str, range_String(url)); | ||
140 | /* Draw the text. */ | ||
141 | iWrapText wt = { .text = range_String(&str), .maxWidth = avail, .mode = word_WrapTextMode }; | ||
142 | d->buf = new_TextBuf(&wt, uiLabel_FontId, tmQuote_ColorId); | ||
143 | deinit_String(&str); | ||
144 | } | ||
145 | else { | ||
146 | if (targetValue_Anim(&d->opacity) > 0) { | ||
147 | setValue_Anim(&d->opacity, 0, 150); | ||
148 | } | ||
149 | } | ||
150 | return iTrue; | ||
151 | } | ||
152 | return iFalse; | ||
153 | } | ||
154 | |||
155 | void invalidate_LinkInfo(iLinkInfo *d) { | ||
156 | if (targetValue_Anim(&d->opacity) > 0) { | ||
157 | setValue_Anim(&d->opacity, 0, 150); | ||
158 | } | ||
159 | |||
160 | // if (d->buf) { | ||
161 | // delete_TextBuf(d->buf); | ||
162 | // d->buf = NULL; | ||
163 | // } | ||
164 | } | ||
165 | |||
166 | void draw_LinkInfo(const iLinkInfo *d, iInt2 topLeft) { | ||
167 | const float opacity = value_Anim(&d->opacity); | ||
168 | if (!d->buf || opacity <= 0.01f) { | ||
169 | return; | ||
170 | } | ||
171 | iPaint p; | ||
172 | init_Paint(&p); | ||
173 | iInt2 size = size_LinkInfo(d); | ||
174 | iRect rect = { topLeft, size }; | ||
175 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
176 | p.alpha = 255 * opacity; | ||
177 | fillRect_Paint(&p, rect, tmBackgroundAltText_ColorId); | ||
178 | drawRect_Paint(&p, rect, tmFrameAltText_ColorId); | ||
179 | SDL_SetTextureAlphaMod(d->buf->texture, p.alpha); | ||
180 | draw_TextBuf(d->buf, add_I2(topLeft, init_I2(hPad_LinkInfo_, vPad_LinkInfo_)), | ||
181 | white_ColorId); | ||
182 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
41 | } | 183 | } |
diff --git a/src/ui/linkinfo.h b/src/ui/linkinfo.h index dbedf359..a1669f95 100644 --- a/src/ui/linkinfo.h +++ b/src/ui/linkinfo.h | |||
@@ -23,10 +23,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "text.h" | 25 | #include "text.h" |
26 | #include "util.h" | ||
27 | #include "../gmdocument.h" | ||
26 | 28 | ||
27 | iDeclareType(LinkInfo) | 29 | iDeclareType(LinkInfo) |
28 | iDeclareTypeConstruction(LinkInfo) | 30 | iDeclareTypeConstruction(LinkInfo) |
29 | 31 | ||
30 | struct Impl_LinkInfo { | 32 | struct Impl_LinkInfo { |
33 | iGmLinkId linkId; | ||
34 | int maxWidth; | ||
31 | iTextBuf *buf; | 35 | iTextBuf *buf; |
36 | iAnim opacity; | ||
37 | iBool isAltPos; | ||
32 | }; | 38 | }; |
39 | |||
40 | iBool update_LinkInfo (iLinkInfo *, const iGmDocument *doc, iGmLinkId linkId, | ||
41 | int maxWidth); /* returns true if changed */ | ||
42 | void invalidate_LinkInfo (iLinkInfo *); | ||
43 | |||
44 | iInt2 size_LinkInfo (const iLinkInfo *); | ||
45 | void draw_LinkInfo (const iLinkInfo *, iInt2 topLeft); | ||