summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-24 19:17:24 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-24 19:17:24 +0300
commit070c69895c5918612c4cb0f7ce82c655877b9442 (patch)
tree48ea8a34040cbdae1e0ba0b4620d80e86dcfdabf
parent066bb85e2169daacb52a4b7c71923de9468a1ef3 (diff)
Added Banner
Banner will take over everything related to the page top banner. DocumentWidget's content positioning is a bit messy, and now that the banner is no longer part of the GmDocument, it needed a bit of revising. It would still benefit from proper functions for mapping coordinates to/from the GmDocument coordinate space.
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/gmdocument.c22
-rw-r--r--src/gmdocument.h19
-rw-r--r--src/ui/banner.c262
-rw-r--r--src/ui/banner.h56
-rw-r--r--src/ui/documentwidget.c295
6 files changed, 492 insertions, 164 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dd8b2eca..e3ed86e1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -174,6 +174,8 @@ set (SOURCES
174 src/audio/player.h 174 src/audio/player.h
175 src/audio/stb_vorbis.c 175 src/audio/stb_vorbis.c
176 # User interface: 176 # User interface:
177 src/ui/banner.c
178 src/ui/banner.h
177 src/ui/bindingswidget.c 179 src/ui/bindingswidget.c
178 src/ui/bindingswidget.h 180 src/ui/bindingswidget.h
179 src/ui/certimportwidget.c 181 src/ui/certimportwidget.c
diff --git a/src/gmdocument.c b/src/gmdocument.c
index b844c0f6..eac0ac62 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -97,8 +97,8 @@ struct Impl_GmDocument {
97 iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */ 97 iBool enableCommandLinks; /* `about:command?` only allowed on selected pages */
98 iArray layout; /* contents of source, laid out in document space */ 98 iArray layout; /* contents of source, laid out in document space */
99 iPtrArray links; 99 iPtrArray links;
100 enum iGmDocumentBanner bannerType; 100// enum iGmDocumentBanner bannerType;
101 iString bannerText; 101// iString bannerText;
102 iString title; /* the first top-level title */ 102 iString title; /* the first top-level title */
103 iArray headings; 103 iArray headings;
104 iArray preMeta; /* metadata about preformatted blocks */ 104 iArray preMeta; /* metadata about preformatted blocks */
@@ -573,7 +573,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
573 const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ 573 const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */
574 clear_Array(&d->preMeta); 574 clear_Array(&d->preMeta);
575 clear_String(&d->title); 575 clear_String(&d->title);
576 clear_String(&d->bannerText); 576// clear_String(&d->bannerText);
577 if (d->size.x <= 0 || isEmpty_String(&d->source)) { 577 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
578 return; 578 return;
579 } 579 }
@@ -587,7 +587,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
587 int preFont = preformatted_FontId; 587 int preFont = preformatted_FontId;
588 uint16_t preId = 0; 588 uint16_t preId = 0;
589 iBool enableIndents = iFalse; 589 iBool enableIndents = iFalse;
590 iBool addSiteBanner = d->bannerType != none_GmDocumentBanner; 590// iBool addSiteBanner = d->bannerType != none_GmDocumentBanner;
591 const iBool isNormalized = isNormalized_GmDocument_(d); 591 const iBool isNormalized = isNormalized_GmDocument_(d);
592 enum iGmLineType prevType = text_GmLineType; 592 enum iGmLineType prevType = text_GmLineType;
593 enum iGmLineType prevNonBlankType = text_GmLineType; 593 enum iGmLineType prevNonBlankType = text_GmLineType;
@@ -669,7 +669,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
669 if (d->format == gemini_SourceFormat && 669 if (d->format == gemini_SourceFormat &&
670 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { 670 startsWithSc_Rangecc(line, "```", &iCaseSensitive)) {
671 isPreformat = iFalse; 671 isPreformat = iFalse;
672 addSiteBanner = iFalse; /* overrides the banner */ 672// addSiteBanner = iFalse; /* overrides the banner */
673 continue; 673 continue;
674 } 674 }
675 run.mediaType = max_MediaType; /* preformatted block */ 675 run.mediaType = max_MediaType; /* preformatted block */
@@ -677,6 +677,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
677 run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); 677 run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont);
678 indent = indents[type]; 678 indent = indents[type];
679 } 679 }
680#if 0
680 if (addSiteBanner) { 681 if (addSiteBanner) {
681 addSiteBanner = iFalse; 682 addSiteBanner = iFalse;
682 const iRangecc bannerText = urlHost_String(&d->url); 683 const iRangecc bannerText = urlHost_String(&d->url);
@@ -697,6 +698,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
697 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing; 698 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing;
698 } 699 }
699 } 700 }
701#endif
700 /* Empty lines don't produce text runs. */ 702 /* Empty lines don't produce text runs. */
701 if (isEmpty_Range(&line)) { 703 if (isEmpty_Range(&line)) {
702 if (type == quote_GmLineType && !prefs->quoteIcon) { 704 if (type == quote_GmLineType && !prefs->quoteIcon) {
@@ -1068,13 +1070,13 @@ void init_GmDocument(iGmDocument *d) {
1068 init_String(&d->source); 1070 init_String(&d->source);
1069 init_String(&d->url); 1071 init_String(&d->url);
1070 init_String(&d->localHost); 1072 init_String(&d->localHost);
1071 d->bannerType = siteDomain_GmDocumentBanner; 1073// d->bannerType = siteDomain_GmDocumentBanner;
1072 d->outsideMargin = 0; 1074 d->outsideMargin = 0;
1073 d->size = zero_I2(); 1075 d->size = zero_I2();
1074 d->enableCommandLinks = iFalse; 1076 d->enableCommandLinks = iFalse;
1075 init_Array(&d->layout, sizeof(iGmRun)); 1077 init_Array(&d->layout, sizeof(iGmRun));
1076 init_PtrArray(&d->links); 1078 init_PtrArray(&d->links);
1077 init_String(&d->bannerText); 1079// init_String(&d->bannerText);
1078 init_String(&d->title); 1080 init_String(&d->title);
1079 init_Array(&d->headings, sizeof(iGmHeading)); 1081 init_Array(&d->headings, sizeof(iGmHeading));
1080 init_Array(&d->preMeta, sizeof(iGmPreMeta)); 1082 init_Array(&d->preMeta, sizeof(iGmPreMeta));
@@ -1089,7 +1091,7 @@ void init_GmDocument(iGmDocument *d) {
1089void deinit_GmDocument(iGmDocument *d) { 1091void deinit_GmDocument(iGmDocument *d) {
1090 iReleasePtr(&d->openURLs); 1092 iReleasePtr(&d->openURLs);
1091 delete_Media(d->media); 1093 delete_Media(d->media);
1092 deinit_String(&d->bannerText); 1094// deinit_String(&d->bannerText);
1093 deinit_String(&d->title); 1095 deinit_String(&d->title);
1094 clearLinks_GmDocument_(d); 1096 clearLinks_GmDocument_(d);
1095 deinit_PtrArray(&d->links); 1097 deinit_PtrArray(&d->links);
@@ -1625,9 +1627,11 @@ void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) {
1625 d->format = format; 1627 d->format = format;
1626} 1628}
1627 1629
1630#if 0
1628void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) { 1631void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) {
1629 d->bannerType = type; 1632 d->bannerType = type;
1630} 1633}
1634#endif
1631 1635
1632void setWidth_GmDocument(iGmDocument *d, int width, int outsideMargin) { 1636void setWidth_GmDocument(iGmDocument *d, int width, int outsideMargin) {
1633 d->size.x = width; 1637 d->size.x = width;
@@ -2104,6 +2108,7 @@ iInt2 size_GmDocument(const iGmDocument *d) {
2104 return d->size; 2108 return d->size;
2105} 2109}
2106 2110
2111#if 0
2107enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *d) { 2112enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *d) {
2108 return d->bannerType; 2113 return d->bannerType;
2109} 2114}
@@ -2126,6 +2131,7 @@ const iGmRun *siteBanner_GmDocument(const iGmDocument *d) {
2126const iString *bannerText_GmDocument(const iGmDocument *d) { 2131const iString *bannerText_GmDocument(const iGmDocument *d) {
2127 return &d->bannerText; 2132 return &d->bannerText;
2128} 2133}
2134#endif
2129 2135
2130const iArray *headings_GmDocument(const iGmDocument *d) { 2136const iArray *headings_GmDocument(const iGmDocument *d) {
2131 return &d->headings; 2137 return &d->headings;
diff --git a/src/gmdocument.h b/src/gmdocument.h
index e6388fbd..e010ad43 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -120,10 +120,10 @@ enum iGmRunFlags {
120 decoration_GmRunFlag = iBit(1), /* not part of the source */ 120 decoration_GmRunFlag = iBit(1), /* not part of the source */
121 startOfLine_GmRunFlag = iBit(2), 121 startOfLine_GmRunFlag = iBit(2),
122 endOfLine_GmRunFlag = iBit(3), 122 endOfLine_GmRunFlag = iBit(3),
123 siteBanner_GmRunFlag = iBit(4), /* area reserved for the site banner */ 123// siteBanner_GmRunFlag = iBit(4), /* area reserved for the site banner */
124 quoteBorder_GmRunFlag = iBit(5), 124 quoteBorder_GmRunFlag = iBit(5),
125 wide_GmRunFlag = iBit(6), /* horizontally scrollable */ 125 wide_GmRunFlag = iBit(6), /* horizontally scrollable */
126 footer_GmRunFlag = iBit(7), 126// footer_GmRunFlag = iBit(7),
127 altText_GmRunFlag = iBit(8), 127 altText_GmRunFlag = iBit(8),
128}; 128};
129 129
@@ -171,12 +171,13 @@ iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos);
171 171
172iDeclareClass(GmDocument) 172iDeclareClass(GmDocument)
173iDeclareObjectConstruction(GmDocument) 173iDeclareObjectConstruction(GmDocument)
174 174
175/*
175enum iGmDocumentBanner { 176enum iGmDocumentBanner {
176 none_GmDocumentBanner, 177 none_GmDocumentBanner,
177 siteDomain_GmDocumentBanner, 178 siteDomain_GmDocumentBanner,
178 certificateWarning_GmDocumentBanner, 179 certificateWarning_GmDocumentBanner,
179}; 180};*/
180 181
181enum iGmDocumentUpdate { 182enum iGmDocumentUpdate {
182 partial_GmDocumentUpdate, /* appending more content */ 183 partial_GmDocumentUpdate, /* appending more content */
@@ -185,7 +186,7 @@ enum iGmDocumentUpdate {
185 186
186void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); 187void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed);
187void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); 188void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format);
188void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); 189//void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type);
189void setWidth_GmDocument (iGmDocument *, int width, int outsideMargin); 190void setWidth_GmDocument (iGmDocument *, int width, int outsideMargin);
190void redoLayout_GmDocument (iGmDocument *); 191void redoLayout_GmDocument (iGmDocument *);
191iBool updateOpenURLs_GmDocument(iGmDocument *); 192iBool updateOpenURLs_GmDocument(iGmDocument *);
@@ -213,10 +214,10 @@ const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun
213 iRangei visRangeY, iGmDocumentRenderFunc render, 214 iRangei visRangeY, iGmDocumentRenderFunc render,
214 void *context); 215 void *context);
215iInt2 size_GmDocument (const iGmDocument *); 216iInt2 size_GmDocument (const iGmDocument *);
216const iGmRun * siteBanner_GmDocument (const iGmDocument *); 217//const iGmRun * siteBanner_GmDocument (const iGmDocument *);
217iBool hasSiteBanner_GmDocument (const iGmDocument *); 218//iBool hasSiteBanner_GmDocument (const iGmDocument *);
218enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *); 219//enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *);
219const iString * bannerText_GmDocument (const iGmDocument *); 220//const iString * bannerText_GmDocument (const iGmDocument *);
220const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 221const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
221const iString * source_GmDocument (const iGmDocument *); 222const iString * source_GmDocument (const iGmDocument *);
222size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ 223size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
diff --git a/src/ui/banner.c b/src/ui/banner.c
new file mode 100644
index 00000000..5ec3c9f0
--- /dev/null
+++ b/src/ui/banner.c
@@ -0,0 +1,262 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "banner.h"
24
25#include "command.h"
26#include "documentwidget.h"
27#include "paint.h"
28#include "util.h"
29
30iDeclareType(BannerItem)
31
32struct Impl_BannerItem {
33 enum iBannerType type;
34 enum iGmStatusCode code;
35 iString message;
36 int height;
37};
38
39static void init_BannerItem(iBannerItem *d) {
40 init_String(&d->message);
41 d->height = 0;
42}
43
44static void deinit_BannerItem(iBannerItem *d) {
45 deinit_String(&d->message);
46}
47
48/*----------------------------------------------------------------------------------------------*/
49
50struct Impl_Banner {
51 iDocumentWidget *doc;
52 iRect rect;
53 iString site;
54 iString icon;
55 iArray items;
56};
57
58iDefineTypeConstruction(Banner)
59
60static void updateHeight_Banner_(iBanner *d) {
61 d->rect.size.y = 0;
62 if (!isEmpty_String(&d->site)) { //} || !isEmpty_String(&d->icon)) {
63 d->rect.size.y += lineHeight_Text(banner_FontId) * 2;
64 }
65}
66
67void init_Banner(iBanner *d) {
68 d->doc = NULL;
69 d->rect = zero_Rect();
70 init_String(&d->site);
71 init_String(&d->icon);
72 init_Array(&d->items, sizeof(iBannerItem));
73}
74
75void deinit_Banner(iBanner *d) {
76 clear_Banner(d);
77 deinit_Array(&d->items);
78 deinit_String(&d->icon);
79 deinit_String(&d->site);
80}
81
82void setOwner_Banner(iBanner *d, iDocumentWidget *owner) {
83 d->doc = owner;
84}
85
86void setWidth_Banner(iBanner *d, int width) {
87 d->rect.size.x = width;
88 updateHeight_Banner_(d);
89}
90
91void setPos_Banner(iBanner *d, iInt2 pos) {
92 d->rect.pos = pos;
93}
94
95int height_Banner(const iBanner *d) {
96 return d->rect.size.y;
97}
98
99void clear_Banner(iBanner *d) {
100 iForEach(Array, i, &d->items) {
101 deinit_BannerItem(i.value);
102 }
103 clear_Array(&d->items);
104 clear_String(&d->site);
105 clear_String(&d->icon);
106 d->rect.size.y = 0;
107}
108
109void setSite_Banner(iBanner *d, iRangecc site, iChar icon) {
110 clear_String(&d->site);
111 clear_String(&d->icon);
112 if (icon) {
113 setRange_String(&d->site, site);
114 appendChar_String(&d->icon, icon);
115 }
116 updateHeight_Banner_(d);
117}
118
119void add_Banner(iBanner *d, enum iBannerType type, enum iGmStatusCode code, const iString *message) {
120 iBannerItem item;
121 init_BannerItem(&item);
122 item.type = type;
123 item.code = code;
124 set_String(&item.message, message);
125 pushBack_Array(&d->items, &item);
126 updateHeight_Banner_(d);
127}
128
129void remove_Banner(iBanner *d, enum iGmStatusCode code) {
130 iForEach(Array, i, &d->items) {
131 iBannerItem *item = i.value;
132 if (item->code == code) {
133 deinit_BannerItem(item);
134 remove_ArrayIterator(&i);
135 }
136 }
137 updateHeight_Banner_(d);
138}
139
140iBool processEvent_Banner(iBanner *d, const SDL_Event *ev) {
141 iWidget *w = as_Widget(d->doc);
142 /* on motion: */
143// setCursor_Window(window_Widget(w), SDL_SYSTEM_CURSOR_HAND);
144 return iFalse;
145}
146
147#if 0
148static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2 visPos) {
149 const iGmDocument *doc = d->widget->doc;
150 const iChar icon = siteIcon_GmDocument(doc);
151 iString str;
152 init_String(&str);
153 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
154 if (icon) {
155 appendChar_String(&str, icon);
156 const iRect iconRect = visualBounds_Text(run->font, range_String(&str));
157 drawRange_Text(
158 run->font,
159 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
160 tmBannerIcon_ColorId,
161 range_String(&str));
162 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
163 }
164 drawRange_Text(run->font,
165 bpos,
166 tmBannerTitle_ColorId,
167 bannerText_DocumentWidget_(d->widget));
168 if (bannerType_GmDocument(doc) == certificateWarning_GmDocumentBanner) {
169 const int domainHeight = lineHeight_Text(banner_FontId) * 2;
170 iRect rect = { add_I2(visPos, init_I2(0, domainHeight)),
171 addY_I2(run->visBounds.size, -domainHeight - lineHeight_Text(uiContent_FontId)) };
172 format_String(&str, "${heading.certwarn}");
173 const int certFlags = d->widget->certFlags;
174 if (certFlags & timeVerified_GmCertFlag && certFlags & domainVerified_GmCertFlag) {
175 iUrl parts;
176 init_Url(&parts, d->widget->mod.url);
177 const iTime oldUntil =
178 domainValidUntil_GmCerts(certs_App(), parts.host, port_Url(&parts));
179 iDate exp;
180 init_Date(&exp, &oldUntil);
181 iTime now;
182 initCurrent_Time(&now);
183 const int days = secondsSince_Time(&oldUntil, &now) / 3600 / 24;
184 appendCStr_String(&str, "\n");
185 if (days <= 30) {
186 appendCStr_String(&str,
187 format_CStr(cstrCount_Lang("dlg.certwarn.mayberenewed.n", days),
188 cstrCollect_String(format_Date(&exp, "%Y-%m-%d")),
189 days));
190 }
191 else {
192 appendCStr_String(&str, cstr_Lang("dlg.certwarn.different"));
193 }
194 }
195 else if (certFlags & domainVerified_GmCertFlag) {
196 appendCStr_String(&str, "\n");
197 appendFormat_String(&str, cstr_Lang("dlg.certwarn.expired"),
198 cstrCollect_String(format_Date(&d->widget->certExpiry, "%Y-%m-%d")));
199 }
200 else if (certFlags & timeVerified_GmCertFlag) {
201 appendCStr_String(&str, "\n");
202 appendFormat_String(&str, cstr_Lang("dlg.certwarn.domain"),
203 cstr_String(d->widget->certSubject));
204 }
205 else {
206 appendCStr_String(&str, "\n");
207 appendCStr_String(&str, cstr_Lang("dlg.certwarn.domain.expired"));
208 }
209 const iInt2 dims = measureWrapRange_Text(
210 uiContent_FontId, width_Rect(rect) - 16 * gap_UI, range_String(&str)).bounds.size;
211 const int warnHeight = run->visBounds.size.y - domainHeight;
212 const int yOff = (lineHeight_Text(uiLabelLarge_FontId) -
213 lineHeight_Text(uiContent_FontId)) / 2;
214 const iRect bgRect =
215 init_Rect(0, visPos.y + domainHeight, d->widgetBounds.size.x, warnHeight);
216 fillRect_Paint(&d->paint, bgRect, orange_ColorId);
217 if (!isDark_ColorTheme(colorTheme_App())) {
218 drawHLine_Paint(&d->paint,
219 topLeft_Rect(bgRect), width_Rect(bgRect), tmBannerTitle_ColorId);
220 drawHLine_Paint(&d->paint,
221 bottomLeft_Rect(bgRect), width_Rect(bgRect), tmBannerTitle_ColorId);
222 }
223 const int fg = black_ColorId;
224 adjustEdges_Rect(&rect, warnHeight / 2 - dims.y / 2 - yOff, 0, 0, 0);
225 bpos = topLeft_Rect(rect);
226 draw_Text(uiLabelLarge_FontId, bpos, fg, "\u26a0");
227 adjustEdges_Rect(&rect, 0, -8 * gap_UI, 0, 8 * gap_UI);
228 translate_Lang(&str);
229 drawWrapRange_Text(uiContent_FontId,
230 addY_I2(topLeft_Rect(rect), yOff),
231 width_Rect(rect),
232 fg,
233 range_String(&str));
234 }
235 deinit_String(&str);
236}
237#endif
238
239void draw_Banner(const iBanner *d) {
240 if (isEmpty_Banner(d)) {
241 return;
242 }
243 iRect bounds = d->rect;
244 iInt2 pos = addY_I2(topLeft_Rect(bounds), lineHeight_Text(banner_FontId) / 2);
245 iPaint p;
246 init_Paint(&p);
247// drawRect_Paint(&p, bounds, red_ColorId);
248 /* Draw the icon. */
249 if (!isEmpty_String(&d->icon)) {
250 const int font = banner_FontId;
251 const iRect iconRect = visualBounds_Text(font, range_String(&d->icon));
252 drawRange_Text(font,
253 addY_I2(pos, -mid_Rect(iconRect).y + lineHeight_Text(font) / 2),
254 tmBannerIcon_ColorId,
255 range_String(&d->icon));
256 pos.x += right_Rect(iconRect) + 3 * gap_Text;
257 }
258 /* Draw the site name. */
259 if (!isEmpty_String(&d->site)) {
260 drawRange_Text(banner_FontId, pos, tmBannerTitle_ColorId, range_String(&d->site));
261 }
262}
diff --git a/src/ui/banner.h b/src/ui/banner.h
new file mode 100644
index 00000000..5ee8b3ae
--- /dev/null
+++ b/src/ui/banner.h
@@ -0,0 +1,56 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#pragma once
24
25#include "../gmutil.h"
26
27#include <the_Foundation/vec2.h>
28#include <SDL_events.h>
29
30iDeclareType(Banner)
31iDeclareTypeConstruction(Banner)
32
33iDeclareType(DocumentWidget)
34
35enum iBannerType {
36 warning_BannerType,
37 error_BannerType,
38};
39
40void setOwner_Banner (iBanner *, iDocumentWidget *owner);
41void setWidth_Banner (iBanner *, int width);
42void setPos_Banner (iBanner *, iInt2 pos);
43
44int height_Banner (const iBanner *);
45
46iLocalDef iBool isEmpty_Banner(const iBanner *d) {
47 return height_Banner(d) == 0;
48}
49
50void clear_Banner (iBanner *);
51void setSite_Banner (iBanner *, iRangecc site, iChar icon);
52void add_Banner (iBanner *, enum iBannerType type, enum iGmStatusCode code, const iString *message);
53void remove_Banner (iBanner *, enum iGmStatusCode code);
54
55iBool processEvent_Banner (iBanner *, const SDL_Event *ev);
56void draw_Banner (const iBanner *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 963bae8f..17111ed0 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
27 27
28#include "app.h" 28#include "app.h"
29#include "audio/player.h" 29#include "audio/player.h"
30#include "banner.h"
30#include "bookmarks.h" 31#include "bookmarks.h"
31#include "command.h" 32#include "command.h"
32#include "defs.h" 33#include "defs.h"
@@ -280,6 +281,7 @@ struct Impl_DocumentWidget {
280 iTime sourceTime; 281 iTime sourceTime;
281 iGempub * sourceGempub; /* NULL unless the page is Gempub content */ 282 iGempub * sourceGempub; /* NULL unless the page is Gempub content */
282 iGmDocument * doc; 283 iGmDocument * doc;
284 iBanner * banner;
283 285
284 /* Rendering: */ 286 /* Rendering: */
285 int pageMargin; 287 int pageMargin;
@@ -339,6 +341,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
339 d->isRequestUpdated = iFalse; 341 d->isRequestUpdated = iFalse;
340 d->media = new_ObjectList(); 342 d->media = new_ObjectList();
341 d->doc = new_GmDocument(); 343 d->doc = new_GmDocument();
344 d->banner = new_Banner();
342 d->redirectCount = 0; 345 d->redirectCount = 0;
343 d->ordinalBase = 0; 346 d->ordinalBase = 0;
344 d->initNormScrollY = 0; 347 d->initNormScrollY = 0;
@@ -429,6 +432,7 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
429 deinit_Block(&d->sourceContent); 432 deinit_Block(&d->sourceContent);
430 deinit_String(&d->sourceMime); 433 deinit_String(&d->sourceMime);
431 deinit_String(&d->sourceHeader); 434 deinit_String(&d->sourceHeader);
435 delete_Banner(d->banner);
432 iRelease(d->doc); 436 iRelease(d->doc);
433 if (d->mediaTimer) { 437 if (d->mediaTimer) {
434 SDL_RemoveTimer(d->mediaTimer); 438 SDL_RemoveTimer(d->mediaTimer);
@@ -512,35 +516,67 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
512 prefs->lineWidth * prefs->zoomPercent / 100); 516 prefs->lineWidth * prefs->zoomPercent / 100);
513} 517}
514 518
519static int documentTopPad_DocumentWidget_(const iDocumentWidget *d) {
520 /* Amount of space between banner and top of the document. */
521 return isEmpty_Banner(d->banner) ? 0 : lineHeight_Text(paragraph_FontId);
522}
523
524static int pageHeight_DocumentWidget_(const iDocumentWidget *d) {
525 return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) + size_GmDocument(d->doc).y;
526}
527
515static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 528static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
516 const iRect bounds = bounds_Widget(constAs_Widget(d)); 529 const iRect bounds = bounds_Widget(constAs_Widget(d));
517 const int margin = gap_UI * d->pageMargin; 530 const int margin = gap_UI * d->pageMargin;
518 iRect rect; 531 iRect rect;
519 rect.size.x = documentWidth_DocumentWidget_(d); 532 rect.size.x = documentWidth_DocumentWidget_(d);
520 rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2; 533 rect.pos.x = mid_Rect(bounds).x - rect.size.x / 2;
521 rect.pos.y = top_Rect(bounds); 534 rect.pos.y = top_Rect(bounds) + margin;
522 rect.size.y = height_Rect(bounds) - margin; 535 rect.size.y = height_Rect(bounds) - margin;
523 const iGmRun *banner = siteBanner_GmDocument(d->doc); 536 iBool wasCentered = iFalse;
524 if (!banner) {
525 rect.pos.y += margin;
526 rect.size.y -= margin;
527 }
528 if (d->flags & centerVertically_DocumentWidgetFlag) { 537 if (d->flags & centerVertically_DocumentWidgetFlag) {
529 const iInt2 docSize = addY_I2(size_GmDocument(d->doc), 538 const iInt2 docSize = addY_I2(size_GmDocument(d->doc),
530 iMax(height_Widget(d->footerButtons), height_Widget(d->phoneToolbar))); 539 iMax(height_Widget(d->footerButtons), height_Widget(d->phoneToolbar)));
531 if (docSize.y < rect.size.y) { 540 if (docSize.y < rect.size.y) {
532 /* Center vertically if short. There is one empty paragraph line's worth of margin 541 /* Center vertically if short. There is one empty paragraph line's worth of margin
533 between the banner and the page contents. */ 542 between the banner and the page contents. */
534 const int bannerHeight = banner ? height_Rect(banner->visBounds) : 0; 543// const int bannerHeight = 0; //banner ? height_Rect(banner->visBounds) : 0;
535 int offset = iMax(0, (rect.size.y + margin - docSize.y - bannerHeight - 544#if 0
536 lineHeight_Text(paragraph_FontId)) / 2); 545 int offset = iMax(0, (rect.size.y + margin - size_GmDocument(d->doc).y
537 rect.pos.y += offset; 546 //- lineHeight_Text(paragraph_FontId)
547 ) / 2 - height_Banner(d->banner)
548 //-
549 //documentTopPad_DocumentWidget_(d)
550 );
551#endif
552 int offset = iMax(0, height_Rect(bounds) / 2
553// - (isEmpty_Banner(d->banner) ? lineHeight_Text(paragraph_FontId) / 2 : 0)
554 - documentTopPad_DocumentWidget_(d)
555// + lineHeight_Text(paragraph_FontId) / 2
556// - isEmpty_Banner(d->banner) ?
557 - height_Banner(d->banner)
558 - size_GmDocument(d->doc).y / 2
559 );
560 //(//documentTopPad_DocumentWidget_(d) +
561 //size_GmDocument(d->doc).y) / 2);
562 rect.pos.y = top_Rect(bounds) + offset;
538 rect.size.y = docSize.y; 563 rect.size.y = docSize.y;
564 wasCentered = iTrue;
539 } 565 }
540 } 566 }
567 if (!wasCentered && !isEmpty_Banner(d->banner)) {
568 /* The banner overtakes the top banner. */
569 rect.pos.y -= margin;
570 rect.size.y -= margin;
571 }
541 return rect; 572 return rect;
542} 573}
543 574
575static int viewPos_DocumentWidget_(const iDocumentWidget *d) {
576 return height_Banner(d->banner) + documentTopPad_DocumentWidget_(d) - pos_SmoothScroll(&d->scrollY);
577}
578
579#if 0
544static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) { 580static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) {
545 const iGmRun *banner = siteBanner_GmDocument(d->doc); 581 const iGmRun *banner = siteBanner_GmDocument(d->doc);
546 if (!banner) { 582 if (!banner) {
@@ -550,17 +586,23 @@ static iRect siteBannerRect_DocumentWidget_(const iDocumentWidget *d) {
550 const iInt2 origin = addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY)); 586 const iInt2 origin = addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY));
551 return moved_Rect(banner->visBounds, origin); 587 return moved_Rect(banner->visBounds, origin);
552} 588}
589#endif
553 590
554static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 591static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
555 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), 592 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))),
556 pos_SmoothScroll(&d->scrollY)); 593 -viewPos_DocumentWidget_(d));
557} 594}
558 595
559static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { 596static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
560 const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0; 597// const int margin = -documentTopPad_DocumentWidget_(d) +
561 return (iRangei){ pos_SmoothScroll(&d->scrollY) - margin, 598// d->pageMargin * gap_UI;
562 pos_SmoothScroll(&d->scrollY) + height_Rect(bounds_Widget(constAs_Widget(d))) - 599 //const int top = -viewPos_DocumentWidget_(d) - margin;
563 margin }; 600 int top = pos_SmoothScroll(&d->scrollY) - height_Banner(d->banner) - documentTopPad_DocumentWidget_(d);
601 if (isEmpty_Banner(d->banner)) {
602 /* Top padding is not collapsed. */
603 top -= d->pageMargin * gap_UI;
604 }
605 return (iRangei){ top, top + height_Rect(bounds_Widget(constAs_Widget(d))) };
564} 606}
565 607
566static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { 608static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
@@ -598,7 +640,7 @@ static const iGmRun *lastVisibleLink_DocumentWidget_(const iDocumentWidget *d) {
598} 640}
599 641
600static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { 642static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) {
601 const int docSize = size_GmDocument(d->doc).y; 643 const int docSize = pageHeight_DocumentWidget_(d); // size_GmDocument(d->doc).y;
602 if (docSize) { 644 if (docSize) {
603 return pos_SmoothScroll(&d->scrollY) / (float) docSize; 645 return pos_SmoothScroll(&d->scrollY) / (float) docSize;
604 } 646 }
@@ -607,15 +649,9 @@ static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) {
607 649
608static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { 650static int scrollMax_DocumentWidget_(const iDocumentWidget *d) {
609 const iWidget *w = constAs_Widget(d); 651 const iWidget *w = constAs_Widget(d);
610 int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(w)) + 652 int sm = pageHeight_DocumentWidget_(d) - height_Rect(bounds_Widget(w)) +
611 (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI + 653 (isEmpty_Banner(d->banner) ? 2 : 1) * d->pageMargin * gap_UI + /* top and bottom margins */
612 iMax(height_Widget(d->phoneToolbar), height_Widget(d->footerButtons)); 654 iMax(height_Widget(d->phoneToolbar), height_Widget(d->footerButtons));
613// sm += height_Widget(d->phoneToolbar);
614// if (d->phoneToolbar) {
615// sm += size_Root(w->root).y -
616// top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar));
617// sm += height_Widget(d->phoneToolbar);
618// }
619 return sm; 655 return sm;
620} 656}
621 657
@@ -699,7 +735,8 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
699 const iGmRun * oldHoverLink = d->hoverLink; 735 const iGmRun * oldHoverLink = d->hoverLink;
700 d->hoverPre = NULL; 736 d->hoverPre = NULL;
701 d->hoverLink = NULL; 737 d->hoverLink = NULL;
702 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), pos_SmoothScroll(&d->scrollY)); 738 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)),
739 -viewPos_DocumentWidget_(d));
703 if (isHoverAllowed_DocumentWidget_(d)) { 740 if (isHoverAllowed_DocumentWidget_(d)) {
704 iConstForEach(PtrArray, i, &d->visibleLinks) { 741 iConstForEach(PtrArray, i, &d->visibleLinks) {
705 const iGmRun *run = i.ptr; 742 const iGmRun *run = i.ptr;
@@ -757,8 +794,9 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
757 794
758static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) { 795static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d, iBool isAnimated) {
759 float opacity = 0.0f; 796 float opacity = 0.0f;
760 const iGmRun *banner = siteBanner_GmDocument(d->doc); 797// const iGmRun *banner = siteBanner_GmDocument(d->doc);
761 if (banner && bottom_Rect(banner->visBounds) < pos_SmoothScroll(&d->scrollY)) { 798 if (!isEmpty_Banner(d->banner) && height_Banner(d->banner) < pos_SmoothScroll(&d->scrollY)) {
799// if (banner && bottom_Rect(banner->visBounds) < pos_SmoothScroll(&d->scrollY)) {
762 opacity = 1.0f; 800 opacity = 1.0f;
763 } 801 }
764 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0); 802 setValue_Anim(&d->sideOpacity, opacity, isAnimated ? (opacity < 0.5f ? 100 : 200) : 0);
@@ -862,6 +900,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
862 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") || 900 prefs_App()->centerShortDocs || startsWithCase_String(d->mod.url, "about:") ||
863 !isSuccess_GmStatusCode(d->sourceStatus)); 901 !isSuccess_GmStatusCode(d->sourceStatus));
864 const iRangei visRange = visibleRange_DocumentWidget_(d); 902 const iRangei visRange = visibleRange_DocumentWidget_(d);
903// printf("visRange: %d...%d\n", visRange.start, visRange.end);
865 const iRect bounds = bounds_Widget(as_Widget(d)); 904 const iRect bounds = bounds_Widget(as_Widget(d));
866 const int scrollMax = updateScrollMax_DocumentWidget_(d); 905 const int scrollMax = updateScrollMax_DocumentWidget_(d);
867 /* Reposition the footer buttons as appropriate. */ 906 /* Reposition the footer buttons as appropriate. */
@@ -880,13 +919,13 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
880 } 919 }
881 else { 920 else {
882 d->footerButtons->animOffsetRef = &d->scrollY.pos; 921 d->footerButtons->animOffsetRef = &d->scrollY.pos;
883 d->footerButtons->rect.pos.y = size_GmDocument(d->doc).y + 2 * gap_UI * d->pageMargin; 922 d->footerButtons->rect.pos.y = pageHeight_DocumentWidget_(d) + 2 * gap_UI * d->pageMargin;
884// + height_Widget(d->phoneToolbar); 923// + height_Widget(d->phoneToolbar);
885 } 924 }
886 } 925 }
887 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); 926 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax });
888 const int docSize = size_GmDocument(d->doc).y + iMax(height_Widget(d->phoneToolbar), 927 const int docSize = pageHeight_DocumentWidget_(d) + iMax(height_Widget(d->phoneToolbar),
889 height_Widget(d->footerButtons)); 928 height_Widget(d->footerButtons));
890 setThumb_ScrollWidget(d->scroll, 929 setThumb_ScrollWidget(d->scroll,
891 pos_SmoothScroll(&d->scrollY), 930 pos_SmoothScroll(&d->scrollY),
892 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0); 931 docSize > 0 ? height_Rect(bounds) * size_Range(&visRange) / docSize : 0);
@@ -906,6 +945,11 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
906 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0)); 945 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window(), 0));
907 updateSideOpacity_DocumentWidget_(d, iTrue); 946 updateSideOpacity_DocumentWidget_(d, iTrue);
908 animateMedia_DocumentWidget_(d); 947 animateMedia_DocumentWidget_(d);
948 setPos_Banner(d->banner, addY_I2(topLeft_Rect(documentBounds_DocumentWidget_(d)),
949 -pos_SmoothScroll(&d->scrollY)));
950 /*init_I2(documentBounds_DocumentWidget_(d).pos.x,
951 viewPos_DocumentWidget_(d) -
952 documentTopPad_DocumentWidget_(d)));*/
909 /* Remember scroll positions of recently visited pages. */ { 953 /* Remember scroll positions of recently visited pages. */ {
910 iRecentUrl *recent = mostRecentUrl_History(d->mod.history); 954 iRecentUrl *recent = mostRecentUrl_History(d->mod.history);
911 if (recent && docSize && d->state == ready_RequestState) { 955 if (recent && docSize && d->state == ready_RequestState) {
@@ -1015,18 +1059,18 @@ static void invalidate_DocumentWidget_(iDocumentWidget *d) {
1015 clear_PtrSet(d->invalidRuns); 1059 clear_PtrSet(d->invalidRuns);
1016} 1060}
1017 1061
1018static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) { 1062static iRangecc siteText_DocumentWidget_(const iDocumentWidget *d) {
1019 return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc)) 1063 return isEmpty_String(d->titleUser) ? urlHost_String(d->mod.url)
1020 : range_String(d->titleUser); 1064 : range_String(d->titleUser);
1021} 1065}
1022 1066
1023static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { 1067static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) {
1024 d->foundMark = iNullRange; 1068 d->foundMark = iNullRange;
1025 d->selectMark = iNullRange; 1069 d->selectMark = iNullRange;
1026 d->hoverPre = NULL; 1070 d->hoverPre = NULL;
1027 d->hoverAltPre = NULL; 1071 d->hoverAltPre = NULL;
1028 d->hoverLink = NULL; 1072 d->hoverLink = NULL;
1029 d->contextLink = NULL; 1073 d->contextLink = NULL;
1030 iZap(d->visibleRuns); 1074 iZap(d->visibleRuns);
1031 iZap(d->renderRuns); 1075 iZap(d->renderRuns);
1032} 1076}
@@ -1087,6 +1131,7 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1087 outsideMargin, 1131 outsideMargin,
1088 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate 1132 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1089 : partial_GmDocumentUpdate); 1133 : partial_GmDocumentUpdate);
1134 setWidth_Banner(d->banner, docWidth);
1090 documentWasChanged_DocumentWidget_(d); 1135 documentWasChanged_DocumentWidget_(d);
1091} 1136}
1092 1137
@@ -1097,6 +1142,11 @@ static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *new
1097 documentWasChanged_DocumentWidget_(d); 1142 documentWasChanged_DocumentWidget_(d);
1098} 1143}
1099 1144
1145static void updateBanner_DocumentWidget_(iDocumentWidget *d) {
1146 clear_Banner(d->banner);
1147 setSite_Banner(d->banner, siteText_DocumentWidget_(d), siteIcon_GmDocument(d->doc));
1148}
1149
1100static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1150static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
1101 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { 1151 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
1102 iBlock empty; 1152 iBlock empty;
@@ -1112,8 +1162,10 @@ static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
1112 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars); 1162 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars);
1113 } 1163 }
1114 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag; 1164 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag;
1165 updateBanner_DocumentWidget_(d);
1115} 1166}
1116 1167
1168#if 0
1117static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget *d) { 1169static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget *d) {
1118 if (d->certFlags & available_GmCertFlag) { 1170 if (d->certFlags & available_GmCertFlag) {
1119 const int req = domainVerified_GmCertFlag | timeVerified_GmCertFlag | trusted_GmCertFlag; 1171 const int req = domainVerified_GmCertFlag | timeVerified_GmCertFlag | trusted_GmCertFlag;
@@ -1123,6 +1175,7 @@ static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget *
1123 } 1175 }
1124 return siteDomain_GmDocumentBanner; 1176 return siteDomain_GmDocumentBanner;
1125} 1177}
1178#endif
1126 1179
1127static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuItem *items, size_t count) { 1180static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuItem *items, size_t count) {
1128 iWidget *w = as_Widget(d); 1181 iWidget *w = as_Widget(d);
@@ -1160,6 +1213,8 @@ static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuIte
1160 1213
1161static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, 1214static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code,
1162 const iString *meta) { 1215 const iString *meta) {
1216 /* TODO: No such thing as an "error page". It should be an empty page with an error banner. */
1217#if 0
1163 iString *src = collectNewCStr_String("# "); 1218 iString *src = collectNewCStr_String("# ");
1164 const iGmError *msg = get_GmError(code); 1219 const iGmError *msg = get_GmError(code);
1165 appendChar_String(src, msg->icon ? msg->icon : 0x2327); /* X in a box */ 1220 appendChar_String(src, msg->icon ? msg->icon : 0x2327); /* X in a box */
@@ -1242,14 +1297,16 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1242 /* Make a new document for the error page.*/ { 1297 /* Make a new document for the error page.*/ {
1243 iGmDocument *errorDoc = new_GmDocument(); 1298 iGmDocument *errorDoc = new_GmDocument();
1244 setUrl_GmDocument(errorDoc, d->mod.url); 1299 setUrl_GmDocument(errorDoc, d->mod.url);
1245 setBanner_GmDocument(errorDoc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1300// setBanner_GmDocument(errorDoc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1301
1246 setFormat_GmDocument(errorDoc, gemini_SourceFormat); 1302 setFormat_GmDocument(errorDoc, gemini_SourceFormat);
1247 replaceDocument_DocumentWidget_(d, errorDoc); 1303 replaceDocument_DocumentWidget_(d, errorDoc);
1248 iRelease(errorDoc); 1304 iRelease(errorDoc);
1249 } 1305 }
1250 translate_Lang(src); 1306 translate_Lang(src);
1307#endif
1251 d->state = ready_RequestState; 1308 d->state = ready_RequestState;
1252 setSource_DocumentWidget(d, src); 1309// setSource_DocumentWidget(d, src);
1253 updateTheme_DocumentWidget_(d); 1310 updateTheme_DocumentWidget_(d);
1254 reset_SmoothScroll(&d->scrollY); 1311 reset_SmoothScroll(&d->scrollY);
1255 init_Anim(&d->sideOpacity, 0); 1312 init_Anim(&d->sideOpacity, 0);
@@ -1729,7 +1786,7 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r
1729 else { 1786 else {
1730 updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_Icon); 1787 updateTextCStr_LabelWidget(lock, green_ColorEscape closedLock_Icon);
1731 } 1788 }
1732 setBanner_GmDocument(d->doc, bannerType_DocumentWidget_(d)); 1789// setBanner_GmDocument(d->doc, bannerType_DocumentWidget_(d));
1733} 1790}
1734 1791
1735static void parseUser_DocumentWidget_(iDocumentWidget *d) { 1792static void parseUser_DocumentWidget_(iDocumentWidget *d) {
@@ -1778,12 +1835,13 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1778 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); 1835 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1779// setCachedDocument_History(d->mod.history, d->doc, 1836// setCachedDocument_History(d->mod.history, d->doc,
1780// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); 1837// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1838 updateBanner_DocumentWidget_(d);
1781 } 1839 }
1782 d->state = ready_RequestState; 1840 d->state = ready_RequestState;
1783 postProcessRequestContent_DocumentWidget_(d, iTrue); 1841 postProcessRequestContent_DocumentWidget_(d, iTrue);
1784 init_Anim(&d->altTextOpacity, 0); 1842 init_Anim(&d->altTextOpacity, 0);
1785 reset_SmoothScroll(&d->scrollY); 1843 reset_SmoothScroll(&d->scrollY);
1786 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1844 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d));
1787 updateSideOpacity_DocumentWidget_(d, iFalse); 1845 updateSideOpacity_DocumentWidget_(d, iFalse);
1788 updateVisible_DocumentWidget_(d); 1846 updateVisible_DocumentWidget_(d);
1789 moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ 1847 moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */
@@ -1870,8 +1928,11 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
1870} 1928}
1871 1929
1872static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) { 1930static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) {
1873 if (!hasSiteBanner_GmDocument(d->doc)) { 1931 if (!isEmpty_Banner(d->banner)) {
1874 documentY += d->pageMargin * gap_UI; 1932 documentY += height_Banner(d->banner) + documentTopPad_DocumentWidget_(d);
1933 }
1934 else {
1935 documentY += documentTopPad_DocumentWidget_(d) + d->pageMargin * gap_UI;
1875 } 1936 }
1876 init_Anim(&d->scrollY.pos, 1937 init_Anim(&d->scrollY.pos,
1877 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 1938 documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2
@@ -2403,6 +2464,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen
2403 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); 2464 voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds);
2404 } 2465 }
2405 setWidth_GmDocument(d->doc, newWidth, (width_Widget(d) - newWidth) / 2); 2466 setWidth_GmDocument(d->doc, newWidth, (width_Widget(d) - newWidth) / 2);
2467 setWidth_Banner(d->banner, newWidth);
2406 documentRunsInvalidated_DocumentWidget_(d); 2468 documentRunsInvalidated_DocumentWidget_(d);
2407 if (runLoc && !keepCenter) { 2469 if (runLoc && !keepCenter) {
2408 run = findRunAtLoc_GmDocument(d->doc, runLoc); 2470 run = findRunAtLoc_GmDocument(d->doc, runLoc);
@@ -2504,7 +2566,8 @@ static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2504 if (recent->cachedDoc) { 2566 if (recent->cachedDoc) {
2505 iChangeRef(swipeIn->doc, recent->cachedDoc); 2567 iChangeRef(swipeIn->doc, recent->cachedDoc);
2506 updateScrollMax_DocumentWidget_(d); 2568 updateScrollMax_DocumentWidget_(d);
2507 setValue_Anim(&swipeIn->scrollY.pos, size_GmDocument(swipeIn->doc).y * recent->normScrollY, 0); 2569 setValue_Anim(&swipeIn->scrollY.pos,
2570 pageHeight_DocumentWidget_(d) * recent->normScrollY, 0);
2508 updateVisible_DocumentWidget_(swipeIn); 2571 updateVisible_DocumentWidget_(swipeIn);
2509 swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 2572 swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
2510 } 2573 }
@@ -2935,7 +2998,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2935 updateFetchProgress_DocumentWidget_(d); 2998 updateFetchProgress_DocumentWidget_(d);
2936 checkResponse_DocumentWidget_(d); 2999 checkResponse_DocumentWidget_(d);
2937 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { 3000 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) {
2938 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ 3001 init_Anim(&d->scrollY.pos, d->initNormScrollY * pageHeight_DocumentWidget_(d)); /* TODO: unless user already scrolled! */
2939 } 3002 }
2940 iChangeFlags(d->flags, 3003 iChangeFlags(d->flags,
2941 urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag, 3004 urlChanged_DocumentWidgetFlag | drawDownloadCounter_DocumentWidgetFlag,
@@ -3352,7 +3415,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3352 3415
3353static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) { 3416static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run) {
3354 const iRect docBounds = documentBounds_DocumentWidget_(d); 3417 const iRect docBounds = documentBounds_DocumentWidget_(d);
3355 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), -pos_SmoothScroll(&d->scrollY))); 3418 return moved_Rect(run->bounds, addY_I2(topLeft_Rect(docBounds), viewPos_DocumentWidget_(d)));
3356} 3419}
3357 3420
3358static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 3421static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
@@ -3660,9 +3723,11 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3660 if (isVisible_Widget(d->menu)) { 3723 if (isVisible_Widget(d->menu)) {
3661 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 3724 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
3662 } 3725 }
3726#if 0
3663 else if (contains_Rect(siteBannerRect_DocumentWidget_(d), mpos)) { 3727 else if (contains_Rect(siteBannerRect_DocumentWidget_(d), mpos)) {
3664 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_HAND); 3728 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_HAND);
3665 } 3729 }
3730#endif
3666 else { 3731 else {
3667 if (value_Anim(&d->altTextOpacity) < 0.833f) { 3732 if (value_Anim(&d->altTextOpacity) < 0.833f) {
3668 setValue_Anim(&d->altTextOpacity, 0, 0); /* keep it hidden while moving */ 3733 setValue_Anim(&d->altTextOpacity, 0, 0); /* keep it hidden while moving */
@@ -4126,6 +4191,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4126 d->selectMark = iNullRange; 4191 d->selectMark = iNullRange;
4127 refresh_Widget(w); 4192 refresh_Widget(w);
4128 } 4193 }
4194#if 0
4129 /* Clicking on the top/side banner navigates to site root. */ 4195 /* Clicking on the top/side banner navigates to site root. */
4130 const iRect banRect = siteBannerRect_DocumentWidget_(d); 4196 const iRect banRect = siteBannerRect_DocumentWidget_(d);
4131 if (contains_Rect(banRect, pos_Click(&d->click))) { 4197 if (contains_Rect(banRect, pos_Click(&d->click))) {
@@ -4139,6 +4205,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
4139 postCommand_Widget(d, "navigate.root"); 4205 postCommand_Widget(d, "navigate.root");
4140 } 4206 }
4141 } 4207 }
4208#endif
4142 } 4209 }
4143 return iTrue; 4210 return iTrue;
4144 case aborted_ClickResult: 4211 case aborted_ClickResult:
@@ -4200,7 +4267,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4200 } 4267 }
4201 if (~run->flags & decoration_GmRunFlag) { 4268 if (~run->flags & decoration_GmRunFlag) {
4202 const iInt2 visPos = 4269 const iInt2 visPos =
4203 add_I2(run->bounds.pos, addY_I2(d->viewPos, -pos_SmoothScroll(&d->widget->scrollY))); 4270 add_I2(run->bounds.pos, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget)));
4204 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) }; 4271 const iRect rangeRect = { addX_I2(visPos, x), init_I2(w, height_Rect(run->bounds)) };
4205 if (rangeRect.size.x) { 4272 if (rangeRect.size.x) {
4206 fillRect_Paint(&d->paint, rangeRect, color); 4273 fillRect_Paint(&d->paint, rangeRect, color);
@@ -4220,7 +4287,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4220 (contains_Range(&url, mark.end) || url.end == mark.end)) { 4287 (contains_Range(&url, mark.end) || url.end == mark.end)) {
4221 fillRect_Paint( 4288 fillRect_Paint(
4222 &d->paint, 4289 &d->paint,
4223 moved_Rect(run->visBounds, addY_I2(d->viewPos, -pos_SmoothScroll(&d->widget->scrollY))), 4290 moved_Rect(run->visBounds, addY_I2(d->viewPos, viewPos_DocumentWidget_(d->widget))),
4224 color); 4291 color);
4225 } 4292 }
4226 } 4293 }
@@ -4234,96 +4301,6 @@ static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4234 } 4301 }
4235} 4302}
4236 4303
4237static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2 visPos) {
4238 const iGmDocument *doc = d->widget->doc;
4239 const iChar icon = siteIcon_GmDocument(doc);
4240 iString str;
4241 init_String(&str);
4242 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
4243 if (icon) {
4244 appendChar_String(&str, icon);
4245 const iRect iconRect = visualBounds_Text(run->font, range_String(&str));
4246 drawRange_Text(
4247 run->font,
4248 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
4249 tmBannerIcon_ColorId,
4250 range_String(&str));
4251 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
4252 }
4253 drawRange_Text(run->font,
4254 bpos,
4255 tmBannerTitle_ColorId,
4256 bannerText_DocumentWidget_(d->widget));
4257 if (bannerType_GmDocument(doc) == certificateWarning_GmDocumentBanner) {
4258 const int domainHeight = lineHeight_Text(banner_FontId) * 2;
4259 iRect rect = { add_I2(visPos, init_I2(0, domainHeight)),
4260 addY_I2(run->visBounds.size, -domainHeight - lineHeight_Text(uiContent_FontId)) };
4261 format_String(&str, "${heading.certwarn}");
4262 const int certFlags = d->widget->certFlags;
4263 if (certFlags & timeVerified_GmCertFlag && certFlags & domainVerified_GmCertFlag) {
4264 iUrl parts;
4265 init_Url(&parts, d->widget->mod.url);
4266 const iTime oldUntil =
4267 domainValidUntil_GmCerts(certs_App(), parts.host, port_Url(&parts));
4268 iDate exp;
4269 init_Date(&exp, &oldUntil);
4270 iTime now;
4271 initCurrent_Time(&now);
4272 const int days = secondsSince_Time(&oldUntil, &now) / 3600 / 24;
4273 appendCStr_String(&str, "\n");
4274 if (days <= 30) {
4275 appendCStr_String(&str,
4276 format_CStr(cstrCount_Lang("dlg.certwarn.mayberenewed.n", days),
4277 cstrCollect_String(format_Date(&exp, "%Y-%m-%d")),
4278 days));
4279 }
4280 else {
4281 appendCStr_String(&str, cstr_Lang("dlg.certwarn.different"));
4282 }
4283 }
4284 else if (certFlags & domainVerified_GmCertFlag) {
4285 appendCStr_String(&str, "\n");
4286 appendFormat_String(&str, cstr_Lang("dlg.certwarn.expired"),
4287 cstrCollect_String(format_Date(&d->widget->certExpiry, "%Y-%m-%d")));
4288 }
4289 else if (certFlags & timeVerified_GmCertFlag) {
4290 appendCStr_String(&str, "\n");
4291 appendFormat_String(&str, cstr_Lang("dlg.certwarn.domain"),
4292 cstr_String(d->widget->certSubject));
4293 }
4294 else {
4295 appendCStr_String(&str, "\n");
4296 appendCStr_String(&str, cstr_Lang("dlg.certwarn.domain.expired"));
4297 }
4298 const iInt2 dims = measureWrapRange_Text(
4299 uiContent_FontId, width_Rect(rect) - 16 * gap_UI, range_String(&str)).bounds.size;
4300 const int warnHeight = run->visBounds.size.y - domainHeight;
4301 const int yOff = (lineHeight_Text(uiLabelLarge_FontId) -
4302 lineHeight_Text(uiContent_FontId)) / 2;
4303 const iRect bgRect =
4304 init_Rect(0, visPos.y + domainHeight, d->widgetBounds.size.x, warnHeight);
4305 fillRect_Paint(&d->paint, bgRect, orange_ColorId);
4306 if (!isDark_ColorTheme(colorTheme_App())) {
4307 drawHLine_Paint(&d->paint,
4308 topLeft_Rect(bgRect), width_Rect(bgRect), tmBannerTitle_ColorId);
4309 drawHLine_Paint(&d->paint,
4310 bottomLeft_Rect(bgRect), width_Rect(bgRect), tmBannerTitle_ColorId);
4311 }
4312 const int fg = black_ColorId;
4313 adjustEdges_Rect(&rect, warnHeight / 2 - dims.y / 2 - yOff, 0, 0, 0);
4314 bpos = topLeft_Rect(rect);
4315 draw_Text(uiLabelLarge_FontId, bpos, fg, "\u26a0");
4316 adjustEdges_Rect(&rect, 0, -8 * gap_UI, 0, 8 * gap_UI);
4317 translate_Lang(&str);
4318 drawWrapRange_Text(uiContent_FontId,
4319 addY_I2(topLeft_Rect(rect), yOff),
4320 width_Rect(rect),
4321 fg,
4322 range_String(&str));
4323 }
4324 deinit_String(&str);
4325}
4326
4327static void drawRun_DrawContext_(void *context, const iGmRun *run) { 4304static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4328 iDrawContext *d = context; 4305 iDrawContext *d = context;
4329 const iInt2 origin = d->viewPos; 4306 const iInt2 origin = d->viewPos;
@@ -4430,6 +4407,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4430 run->color, 4407 run->color,
4431 run->text); 4408 run->text);
4432 } 4409 }
4410#if 0
4433 else if (run->flags & siteBanner_GmRunFlag) { 4411 else if (run->flags & siteBanner_GmRunFlag) {
4434 /* Banner background. */ 4412 /* Banner background. */
4435 iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds), 4413 iRect bannerBack = initCorners_Rect(topLeft_Rect(d->widgetBounds),
@@ -4438,6 +4416,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4438 fillRect_Paint(&d->paint, bannerBack, tmBannerBackground_ColorId); 4416 fillRect_Paint(&d->paint, bannerBack, tmBannerBackground_ColorId);
4439 drawBannerRun_DrawContext_(d, run, visPos); 4417 drawBannerRun_DrawContext_(d, run, visPos);
4440 } 4418 }
4419#endif
4441 else { 4420 else {
4442 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 4421 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
4443 const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId); 4422 const size_t ord = visibleLinkOrdinal_DocumentWidget_(d->widget, run->linkId);
@@ -4663,8 +4642,8 @@ static void updateSideIconBuf_DocumentWidget_(const iDocumentWidget *d) {
4663 SDL_DestroyTexture(dbuf->sideIconBuf); 4642 SDL_DestroyTexture(dbuf->sideIconBuf);
4664 dbuf->sideIconBuf = NULL; 4643 dbuf->sideIconBuf = NULL;
4665 } 4644 }
4666 const iGmRun *banner = siteBanner_GmDocument(d->doc); 4645// const iGmRun *banner = siteBanner_GmDocument(d->doc);
4667 if (!banner) { 4646 if (isEmpty_Banner(d->banner)) {
4668 return; 4647 return;
4669 } 4648 }
4670 const int margin = gap_UI * d->pageMargin; 4649 const int margin = gap_UI * d->pageMargin;
@@ -4747,7 +4726,7 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4747 bottomLeft_Rect(bounds), 4726 bottomLeft_Rect(bounds),
4748 init_I2(margin, 4727 init_I2(margin,
4749 -margin + -dbuf->timestampBuf->size.y + 4728 -margin + -dbuf->timestampBuf->size.y +
4750 iMax(0, d->scrollY.max - pos_SmoothScroll(&d->scrollY)))), 4729 iMax(0, d->scrollY.max + viewPos_DocumentWidget_(d)))),
4751 tmQuoteIcon_ColorId); 4730 tmQuoteIcon_ColorId);
4752 } 4731 }
4753 unsetClip_Paint(&p); 4732 unsetClip_Paint(&p);
@@ -4988,7 +4967,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4988 }; 4967 };
4989 init_Paint(&ctx.paint); 4968 init_Paint(&ctx.paint);
4990 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 4969 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
4991 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); 4970 int yTop = docBounds.pos.y + viewPos_DocumentWidget_(d);
4992 const iBool isDocEmpty = size_GmDocument(d->doc).y == 0; 4971 const iBool isDocEmpty = size_GmDocument(d->doc).y == 0;
4993 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 4972 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
4994 if (!isDocEmpty) { 4973 if (!isDocEmpty) {
@@ -5025,12 +5004,22 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5025 } 5004 }
5026 } 5005 }
5027 drawMedia_DocumentWidget_(d, &ctx.paint); 5006 drawMedia_DocumentWidget_(d, &ctx.paint);
5028 /* Fill the top and bottom, in case the document is short. */ 5007 /* Fill the top and bottom, in case the document is short. */
5029 if (yTop > top_Rect(bounds)) { 5008 if (yTop > top_Rect(bounds)) {
5030 fillRect_Paint(&ctx.paint, 5009 fillRect_Paint(&ctx.paint,
5031 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) }, 5010 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) },
5032 hasSiteBanner_GmDocument(d->doc) ? tmBannerBackground_ColorId 5011 !isEmpty_Banner(d->banner) ? tmBannerBackground_ColorId
5033 : tmBackground_ColorId); 5012 : tmBackground_ColorId);
5013 }
5014 /* Banner. */ {
5015 /* Fill the part between the banner and the top of the document. */
5016 fillRect_Paint(&ctx.paint,
5017 (iRect){ init_I2(left_Rect(bounds),
5018 top_Rect(docBounds) + viewPos_DocumentWidget_(d) -
5019 documentTopPad_DocumentWidget_(d)),
5020 init_I2(bounds.size.x, documentTopPad_DocumentWidget_(d)) },
5021 tmBackground_ColorId);
5022 draw_Banner(d->banner);
5034 } 5023 }
5035 const int yBottom = yTop + size_GmDocument(d->doc).y + 1; 5024 const int yBottom = yTop + size_GmDocument(d->doc).y + 1;
5036 if (yBottom < bottom_Rect(bounds)) { 5025 if (yBottom < bottom_Rect(bounds)) {
@@ -5073,7 +5062,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
5073 const int altFont = uiLabel_FontId; 5062 const int altFont = uiLabel_FontId;
5074 const int wrap = docBounds.size.x - 2 * margin; 5063 const int wrap = docBounds.size.x - 2 * margin;
5075 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos), 5064 iInt2 pos = addY_I2(add_I2(docBounds.pos, meta->pixelRect.pos),
5076 -pos_SmoothScroll(&d->scrollY)); 5065 viewPos_DocumentWidget_(d));
5077 const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size; 5066 const iInt2 textSize = measureWrapRange_Text(altFont, wrap, meta->altText).bounds.size;
5078 pos.y -= textSize.y + gap_UI; 5067 pos.y -= textSize.y + gap_UI;
5079 pos.y = iMax(pos.y, top_Rect(bounds)); 5068 pos.y = iMax(pos.y, top_Rect(bounds));
@@ -5276,11 +5265,23 @@ void updateSize_DocumentWidget(iDocumentWidget *d) {
5276 resetWideRuns_DocumentWidget_(d); 5265 resetWideRuns_DocumentWidget_(d);
5277 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 5266 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
5278 updateVisible_DocumentWidget_(d); 5267 updateVisible_DocumentWidget_(d);
5268 setWidth_Banner(d->banner, documentWidth_DocumentWidget(d));
5279 invalidate_DocumentWidget_(d); 5269 invalidate_DocumentWidget_(d);
5280 arrange_Widget(d->footerButtons); 5270 arrange_Widget(d->footerButtons);
5281} 5271}
5282 5272
5273#if 0
5274static void sizeChanged_DocumentWidget_(iDocumentWidget *d) {
5275 if (current_Root()) {
5276 /* TODO: This gets called more than once during a single arrange.
5277 It could be done via some sort of callback instead. */
5278 updateVisible_DocumentWidget_(d);
5279 }
5280}
5281#endif
5282
5283iBeginDefineSubclass(DocumentWidget, Widget) 5283iBeginDefineSubclass(DocumentWidget, Widget)
5284 .processEvent = (iAny *) processEvent_DocumentWidget_, 5284 .processEvent = (iAny *) processEvent_DocumentWidget_,
5285 .draw = (iAny *) draw_DocumentWidget_, 5285 .draw = (iAny *) draw_DocumentWidget_,
5286// .sizeChanged = (iAny *) sizeChanged_DocumentWidget_,
5286iEndDefineSubclass(DocumentWidget) 5287iEndDefineSubclass(DocumentWidget)