summaryrefslogtreecommitdiff
path: root/src/ui/documentwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-18 10:53:49 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-18 10:53:49 +0300
commit115bbb5f85fd964029aa7d9076dc124bd4034065 (patch)
tree6083637de78b20ccd89023c314fc1e7f9dce9e9f /src/ui/documentwidget.c
parentef297dafa84154d8f28f014410ab742f7994c557 (diff)
DocumentWidget: Faster redraw of the document
Keep static content in a buffer. The marked ranges still need to be drawn in a better way.
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r--src/ui/documentwidget.c339
1 files changed, 239 insertions, 100 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 47d7f755..b5fdde93 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -19,6 +19,7 @@
19#include <the_Foundation/stringarray.h> 19#include <the_Foundation/stringarray.h>
20#include <SDL_clipboard.h> 20#include <SDL_clipboard.h>
21#include <SDL_timer.h> 21#include <SDL_timer.h>
22#include <SDL_render.h>
22#include <ctype.h> 23#include <ctype.h>
23 24
24iDeclareClass(MediaRequest) 25iDeclareClass(MediaRequest)
@@ -112,33 +113,37 @@ enum iRequestState {
112}; 113};
113 114
114struct Impl_DocumentWidget { 115struct Impl_DocumentWidget {
115 iWidget widget; 116 iWidget widget;
116 enum iRequestState state; 117 enum iRequestState state;
117 iModel mod; 118 iModel mod;
118 iString *titleUser; 119 iString * titleUser;
119 iGmRequest *request; 120 iGmRequest * request;
120 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ 121 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
121 iObjectList *media; 122 iObjectList * media;
122 iGmDocument *doc; 123 iGmDocument * doc;
123 int certFlags; 124 int certFlags;
124 iDate certExpiry; 125 iDate certExpiry;
125 iString * certSubject; 126 iString * certSubject;
126 iBool selecting; 127 iBool selecting;
127 iRangecc selectMark; 128 iRangecc selectMark;
128 iRangecc foundMark; 129 iRangecc foundMark;
129 int pageMargin; 130 int pageMargin;
130 iPtrArray visibleLinks; 131 iPtrArray visibleLinks;
131 const iGmRun *hoverLink; 132 const iGmRun * hoverLink;
132 iBool noHoverWhileScrolling; 133 iBool noHoverWhileScrolling;
133 iBool showLinkNumbers; 134 iBool showLinkNumbers;
134 iClick click; 135 iClick click;
135 float initialNormScrollY; 136 float initNormScrollY;
136 int scrollY; 137 int scrollY;
137 iScrollWidget *scroll; 138 iScrollWidget *scroll;
138 iWidget *menu; 139 iWidget * menu;
139 SDL_Cursor *arrowCursor; /* TODO: cursors belong in Window */ 140 SDL_Cursor * arrowCursor; /* TODO: cursors belong in Window */
140 SDL_Cursor *beamCursor; 141 SDL_Cursor * beamCursor;
141 SDL_Cursor *handCursor; 142 SDL_Cursor * handCursor;
143 SDL_Texture * visBuffer[2];
144 int visBufferIndex;
145 iInt2 visBufferSize;
146 iRangei visBufferValidRange;
142}; 147};
143 148
144iDefineObjectConstruction(DocumentWidget) 149iDefineObjectConstruction(DocumentWidget)
@@ -158,7 +163,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
158 d->isRequestUpdated = iFalse; 163 d->isRequestUpdated = iFalse;
159 d->media = new_ObjectList(); 164 d->media = new_ObjectList();
160 d->doc = new_GmDocument(); 165 d->doc = new_GmDocument();
161 d->initialNormScrollY = 0; 166 d->initNormScrollY = 0;
162 d->scrollY = 0; 167 d->scrollY = 0;
163 d->selecting = iFalse; 168 d->selecting = iFalse;
164 d->selectMark = iNullRange; 169 d->selectMark = iNullRange;
@@ -167,9 +172,13 @@ void init_DocumentWidget(iDocumentWidget *d) {
167 d->hoverLink = NULL; 172 d->hoverLink = NULL;
168 d->noHoverWhileScrolling = iFalse; 173 d->noHoverWhileScrolling = iFalse;
169 d->showLinkNumbers = iFalse; 174 d->showLinkNumbers = iFalse;
170 d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); 175 iZap(d->visBuffer);
171 d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); 176 d->visBufferIndex = 0;
172 d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); 177 d->visBufferSize = zero_I2();
178 iZap(d->visBufferValidRange);
179 d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
180 d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
181 d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
173 init_PtrArray(&d->visibleLinks); 182 init_PtrArray(&d->visibleLinks);
174 init_Click(&d->click, d, SDL_BUTTON_LEFT); 183 init_Click(&d->click, d, SDL_BUTTON_LEFT);
175 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 184 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
@@ -219,7 +228,7 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
219 rect.pos.y += margin; 228 rect.pos.y += margin;
220 rect.size.y -= margin; 229 rect.size.y -= margin;
221 } 230 }
222 iInt2 docSize = addY_I2(size_GmDocument(d->doc), 0 /*-lineHeight_Text(banner_FontId) * 2*/); 231 const iInt2 docSize = size_GmDocument(d->doc);
223 if (docSize.y < rect.size.y) { 232 if (docSize.y < rect.size.y) {
224 /* Center vertically if short. */ 233 /* Center vertically if short. */
225 int offset = (rect.size.y - docSize.y) / 2; 234 int offset = (rect.size.y - docSize.y) / 2;
@@ -229,6 +238,14 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
229 return rect; 238 return rect;
230} 239}
231 240
241iLocalDef int documentToWindowY_DocumentWidget_(const iDocumentWidget *d, int docY) {
242 return docY - d->scrollY + documentBounds_DocumentWidget_(d).pos.y;
243}
244
245iLocalDef int windowToDocumentY_DocumentWidget_(const iDocumentWidget *d, int localY) {
246 return localY + d->scrollY - documentBounds_DocumentWidget_(d).pos.y;
247}
248
232static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { 249static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) {
233 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY); 250 return addY_I2(sub_I2(pos, topLeft_Rect(documentBounds_DocumentWidget_(d))), d->scrollY);
234} 251}
@@ -252,9 +269,9 @@ static void requestFinished_DocumentWidget_(iAnyObject *obj) {
252} 269}
253 270
254static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { 271static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
255 const int margin = gap_UI * d->pageMargin; 272 const int margin = !hasSiteBanner_GmDocument(d->doc) ? gap_UI * d->pageMargin : 0;
256 return (iRangei){ d->scrollY - margin, 273 return (iRangei){ d->scrollY - margin,
257 d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) }; 274 d->scrollY + height_Rect(bounds_Widget(constAs_Widget(d))) - margin };
258} 275}
259 276
260static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) { 277static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) {
@@ -453,6 +470,10 @@ static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
453 } 470 }
454} 471}
455 472
473static void invalidate_DocumentWidget_(iDocumentWidget *d) {
474 iZap(d->visBufferValidRange);
475}
476
456static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) { 477static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) {
457 if (d->state == ready_RequestState) { 478 if (d->state == ready_RequestState) {
458 return; 479 return;
@@ -462,6 +483,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
462 const enum iGmStatusCode statusCode = response->statusCode; 483 const enum iGmStatusCode statusCode = response->statusCode;
463 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) { 484 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) {
464 iString str; 485 iString str;
486 invalidate_DocumentWidget_(d);
465 updateTheme_DocumentWidget_(d); 487 updateTheme_DocumentWidget_(d);
466 initBlock_String(&str, &response->body); 488 initBlock_String(&str, &response->body);
467 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { 489 if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) {
@@ -600,11 +622,11 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
600 if (recent && recent->cachedResponse) { 622 if (recent && recent->cachedResponse) {
601 const iGmResponse *resp = recent->cachedResponse; 623 const iGmResponse *resp = recent->cachedResponse;
602 d->state = fetching_RequestState; 624 d->state = fetching_RequestState;
603 d->initialNormScrollY = recent->normScrollY; 625 d->initNormScrollY = recent->normScrollY;
604 /* Use the cached response data. */ 626 /* Use the cached response data. */
605 updateTrust_DocumentWidget_(d, resp); 627 updateTrust_DocumentWidget_(d, resp);
606 updateDocument_DocumentWidget_(d, resp); 628 updateDocument_DocumentWidget_(d, resp);
607 d->scrollY = d->initialNormScrollY * size_GmDocument(d->doc).y; 629 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y;
608 d->state = ready_RequestState; 630 d->state = ready_RequestState;
609 updateVisible_DocumentWidget_(d); 631 updateVisible_DocumentWidget_(d);
610 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 632 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
@@ -640,7 +662,7 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo
640iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { 662iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) {
641 iDocumentWidget *d = new_DocumentWidget(); 663 iDocumentWidget *d = new_DocumentWidget();
642 delete_History(d->mod.history); 664 delete_History(d->mod.history);
643 d->initialNormScrollY = normScrollPos_DocumentWidget_(d); 665 d->initNormScrollY = normScrollPos_DocumentWidget_(d);
644 d->mod.history = copy_History(orig->mod.history); 666 d->mod.history = copy_History(orig->mod.history);
645 setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue); 667 setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue);
646 return d; 668 return d;
@@ -651,7 +673,7 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
651} 673}
652 674
653void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) { 675void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) {
654 d->initialNormScrollY = normScrollY; 676 d->initNormScrollY = normScrollY;
655} 677}
656 678
657iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { 679iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
@@ -695,7 +717,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
695 case categoryInput_GmStatusCode: { 717 case categoryInput_GmStatusCode: {
696 iUrl parts; 718 iUrl parts;
697 init_Url(&parts, d->mod.url); 719 init_Url(&parts, d->mod.url);
698 printf("%s\n", cstr_String(meta_GmRequest(d->request))); 720// printf("%s\n", cstr_String(meta_GmRequest(d->request)));
699 iWidget *dlg = makeValueInput_Widget( 721 iWidget *dlg = makeValueInput_Widget(
700 as_Widget(d), 722 as_Widget(d),
701 NULL, 723 NULL,
@@ -809,6 +831,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId)
809 d->media, 831 d->media,
810 iClob(new_MediaRequest( 832 iClob(new_MediaRequest(
811 d, linkId, absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId))))); 833 d, linkId, absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId)))));
834 invalidate_DocumentWidget_(d);
812 return iTrue; 835 return iTrue;
813 } 836 }
814 return iFalse; 837 return iFalse;
@@ -835,6 +858,7 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
835 setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), 858 setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req),
836 body_GmRequest(req->req)); 859 body_GmRequest(req->req));
837 updateVisible_DocumentWidget_(d); 860 updateVisible_DocumentWidget_(d);
861 invalidate_DocumentWidget_(d);
838 refresh_Widget(as_Widget(d)); 862 refresh_Widget(as_Widget(d));
839 } 863 }
840 } 864 }
@@ -848,6 +872,36 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
848 return iFalse; 872 return iFalse;
849} 873}
850 874
875static void deallocVisBuffer_DocumentWidget_(iDocumentWidget *d) {
876 d->visBufferSize = zero_I2();
877 iZap(d->visBufferValidRange);
878 iForIndices(i, d->visBuffer) {
879 SDL_DestroyTexture(d->visBuffer[i]);
880 d->visBuffer[i] = NULL;
881 }
882}
883
884static void allocVisBuffer_DocumentWidget_(iDocumentWidget *d) {
885 iWidget *w = as_Widget(d);
886 const iBool isVisible = isVisible_Widget(w);
887 const iInt2 size = bounds_Widget(w).size;
888 if (!isEqual_I2(size, d->visBufferSize) || !isVisible) {
889 deallocVisBuffer_DocumentWidget_(d);
890 }
891 if (isVisible && !d->visBuffer[0]) {
892 iZap(d->visBufferValidRange);
893 d->visBufferSize = size;
894 iForIndices(i, d->visBuffer) {
895 d->visBuffer[i] = SDL_CreateTexture(renderer_Window(get_Window()),
896 SDL_PIXELFORMAT_RGBA8888,
897 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
898 size.x,
899 size.y);
900 SDL_SetTextureBlendMode(d->visBuffer[i], SDL_BLENDMODE_NONE);
901 }
902 }
903}
904
851void updateSize_DocumentWidget(iDocumentWidget *d) { 905void updateSize_DocumentWidget(iDocumentWidget *d) {
852 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d)); 906 setWidth_GmDocument(d->doc, documentWidth_DocumentWidget_(d));
853 updateVisible_DocumentWidget_(d); 907 updateVisible_DocumentWidget_(d);
@@ -867,11 +921,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
867 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue); 921 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue);
868 } 922 }
869 } 923 }
924 invalidate_DocumentWidget_(d);
925 allocVisBuffer_DocumentWidget_(d);
870 refresh_Widget(w); 926 refresh_Widget(w);
871 updateWindowTitle_DocumentWidget_(d); 927 updateWindowTitle_DocumentWidget_(d);
872 } 928 }
873 else if (equal_Command(cmd, "theme.changed") && document_App() == d) { 929 else if (equal_Command(cmd, "theme.changed") && document_App() == d) {
874 updateTheme_DocumentWidget_(d); 930 updateTheme_DocumentWidget_(d);
931 invalidate_DocumentWidget_(d);
875 refresh_Widget(w); 932 refresh_Widget(w);
876 } 933 }
877 else if (equal_Command(cmd, "tabs.changed")) { 934 else if (equal_Command(cmd, "tabs.changed")) {
@@ -884,6 +941,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
884 updateVisible_DocumentWidget_(d); 941 updateVisible_DocumentWidget_(d);
885 } 942 }
886 updateWindowTitle_DocumentWidget_(d); 943 updateWindowTitle_DocumentWidget_(d);
944 allocVisBuffer_DocumentWidget_(d);
887 return iFalse; 945 return iFalse;
888 } 946 }
889 else if (equal_Command(cmd, "server.showcert") && d == document_App()) { 947 else if (equal_Command(cmd, "server.showcert") && d == document_App()) {
@@ -968,7 +1026,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
968 else if (equalWidget_Command(cmd, w, "document.request.finished") && 1026 else if (equalWidget_Command(cmd, w, "document.request.finished") &&
969 pointerLabel_Command(cmd, "request") == d->request) { 1027 pointerLabel_Command(cmd, "request") == d->request) {
970 checkResponse_DocumentWidget_(d); 1028 checkResponse_DocumentWidget_(d);
971 d->scrollY = d->initialNormScrollY * size_GmDocument(d->doc).y; 1029 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y;
972 d->state = ready_RequestState; 1030 d->state = ready_RequestState;
973 /* The response may be cached. */ { 1031 /* The response may be cached. */ {
974 const iRangecc proto = urlProtocol_String(d->mod.url); 1032 const iRangecc proto = urlProtocol_String(d->mod.url);
@@ -1002,7 +1060,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1002 return handleMediaCommand_DocumentWidget_(d, cmd); 1060 return handleMediaCommand_DocumentWidget_(d, cmd);
1003 } 1061 }
1004 else if (equal_Command(cmd, "document.reload") && document_App() == d) { 1062 else if (equal_Command(cmd, "document.reload") && document_App() == d) {
1005 d->initialNormScrollY = normScrollPos_DocumentWidget_(d); 1063 d->initNormScrollY = normScrollPos_DocumentWidget_(d);
1006 fetch_DocumentWidget_(d); 1064 fetch_DocumentWidget_(d);
1007 return iTrue; 1065 return iTrue;
1008 } 1066 }
@@ -1100,6 +1158,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1100 case SDLK_RALT: 1158 case SDLK_RALT:
1101 if (document_App() == d) { 1159 if (document_App() == d) {
1102 d->showLinkNumbers = iFalse; 1160 d->showLinkNumbers = iFalse;
1161 invalidate_DocumentWidget_(d);
1103 refresh_Widget(w); 1162 refresh_Widget(w);
1104 } 1163 }
1105 break; 1164 break;
@@ -1127,21 +1186,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1127 case SDLK_RALT: 1186 case SDLK_RALT:
1128 if (document_App() == d) { 1187 if (document_App() == d) {
1129 d->showLinkNumbers = iTrue; 1188 d->showLinkNumbers = iTrue;
1189 invalidate_DocumentWidget_(d);
1130 refresh_Widget(w); 1190 refresh_Widget(w);
1131 } 1191 }
1132 break; 1192 break;
1133 case SDLK_1:
1134 case SDLK_2:
1135 case SDLK_3:
1136 case SDLK_4:
1137 case SDLK_5:
1138 case SDLK_6:
1139 case SDLK_7:
1140 case SDLK_8:
1141 case SDLK_9:
1142 if (mods == KMOD_ALT || mods == (KMOD_ALT | KMOD_PRIMARY)) {
1143 }
1144 break;
1145 case SDLK_HOME: 1193 case SDLK_HOME:
1146 d->scrollY = 0; 1194 d->scrollY = 0;
1147 scroll_DocumentWidget_(d, 0); 1195 scroll_DocumentWidget_(d, 0);
@@ -1264,6 +1312,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1264 d->hoverLink = NULL; 1312 d->hoverLink = NULL;
1265 scroll_DocumentWidget_(d, 0); 1313 scroll_DocumentWidget_(d, 0);
1266 updateVisible_DocumentWidget_(d); 1314 updateVisible_DocumentWidget_(d);
1315 invalidate_DocumentWidget_(d);
1267 refresh_Widget(w); 1316 refresh_Widget(w);
1268 return iTrue; 1317 return iTrue;
1269 } 1318 }
@@ -1274,6 +1323,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1274 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req), 1323 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req),
1275 body_GmRequest(req->req)); 1324 body_GmRequest(req->req));
1276 updateVisible_DocumentWidget_(d); 1325 updateVisible_DocumentWidget_(d);
1326 invalidate_DocumentWidget_(d);
1277 refresh_Widget(w); 1327 refresh_Widget(w);
1278 return iTrue; 1328 return iTrue;
1279 } 1329 }
@@ -1305,7 +1355,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1305 1355
1306iDeclareType(DrawContext) 1356iDeclareType(DrawContext)
1307 1357
1358enum iDrawRunPass {
1359 static_DrawRunPass,
1360 dynamic_DrawRunPass,
1361};
1362
1308struct Impl_DrawContext { 1363struct Impl_DrawContext {
1364 enum iDrawRunPass pass;
1309 const iDocumentWidget *widget; 1365 const iDocumentWidget *widget;
1310 iRect widgetBounds; 1366 iRect widgetBounds;
1311 iRect bounds; /* document area */ 1367 iRect bounds; /* document area */
@@ -1348,57 +1404,67 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1348 iDrawContext *d = context; 1404 iDrawContext *d = context;
1349 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); 1405 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY);
1350 if (run->imageId) { 1406 if (run->imageId) {
1351 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); 1407 if (d->pass == static_DrawRunPass) {
1352 if (tex) { 1408 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId);
1353 const iRect dst = moved_Rect(run->visBounds, origin); 1409 if (tex) {
1354 SDL_RenderCopy(d->paint.dst->render, tex, NULL, 1410 const iRect dst = moved_Rect(run->visBounds, origin);
1355 &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); 1411 SDL_RenderCopy(d->paint.dst->render, tex, NULL,
1412 &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y });
1413 }
1356 } 1414 }
1357 return; 1415 return;
1358 } 1416 }
1417 /* Text markers. */
1418 if (d->pass == dynamic_DrawRunPass) {
1419 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
1420 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
1421 }
1359 enum iColorId fg = run->color; 1422 enum iColorId fg = run->color;
1360 const iGmDocument *doc = d->widget->doc; 1423 const iGmDocument *doc = d->widget->doc;
1424 /* Matches the current drawing pass? */
1425 const iBool isDynamic = (run->linkId && ~run->flags & decoration_GmRunFlag);
1426 if (isDynamic ^ (d->pass == dynamic_DrawRunPass)) {
1427 return;
1428 }
1361 const iBool isHover = 1429 const iBool isHover =
1362 (run->linkId != 0 && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && 1430 (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId &&
1363 !isEmpty_Rect(run->bounds)); 1431 ~run->flags & decoration_GmRunFlag);
1364 const iInt2 visPos = add_I2(run->visBounds.pos, origin); 1432 const iInt2 visPos = add_I2(run->visBounds.pos, origin);
1365 /* Text markers. */ 1433 if (run->linkId && ~run->flags & decoration_GmRunFlag) {
1366 /* TODO: Add themed palette entries */
1367 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
1368 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
1369 if (run->linkId && !isEmpty_Rect(run->bounds)) {
1370 fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart); 1434 fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart);
1371 if (linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) { 1435 if (linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) {
1372 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ 1436 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */
1373 } 1437 }
1374 } 1438 }
1375 if (run->flags & siteBanner_GmRunFlag) { 1439 if (run->flags & siteBanner_GmRunFlag) {
1376 /* Draw the site banner. */ 1440 if (d->pass == static_DrawRunPass) {
1377 fillRect_Paint( 1441 /* Draw the site banner. */
1378 &d->paint, 1442 fillRect_Paint(
1379 initCorners_Rect(topLeft_Rect(d->widgetBounds), 1443 &d->paint,
1380 init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), 1444 initCorners_Rect(topLeft_Rect(d->widgetBounds),
1381 visPos.y + height_Rect(run->visBounds))), 1445 init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))),
1382 tmBannerBackground_ColorId); 1446 visPos.y + height_Rect(run->visBounds))),
1383 const iChar icon = siteIcon_GmDocument(doc); 1447 tmBannerBackground_ColorId);
1384 iString bannerText; 1448 const iChar icon = siteIcon_GmDocument(doc);
1385 init_String(&bannerText); 1449 iString bannerText;
1386 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); 1450 init_String(&bannerText);
1387 if (icon) { 1451 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
1388 appendChar_String(&bannerText, icon); 1452 if (icon) {
1389 const iRect iconRect = visualBounds_Text(banner_FontId, range_String(&bannerText)); 1453 appendChar_String(&bannerText, icon);
1454 const iRect iconRect = visualBounds_Text(banner_FontId, range_String(&bannerText));
1455 drawRange_Text(run->font,
1456 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
1457 tmBannerIcon_ColorId,
1458 range_String(&bannerText));
1459 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
1460 }
1390 drawRange_Text(run->font, 1461 drawRange_Text(run->font,
1391 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2), 1462 bpos,
1392 tmBannerIcon_ColorId, 1463 tmBannerTitle_ColorId,
1393 range_String(&bannerText)); 1464 isEmpty_String(d->widget->titleUser) ? run->text
1394 bpos.x += right_Rect(iconRect) + 3 * gap_Text; 1465 : range_String(d->widget->titleUser));
1466 deinit_String(&bannerText);
1395 } 1467 }
1396 drawRange_Text(run->font,
1397 bpos,
1398 tmBannerTitle_ColorId,
1399 isEmpty_String(d->widget->titleUser) ? run->text
1400 : range_String(d->widget->titleUser));
1401 deinit_String(&bannerText);
1402 } 1468 }
1403 else { 1469 else {
1404 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 1470 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
@@ -1413,10 +1479,11 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1413 } 1479 }
1414 } 1480 }
1415 drawRange_Text(run->font, visPos, fg, run->text); 1481 drawRange_Text(run->font, visPos, fg, run->text);
1482// printf("{%s}\n", cstr_Rangecc(run->text));
1416 runDrawn:; 1483 runDrawn:;
1417 } 1484 }
1418 /* Presentation of links. */ 1485 /* Presentation of links. */
1419 if (run->linkId && ~run->flags & decoration_GmRunFlag) { 1486 if (run->linkId && ~run->flags & decoration_GmRunFlag && d->pass == dynamic_DrawRunPass) {
1420 const int metaFont = paragraph_FontId; 1487 const int metaFont = paragraph_FontId;
1421 /* TODO: Show status of an ongoing media request. */ 1488 /* TODO: Show status of an ongoing media request. */
1422 const int flags = linkFlags_GmDocument(doc, run->linkId); 1489 const int flags = linkFlags_GmDocument(doc, run->linkId);
@@ -1515,22 +1582,94 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1515// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 1582// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
1516} 1583}
1517 1584
1585static iRangei intersect_Rangei_(iRangei a, iRangei b) {
1586 if (a.end < b.start || a.start > b.end) {
1587 return (iRangei){ 0, 0 };
1588 }
1589 return (iRangei){ iMax(a.start, b.start), iMin(a.end, b.end) };
1590}
1591
1592iLocalDef iBool isEmpty_Rangei_(iRangei d) {
1593 return size_Range(&d) == 0;
1594}
1595
1518static void draw_DocumentWidget_(const iDocumentWidget *d) { 1596static void draw_DocumentWidget_(const iDocumentWidget *d) {
1519 const iWidget *w = constAs_Widget(d); 1597 const iWidget *w = constAs_Widget(d);
1520 const iRect bounds = bounds_Widget(w); 1598 const iRect bounds = bounds_Widget(w);
1599 const iInt2 origin = topLeft_Rect(bounds);
1600 const iRangei visRange = visibleRange_DocumentWidget_(d);
1521 draw_Widget(w); 1601 draw_Widget(w);
1522 iDrawContext ctx = { 1602 allocVisBuffer_DocumentWidget_(iConstCast(iDocumentWidget *, d));
1523 .widget = d, 1603 iDrawContext ctxDynamic = {
1524 .widgetBounds = /* omit scrollbar width */ 1604 .pass = dynamic_DrawRunPass,
1525 adjusted_Rect(bounds, zero_I2(), init_I2(-constAs_Widget(d->scroll)->rect.size.x, 0)), 1605 .widget = d,
1526 .bounds = documentBounds_DocumentWidget_(d), 1606 .widgetBounds = adjusted_Rect(bounds,
1607 zero_I2(),
1608 init_I2(-constAs_Widget(d->scroll)->rect.size.x, 0)), /* omit scrollbar width */
1609 .bounds = documentBounds_DocumentWidget_(d),
1527 .showLinkNumbers = d->showLinkNumbers, 1610 .showLinkNumbers = d->showLinkNumbers,
1528 }; 1611 };
1529 init_Paint(&ctx.paint); 1612 iDrawContext ctxStatic = ctxDynamic;
1530 fillRect_Paint(&ctx.paint, bounds, tmBackground_ColorId); 1613 ctxStatic.pass = static_DrawRunPass;
1531 setClip_Paint(&ctx.paint, bounds); 1614 subv_I2(&ctxStatic.widgetBounds.pos, origin);
1532 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx); 1615 subv_I2(&ctxStatic.bounds.pos, origin);
1533 unsetClip_Paint(&ctx.paint); 1616 SDL_Renderer *render = get_Window()->render;
1617 /* Static content. */ {
1618 iPaint *p = &ctxStatic.paint;
1619 init_Paint(p);
1620 const int vbSrc = d->visBufferIndex;
1621 const int vbDst = d->visBufferIndex ^ 1;
1622 iRangei drawRange = visRange;
1623 iAssert(d->visBuffer[vbDst]);
1624 beginTarget_Paint(p, d->visBuffer[vbDst]);
1625 const iRect visBufferRect = { zero_I2(), d->visBufferSize };
1626 iRect drawRect = visBufferRect;
1627 if (!isEmpty_Rangei_(intersect_Rangei_(visRange, d->visBufferValidRange))) {
1628 const iRangei isct = intersect_Rangei_(visRange, d->visBufferValidRange);
1629 if (visRange.start < d->visBufferValidRange.start) {
1630 drawRange = (iRangei){ visRange.start, d->visBufferValidRange.start };
1631 }
1632 else {
1633 drawRange = (iRangei){ d->visBufferValidRange.end, visRange.end };
1634 }
1635 if (isEmpty_Range(&drawRange)) {
1636 SDL_RenderCopy(render, d->visBuffer[vbSrc], NULL, NULL);
1637 }
1638 else {
1639 SDL_RenderCopy(
1640 render,
1641 d->visBuffer[vbSrc],
1642 NULL,
1643 &(SDL_Rect){ 0,
1644 documentToWindowY_DocumentWidget_(d, d->visBufferValidRange.start) - origin.y,
1645 d->visBufferSize.x,
1646 d->visBufferSize.y });
1647 drawRect = init_Rect(0,
1648 documentToWindowY_DocumentWidget_(d, drawRange.start) - origin.y,
1649 d->visBufferSize.x,
1650 size_Range(&drawRange));
1651 }
1652 }
1653 if (!isEmpty_Range(&drawRange)) {
1654 setClip_Paint(p, drawRect);
1655 fillRect_Paint(p, drawRect, vbDst == 1 ? blue_ColorId : red_ColorId); //tmBackground_ColorId);
1656 render_GmDocument(d->doc, drawRange, drawRun_DrawContext_, &ctxStatic);
1657 unsetClip_Paint(p);
1658 }
1659 endTarget_Paint(p);
1660 SDL_RenderCopy(render, d->visBuffer[vbDst], NULL,
1661 &(SDL_Rect){ origin.x, origin.y, bounds.size.x, bounds.size.y } );
1662 iConstCast(iDocumentWidget *, d)->visBufferValidRange = visRange;
1663 iConstCast(iDocumentWidget *, d)->visBufferIndex = vbDst;
1664 }
1665 /* Dynamic content. */ {
1666 iPaint *p = &ctxDynamic.paint;
1667 init_Paint(p);
1668 setClip_Paint(p, bounds);
1669 render_GmDocument(d->doc, visRange, drawRun_DrawContext_, &ctxDynamic);
1670 unsetClip_Paint(p);
1671 }
1672
1534// drawRect_Paint(&ctx.paint, 1673// drawRect_Paint(&ctx.paint,
1535// moved_Rect((iRect){ zero_I2(), size_GmDocument(d->doc) }, 1674// moved_Rect((iRect){ zero_I2(), size_GmDocument(d->doc) },
1536// add_I2(topLeft_Rect(ctx.bounds), init_I2(0, -d->scrollY))), 1675// add_I2(topLeft_Rect(ctx.bounds), init_I2(0, -d->scrollY))),