summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ui/documentwidget.c50
-rw-r--r--src/ui/linkinfo.c150
-rw-r--r--src/ui/linkinfo.h13
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
163struct Impl_DrawBufs { 164struct 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
170static void init_DrawBufs(iDrawBufs *d) { 171static 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
243enum iDocumentLinkOrdinalMode { 245enum 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);
737static void animate_DocumentWidget_(void *ticker) { 742static 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
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, 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
31iDefineTypeConstruction(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
28void init_LinkInfo(iLinkInfo *d) { 37void 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
32void deinit_LinkInfo(iLinkInfo *d) { 43void 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
54iBool 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
155void 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
166void 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
27iDeclareType(LinkInfo) 29iDeclareType(LinkInfo)
28iDeclareTypeConstruction(LinkInfo) 30iDeclareTypeConstruction(LinkInfo)
29 31
30struct Impl_LinkInfo { 32struct Impl_LinkInfo {
33 iGmLinkId linkId;
34 int maxWidth;
31 iTextBuf *buf; 35 iTextBuf *buf;
36 iAnim opacity;
37 iBool isAltPos;
32}; 38};
39
40iBool update_LinkInfo (iLinkInfo *, const iGmDocument *doc, iGmLinkId linkId,
41 int maxWidth); /* returns true if changed */
42void invalidate_LinkInfo (iLinkInfo *);
43
44iInt2 size_LinkInfo (const iLinkInfo *);
45void draw_LinkInfo (const iLinkInfo *, iInt2 topLeft);