summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c359
-rw-r--r--src/ui/paint.c24
-rw-r--r--src/ui/paint.h12
-rw-r--r--src/ui/sidebarwidget.c170
-rw-r--r--src/ui/text.c12
-rw-r--r--src/ui/text.h8
6 files changed, 401 insertions, 184 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 47d7f755..29803252 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()) {
@@ -943,16 +1001,18 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
943 return iTrue; 1001 return iTrue;
944 } 1002 }
945 else if (equal_Command(cmd, "document.input.submit")) { 1003 else if (equal_Command(cmd, "document.input.submit")) {
946 iString *value = collect_String(suffix_Command(cmd, "value")); 1004 if (arg_Command(cmd)) {
947 urlEncode_String(value); 1005 iString *value = collect_String(suffix_Command(cmd, "value"));
948 iString *url = collect_String(copy_String(d->mod.url)); 1006 urlEncode_String(value);
949 const size_t qPos = indexOfCStr_String(url, "?"); 1007 iString *url = collect_String(copy_String(d->mod.url));
950 if (qPos != iInvalidPos) { 1008 const size_t qPos = indexOfCStr_String(url, "?");
951 remove_Block(&url->chars, qPos, iInvalidSize); 1009 if (qPos != iInvalidPos) {
1010 remove_Block(&url->chars, qPos, iInvalidSize);
1011 }
1012 appendCStr_String(url, "?");
1013 append_String(url, value);
1014 postCommandf_App("open url:%s", cstr_String(url));
952 } 1015 }
953 appendCStr_String(url, "?");
954 append_String(url, value);
955 postCommandf_App("open url:%s", cstr_String(url));
956 return iTrue; 1016 return iTrue;
957 } 1017 }
958 else if (equal_Command(cmd, "valueinput.cancelled") && 1018 else if (equal_Command(cmd, "valueinput.cancelled") &&
@@ -968,7 +1028,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
968 else if (equalWidget_Command(cmd, w, "document.request.finished") && 1028 else if (equalWidget_Command(cmd, w, "document.request.finished") &&
969 pointerLabel_Command(cmd, "request") == d->request) { 1029 pointerLabel_Command(cmd, "request") == d->request) {
970 checkResponse_DocumentWidget_(d); 1030 checkResponse_DocumentWidget_(d);
971 d->scrollY = d->initialNormScrollY * size_GmDocument(d->doc).y; 1031 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y;
972 d->state = ready_RequestState; 1032 d->state = ready_RequestState;
973 /* The response may be cached. */ { 1033 /* The response may be cached. */ {
974 const iRangecc proto = urlProtocol_String(d->mod.url); 1034 const iRangecc proto = urlProtocol_String(d->mod.url);
@@ -1002,7 +1062,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1002 return handleMediaCommand_DocumentWidget_(d, cmd); 1062 return handleMediaCommand_DocumentWidget_(d, cmd);
1003 } 1063 }
1004 else if (equal_Command(cmd, "document.reload") && document_App() == d) { 1064 else if (equal_Command(cmd, "document.reload") && document_App() == d) {
1005 d->initialNormScrollY = normScrollPos_DocumentWidget_(d); 1065 d->initNormScrollY = normScrollPos_DocumentWidget_(d);
1006 fetch_DocumentWidget_(d); 1066 fetch_DocumentWidget_(d);
1007 return iTrue; 1067 return iTrue;
1008 } 1068 }
@@ -1100,6 +1160,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1100 case SDLK_RALT: 1160 case SDLK_RALT:
1101 if (document_App() == d) { 1161 if (document_App() == d) {
1102 d->showLinkNumbers = iFalse; 1162 d->showLinkNumbers = iFalse;
1163 invalidate_DocumentWidget_(d);
1103 refresh_Widget(w); 1164 refresh_Widget(w);
1104 } 1165 }
1105 break; 1166 break;
@@ -1127,21 +1188,10 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1127 case SDLK_RALT: 1188 case SDLK_RALT:
1128 if (document_App() == d) { 1189 if (document_App() == d) {
1129 d->showLinkNumbers = iTrue; 1190 d->showLinkNumbers = iTrue;
1191 invalidate_DocumentWidget_(d);
1130 refresh_Widget(w); 1192 refresh_Widget(w);
1131 } 1193 }
1132 break; 1194 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: 1195 case SDLK_HOME:
1146 d->scrollY = 0; 1196 d->scrollY = 0;
1147 scroll_DocumentWidget_(d, 0); 1197 scroll_DocumentWidget_(d, 0);
@@ -1264,6 +1314,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1264 d->hoverLink = NULL; 1314 d->hoverLink = NULL;
1265 scroll_DocumentWidget_(d, 0); 1315 scroll_DocumentWidget_(d, 0);
1266 updateVisible_DocumentWidget_(d); 1316 updateVisible_DocumentWidget_(d);
1317 invalidate_DocumentWidget_(d);
1267 refresh_Widget(w); 1318 refresh_Widget(w);
1268 return iTrue; 1319 return iTrue;
1269 } 1320 }
@@ -1274,6 +1325,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1274 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req), 1325 setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req),
1275 body_GmRequest(req->req)); 1326 body_GmRequest(req->req));
1276 updateVisible_DocumentWidget_(d); 1327 updateVisible_DocumentWidget_(d);
1328 invalidate_DocumentWidget_(d);
1277 refresh_Widget(w); 1329 refresh_Widget(w);
1278 return iTrue; 1330 return iTrue;
1279 } 1331 }
@@ -1305,7 +1357,13 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1305 1357
1306iDeclareType(DrawContext) 1358iDeclareType(DrawContext)
1307 1359
1360enum iDrawRunPass {
1361 static_DrawRunPass,
1362 dynamic_DrawRunPass,
1363};
1364
1308struct Impl_DrawContext { 1365struct Impl_DrawContext {
1366 enum iDrawRunPass pass;
1309 const iDocumentWidget *widget; 1367 const iDocumentWidget *widget;
1310 iRect widgetBounds; 1368 iRect widgetBounds;
1311 iRect bounds; /* document area */ 1369 iRect bounds; /* document area */
@@ -1348,57 +1406,67 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1348 iDrawContext *d = context; 1406 iDrawContext *d = context;
1349 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); 1407 const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY);
1350 if (run->imageId) { 1408 if (run->imageId) {
1351 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); 1409 if (d->pass == static_DrawRunPass) {
1352 if (tex) { 1410 SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId);
1353 const iRect dst = moved_Rect(run->visBounds, origin); 1411 if (tex) {
1354 SDL_RenderCopy(d->paint.dst->render, tex, NULL, 1412 const iRect dst = moved_Rect(run->visBounds, origin);
1355 &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); 1413 SDL_RenderCopy(d->paint.dst->render, tex, NULL,
1414 &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y });
1415 }
1356 } 1416 }
1357 return; 1417 return;
1358 } 1418 }
1419 /* Text markers. */
1420 if (d->pass == dynamic_DrawRunPass) {
1421 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
1422 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
1423 }
1359 enum iColorId fg = run->color; 1424 enum iColorId fg = run->color;
1360 const iGmDocument *doc = d->widget->doc; 1425 const iGmDocument *doc = d->widget->doc;
1426 /* Matches the current drawing pass? */
1427 const iBool isDynamic = (run->linkId && ~run->flags & decoration_GmRunFlag);
1428 if (isDynamic ^ (d->pass == dynamic_DrawRunPass)) {
1429 return;
1430 }
1361 const iBool isHover = 1431 const iBool isHover =
1362 (run->linkId != 0 && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId && 1432 (run->linkId && d->widget->hoverLink && run->linkId == d->widget->hoverLink->linkId &&
1363 !isEmpty_Rect(run->bounds)); 1433 ~run->flags & decoration_GmRunFlag);
1364 const iInt2 visPos = add_I2(run->visBounds.pos, origin); 1434 const iInt2 visPos = add_I2(run->visBounds.pos, origin);
1365 /* Text markers. */ 1435 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); 1436 fg = linkColor_GmDocument(doc, run->linkId, isHover ? textHover_GmLinkPart : text_GmLinkPart);
1371 if (linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) { 1437 if (linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) {
1372 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */ 1438 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); /* link is inactive */
1373 } 1439 }
1374 } 1440 }
1375 if (run->flags & siteBanner_GmRunFlag) { 1441 if (run->flags & siteBanner_GmRunFlag) {
1376 /* Draw the site banner. */ 1442 if (d->pass == static_DrawRunPass) {
1377 fillRect_Paint( 1443 /* Draw the site banner. */
1378 &d->paint, 1444 fillRect_Paint(
1379 initCorners_Rect(topLeft_Rect(d->widgetBounds), 1445 &d->paint,
1380 init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))), 1446 initCorners_Rect(topLeft_Rect(d->widgetBounds),
1381 visPos.y + height_Rect(run->visBounds))), 1447 init_I2(right_Rect(bounds_Widget(constAs_Widget(d->widget))),
1382 tmBannerBackground_ColorId); 1448 visPos.y + height_Rect(run->visBounds))),
1383 const iChar icon = siteIcon_GmDocument(doc); 1449 tmBannerBackground_ColorId);
1384 iString bannerText; 1450 const iChar icon = siteIcon_GmDocument(doc);
1385 init_String(&bannerText); 1451 iString bannerText;
1386 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); 1452 init_String(&bannerText);
1387 if (icon) { 1453 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
1388 appendChar_String(&bannerText, icon); 1454 if (icon) {
1389 const iRect iconRect = visualBounds_Text(banner_FontId, range_String(&bannerText)); 1455 appendChar_String(&bannerText, icon);
1456 const iRect iconRect = visualBounds_Text(banner_FontId, range_String(&bannerText));
1457 drawRange_Text(run->font,
1458 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
1459 tmBannerIcon_ColorId,
1460 range_String(&bannerText));
1461 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
1462 }
1390 drawRange_Text(run->font, 1463 drawRange_Text(run->font,
1391 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2), 1464 bpos,
1392 tmBannerIcon_ColorId, 1465 tmBannerTitle_ColorId,
1393 range_String(&bannerText)); 1466 isEmpty_String(d->widget->titleUser) ? run->text
1394 bpos.x += right_Rect(iconRect) + 3 * gap_Text; 1467 : range_String(d->widget->titleUser));
1468 deinit_String(&bannerText);
1395 } 1469 }
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 } 1470 }
1403 else { 1471 else {
1404 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 1472 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) {
@@ -1413,10 +1481,11 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1413 } 1481 }
1414 } 1482 }
1415 drawRange_Text(run->font, visPos, fg, run->text); 1483 drawRange_Text(run->font, visPos, fg, run->text);
1484// printf("{%s}\n", cstr_Rangecc(run->text));
1416 runDrawn:; 1485 runDrawn:;
1417 } 1486 }
1418 /* Presentation of links. */ 1487 /* Presentation of links. */
1419 if (run->linkId && ~run->flags & decoration_GmRunFlag) { 1488 if (run->linkId && ~run->flags & decoration_GmRunFlag && d->pass == dynamic_DrawRunPass) {
1420 const int metaFont = paragraph_FontId; 1489 const int metaFont = paragraph_FontId;
1421 /* TODO: Show status of an ongoing media request. */ 1490 /* TODO: Show status of an ongoing media request. */
1422 const int flags = linkFlags_GmDocument(doc, run->linkId); 1491 const int flags = linkFlags_GmDocument(doc, run->linkId);
@@ -1510,27 +1579,97 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1510 } 1579 }
1511 } 1580 }
1512 } 1581 }
1513
1514// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); 1582// drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId);
1515// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 1583// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
1516} 1584}
1517 1585
1586static iRangei intersect_Rangei_(iRangei a, iRangei b) {
1587 if (a.end < b.start || a.start > b.end) {
1588 return (iRangei){ 0, 0 };
1589 }
1590 return (iRangei){ iMax(a.start, b.start), iMin(a.end, b.end) };
1591}
1592
1593iLocalDef iBool isEmpty_Rangei_(iRangei d) {
1594 return size_Range(&d) == 0;
1595}
1596
1518static void draw_DocumentWidget_(const iDocumentWidget *d) { 1597static void draw_DocumentWidget_(const iDocumentWidget *d) {
1519 const iWidget *w = constAs_Widget(d); 1598 const iWidget *w = constAs_Widget(d);
1520 const iRect bounds = bounds_Widget(w); 1599 const iRect bounds = bounds_Widget(w);
1600 const iInt2 origin = topLeft_Rect(bounds);
1601 const iRangei visRange = visibleRange_DocumentWidget_(d);
1521 draw_Widget(w); 1602 draw_Widget(w);
1522 iDrawContext ctx = { 1603 allocVisBuffer_DocumentWidget_(iConstCast(iDocumentWidget *, d));
1523 .widget = d, 1604 iDrawContext ctxDynamic = {
1524 .widgetBounds = /* omit scrollbar width */ 1605 .pass = dynamic_DrawRunPass,
1525 adjusted_Rect(bounds, zero_I2(), init_I2(-constAs_Widget(d->scroll)->rect.size.x, 0)), 1606 .widget = d,
1526 .bounds = documentBounds_DocumentWidget_(d), 1607 .widgetBounds = adjusted_Rect(bounds,
1608 zero_I2(),
1609 init_I2(-constAs_Widget(d->scroll)->rect.size.x, 0)), /* omit scrollbar width */
1610 .bounds = documentBounds_DocumentWidget_(d),
1527 .showLinkNumbers = d->showLinkNumbers, 1611 .showLinkNumbers = d->showLinkNumbers,
1528 }; 1612 };
1529 init_Paint(&ctx.paint); 1613 iDrawContext ctxStatic = ctxDynamic;
1530 fillRect_Paint(&ctx.paint, bounds, tmBackground_ColorId); 1614 ctxStatic.pass = static_DrawRunPass;
1531 setClip_Paint(&ctx.paint, bounds); 1615 subv_I2(&ctxStatic.widgetBounds.pos, origin);
1532 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx); 1616 subv_I2(&ctxStatic.bounds.pos, origin);
1533 unsetClip_Paint(&ctx.paint); 1617 SDL_Renderer *render = get_Window()->render;
1618 /* Static content. */ {
1619 iPaint *p = &ctxStatic.paint;
1620 init_Paint(p);
1621 const int vbSrc = d->visBufferIndex;
1622 const int vbDst = d->visBufferIndex ^ 1;
1623 iRangei drawRange = visRange;
1624 iAssert(d->visBuffer[vbDst]);
1625 beginTarget_Paint(p, d->visBuffer[vbDst]);
1626 const iRect visBufferRect = { zero_I2(), d->visBufferSize };
1627 iRect drawRect = visBufferRect;
1628 if (!isEmpty_Rangei_(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, tmBackground_ColorId); // vbDst == 1 ? blue_ColorId : red_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))),
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 85e75f15..264ca0d8 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -1,5 +1,7 @@
1#include "paint.h" 1#include "paint.h"
2 2
3#include <SDL_version.h>
4
3iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { 5iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) {
4 iAssert(d->dst); 6 iAssert(d->dst);
5 return d->dst->render; 7 return d->dst->render;
@@ -12,6 +14,18 @@ static void setColor_Paint_(const iPaint *d, int color) {
12 14
13void init_Paint(iPaint *d) { 15void init_Paint(iPaint *d) {
14 d->dst = get_Window(); 16 d->dst = get_Window();
17 d->oldTarget = NULL;
18}
19
20void beginTarget_Paint(iPaint *d, SDL_Texture *target) {
21 SDL_Renderer *rend = renderer_Paint_(d);
22 d->oldTarget = SDL_GetRenderTarget(rend);
23 SDL_SetRenderTarget(rend, target);
24}
25
26void endTarget_Paint(iPaint *d) {
27 SDL_SetRenderTarget(renderer_Paint_(d), d->oldTarget);
28 d->oldTarget = NULL;
15} 29}
16 30
17void setClip_Paint(iPaint *d, iRect rect) { 31void setClip_Paint(iPaint *d, iRect rect) {
@@ -19,8 +33,12 @@ void setClip_Paint(iPaint *d, iRect rect) {
19} 33}
20 34
21void unsetClip_Paint(iPaint *d) { 35void unsetClip_Paint(iPaint *d) {
36#if SDL_VERSION_ATLEAST(2, 0, 12)
37 SDL_RenderSetClipRect(renderer_Paint_(d), NULL);
38#else
22 const SDL_Rect winRect = { 0, 0, d->dst->root->rect.size.x, d->dst->root->rect.size.y }; 39 const SDL_Rect winRect = { 0, 0, d->dst->root->rect.size.x, d->dst->root->rect.size.y };
23 SDL_RenderSetClipRect(renderer_Paint_(d), &winRect); 40 SDL_RenderSetClipRect(renderer_Paint_(d), &winRect);
41#endif
24} 42}
25 43
26void drawRect_Paint(const iPaint *d, iRect rect, int color) { 44void drawRect_Paint(const iPaint *d, iRect rect, int color) {
@@ -56,3 +74,9 @@ void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t count, int col
56 setColor_Paint_(d, color); 74 setColor_Paint_(d, color);
57 SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) points, count); 75 SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) points, count);
58} 76}
77
78iInt2 size_SDLTexture(SDL_Texture *d) {
79 iInt2 size;
80 SDL_QueryTexture(d, NULL, NULL, &size.x, &size.y);
81 return size;
82}
diff --git a/src/ui/paint.h b/src/ui/paint.h
index 90c5f504..5b29b176 100644
--- a/src/ui/paint.h
+++ b/src/ui/paint.h
@@ -9,12 +9,16 @@ iDeclareType(Paint)
9 9
10struct Impl_Paint { 10struct Impl_Paint {
11 iWindow *dst; 11 iWindow *dst;
12 SDL_Texture *oldTarget;
12}; 13};
13 14
14void init_Paint (iPaint *); 15void init_Paint (iPaint *);
15 16
16void setClip_Paint (iPaint *, iRect rect); 17void beginTarget_Paint (iPaint *, SDL_Texture *target);
17void unsetClip_Paint (iPaint *); 18void endTarget_Paint (iPaint *);
19
20void setClip_Paint (iPaint *, iRect rect);
21void unsetClip_Paint (iPaint *);
18 22
19void drawRect_Paint (const iPaint *, iRect rect, int color); 23void drawRect_Paint (const iPaint *, iRect rect, int color);
20void drawRectThickness_Paint (const iPaint *, iRect rect, int thickness, int color); 24void drawRectThickness_Paint (const iPaint *, iRect rect, int thickness, int color);
@@ -31,3 +35,5 @@ iLocalDef void drawHLine_Paint(const iPaint *d, iInt2 pos, int len, int color) {
31iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) { 35iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) {
32 drawLine_Paint(d, pos, addY_I2(pos, len), color); 36 drawLine_Paint(d, pos, addY_I2(pos, len), color);
33} 37}
38
39iInt2 size_SDLTexture (SDL_Texture *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index de576f72..71b641d4 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -59,10 +59,17 @@ struct Impl_SidebarWidget {
59 iWidget *resizer; 59 iWidget *resizer;
60 SDL_Cursor *resizeCursor; 60 SDL_Cursor *resizeCursor;
61 iWidget *menu; 61 iWidget *menu;
62 SDL_Texture *visBuffer;
63 iBool visBufferValid;
62}; 64};
63 65
64iDefineObjectConstruction(SidebarWidget) 66iDefineObjectConstruction(SidebarWidget)
65 67
68static void invalidate_SidebarWidget_(iSidebarWidget *d) {
69 d->visBufferValid = iFalse;
70 refresh_Widget(as_Widget(d));
71}
72
66static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { 73static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) {
67 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; 74 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0;
68} 75}
@@ -157,7 +164,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
157 break; 164 break;
158 } 165 }
159 updateVisible_SidebarWidget_(d); 166 updateVisible_SidebarWidget_(d);
160 refresh_Widget(as_Widget(d)); 167 invalidate_SidebarWidget_(d);
161} 168}
162 169
163void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { 170void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
@@ -166,8 +173,8 @@ void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
166 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) { 173 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
167 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode); 174 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
168 } 175 }
169 const float heights[max_SidebarMode] = { 1.5f, 3, 3, 1.2f }; 176 const float heights[max_SidebarMode] = { 1.333f, 3, 3, 1.2f };
170 d->itemHeight = heights[mode] * lineHeight_Text(default_FontId); 177 d->itemHeight = heights[mode] * lineHeight_Text(uiContent_FontId);
171} 178}
172 179
173enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { 180enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) {
@@ -217,7 +224,7 @@ void init_SidebarWidget(iSidebarWidget *d) {
217 frameless_WidgetFlag | expand_WidgetFlag); 224 frameless_WidgetFlag | expand_WidgetFlag);
218 d->maxButtonLabelWidth = 225 d->maxButtonLabelWidth =
219 iMaxi(d->maxButtonLabelWidth, 226 iMaxi(d->maxButtonLabelWidth,
220 3 * gap_UI + measure_Text(default_FontId, normalModeLabels_[i]).x); 227 3 * gap_UI + measure_Text(uiLabel_FontId, normalModeLabels_[i]).x);
221 } 228 }
222 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 229 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
223 setThumb_ScrollWidget(d->scroll, 0, 0); 230 setThumb_ScrollWidget(d->scroll, 0, 0);
@@ -232,12 +239,15 @@ void init_SidebarWidget(iSidebarWidget *d) {
232 setBackgroundColor_Widget(d->resizer, none_ColorId); 239 setBackgroundColor_Widget(d->resizer, none_ColorId);
233 d->resizeCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); 240 d->resizeCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
234 d->menu = NULL; 241 d->menu = NULL;
242 d->visBuffer = NULL;
243 d->visBufferValid = iFalse;
235} 244}
236 245
237void deinit_SidebarWidget(iSidebarWidget *d) { 246void deinit_SidebarWidget(iSidebarWidget *d) {
238 SDL_FreeCursor(d->resizeCursor); 247 SDL_FreeCursor(d->resizeCursor);
239 clearItems_SidebarWidget_(d); 248 clearItems_SidebarWidget_(d);
240 deinit_Array(&d->items); 249 deinit_Array(&d->items);
250 SDL_DestroyTexture(d->visBuffer);
241} 251}
242 252
243static int visCount_SidebarWidget_(const iSidebarWidget *d) { 253static int visCount_SidebarWidget_(const iSidebarWidget *d) {
@@ -277,14 +287,18 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) {
277} 287}
278 288
279static void scroll_SidebarWidget_(iSidebarWidget *d, int offset) { 289static void scroll_SidebarWidget_(iSidebarWidget *d, int offset) {
290 const int oldScroll = d->scrollY;
280 d->scrollY += offset; 291 d->scrollY += offset;
281 if (d->scrollY < 0) { 292 if (d->scrollY < 0) {
282 d->scrollY = 0; 293 d->scrollY = 0;
283 } 294 }
284 const int scrollMax = scrollMax_SidebarWidget_(d); 295 const int scrollMax = scrollMax_SidebarWidget_(d);
285 d->scrollY = iMin(d->scrollY, scrollMax); 296 d->scrollY = iMin(d->scrollY, scrollMax);
286 updateVisible_SidebarWidget_(d); 297 if (oldScroll != d->scrollY) {
287 refresh_Widget(as_Widget(d)); 298 d->hoverItem = iInvalidPos;
299 updateVisible_SidebarWidget_(d);
300 invalidate_SidebarWidget_(d);
301 }
288} 302}
289 303
290static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { 304static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
@@ -320,7 +334,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, int width) {
320 checkModeButtonLayout_SidebarWidget_(d); 334 checkModeButtonLayout_SidebarWidget_(d);
321 if (!isRefreshPending_App()) { 335 if (!isRefreshPending_App()) {
322 updateSize_DocumentWidget(document_App()); 336 updateSize_DocumentWidget(document_App());
323 refresh_Widget(w); 337 invalidate_SidebarWidget_(d);
324 } 338 }
325} 339}
326 340
@@ -349,6 +363,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
349 if (isResize_UserEvent(ev)) { 363 if (isResize_UserEvent(ev)) {
350 updateVisible_SidebarWidget_(d); 364 updateVisible_SidebarWidget_(d);
351 checkModeButtonLayout_SidebarWidget_(d); 365 checkModeButtonLayout_SidebarWidget_(d);
366 invalidate_SidebarWidget_(d);
352 } 367 }
353 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) { 368 else if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
354 const char *cmd = command_UserEvent(ev); 369 const char *cmd = command_UserEvent(ev);
@@ -392,6 +407,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
392 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); 407 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w));
393 if (isVisible_Widget(w)) { 408 if (isVisible_Widget(w)) {
394 w->rect.size.x = d->width; 409 w->rect.size.x = d->width;
410 invalidate_SidebarWidget_(d);
395 } 411 }
396 arrange_Widget(w->parent); 412 arrange_Widget(w->parent);
397 updateSize_DocumentWidget(document_App()); 413 updateSize_DocumentWidget(document_App());
@@ -401,13 +417,16 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
401 else if (equal_Command(cmd, "scroll.moved")) { 417 else if (equal_Command(cmd, "scroll.moved")) {
402 d->scrollY = arg_Command(command_UserEvent(ev)); 418 d->scrollY = arg_Command(command_UserEvent(ev));
403 d->hoverItem = iInvalidPos; 419 d->hoverItem = iInvalidPos;
404 refresh_Widget(w); 420 invalidate_SidebarWidget_(d);
405 return iTrue; 421 return iTrue;
406 } 422 }
407 else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) { 423 else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) {
408 d->scrollY = 0; 424 d->scrollY = 0;
409 updateItems_SidebarWidget_(d); 425 updateItems_SidebarWidget_(d);
410 } 426 }
427 else if (equal_Command(cmd, "theme.changed")) {
428 invalidate_SidebarWidget_(d);
429 }
411 else if (equal_Command(cmd, "bookmark.copy")) { 430 else if (equal_Command(cmd, "bookmark.copy")) {
412 const iSidebarItem *item = hoverItem_SidebarWidget_(d); 431 const iSidebarItem *item = hoverItem_SidebarWidget_(d);
413 if (d->mode == bookmarks_SidebarMode && item) { 432 if (d->mode == bookmarks_SidebarMode && item) {
@@ -454,7 +473,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
454 } 473 }
455 if (hover != d->hoverItem) { 474 if (hover != d->hoverItem) {
456 d->hoverItem = hover; 475 d->hoverItem = hover;
457 refresh_Widget(w); 476 invalidate_SidebarWidget_(d);
458 } 477 }
459 } 478 }
460 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 479 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
@@ -464,8 +483,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
464#else 483#else
465 scroll_SidebarWidget_(d, -ev->wheel.y * 3 * d->itemHeight); 484 scroll_SidebarWidget_(d, -ev->wheel.y * 3 * d->itemHeight);
466#endif 485#endif
467 d->hoverItem = iInvalidPos;
468 refresh_Widget(w);
469 return iTrue; 486 return iTrue;
470 } 487 }
471 if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { 488 if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) {
@@ -475,14 +492,14 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
475 } 492 }
476 switch (processEvent_Click(&d->click, ev)) { 493 switch (processEvent_Click(&d->click, ev)) {
477 case started_ClickResult: 494 case started_ClickResult:
478 refresh_Widget(w); 495 invalidate_SidebarWidget_(d);
479 break; 496 break;
480 case finished_ClickResult: 497 case finished_ClickResult:
481 if (contains_Rect(contentBounds_SidebarWidget_(d), pos_Click(&d->click)) && 498 if (contains_Rect(contentBounds_SidebarWidget_(d), pos_Click(&d->click)) &&
482 d->hoverItem != iInvalidSize) { 499 d->hoverItem != iInvalidSize) {
483 itemClicked_SidebarWidget_(d, d->hoverItem); 500 itemClicked_SidebarWidget_(d, d->hoverItem);
484 } 501 }
485 refresh_Widget(w); 502 invalidate_SidebarWidget_(d);
486 break; 503 break;
487 default: 504 default:
488 break; 505 break;
@@ -490,64 +507,93 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
490 return processEvent_Widget(w, ev); 507 return processEvent_Widget(w, ev);
491} 508}
492 509
510static void allocVisBuffer_SidebarWidget_(iSidebarWidget *d) {
511 const iInt2 size = contentBounds_SidebarWidget_(d).size;
512 if (!d->visBuffer || !isEqual_I2(size_SDLTexture(d->visBuffer), size)) {
513 if (d->visBuffer) {
514 SDL_DestroyTexture(d->visBuffer);
515 }
516 d->visBuffer = SDL_CreateTexture(renderer_Window(get_Window()),
517 SDL_PIXELFORMAT_RGBA8888,
518 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
519 size.x,
520 size.y);
521 SDL_SetTextureBlendMode(d->visBuffer, SDL_BLENDMODE_NONE);
522 d->visBufferValid = iFalse;
523 }
524}
525
493static void draw_SidebarWidget_(const iSidebarWidget *d) { 526static void draw_SidebarWidget_(const iSidebarWidget *d) {
494 const iWidget *w = constAs_Widget(d); 527 const iWidget *w = constAs_Widget(d);
495 const iRect bounds = contentBounds_SidebarWidget_(d); 528 const iRect bounds = contentBounds_SidebarWidget_(d);
496 const iBool isPressing = d->click.isActive && contains_Rect(bounds, pos_Click(&d->click)); 529 const iBool isPressing = d->click.isActive && contains_Rect(bounds, pos_Click(&d->click));
497 iPaint p; 530 iPaint p;
498 init_Paint(&p); 531 init_Paint(&p);
499 fillRect_Paint(&p, 532 const int bg =
500 bounds_Widget(w), 533 d->mode == documentOutline_SidebarMode ? tmBackground_ColorId : uiBackground_ColorId;
501 d->mode == documentOutline_SidebarMode ? tmBackground_ColorId 534 fillRect_Paint(&p, bounds_Widget(w), bg); /* TODO: should do only the mode buttons area */
502 : uiBackground_ColorId); 535 if (!d->visBufferValid) {
503 /* Draw the items. */ { 536 allocVisBuffer_SidebarWidget_(iConstCast(iSidebarWidget *, d));
504 const int font = default_FontId; 537 iRect bufBounds = bounds;
505 const iRanges visRange = visRange_SidebarWidget_(d); 538 bufBounds.pos = zero_I2();
506 iInt2 pos = addY_I2(topLeft_Rect(bounds), -(d->scrollY % d->itemHeight)); 539 beginTarget_Paint(&p, d->visBuffer);
507 for (size_t i = visRange.start; i < visRange.end; i++) { 540 fillRect_Paint(&p, bufBounds, bg);
508 const iSidebarItem *item = constAt_Array(&d->items, i); 541 /* Draw the items. */ {
509 const iRect itemRect = { pos, init_I2(width_Rect(bounds), d->itemHeight) }; 542 const int font = uiContent_FontId;
510 const iBool isHover = (d->hoverItem == i); 543 const iRanges visRange = visRange_SidebarWidget_(d);
511 setClip_Paint(&p, intersect_Rect(itemRect, bounds)); 544 iInt2 pos = addY_I2(topLeft_Rect(bufBounds), -(d->scrollY % d->itemHeight));
512 if (isHover) { 545 for (size_t i = visRange.start; i < visRange.end; i++) {
513 fillRect_Paint(&p, 546 const iSidebarItem *item = constAt_Array(&d->items, i);
514 itemRect, 547 const iRect itemRect = { pos, init_I2(width_Rect(bufBounds), d->itemHeight) };
515 isPressing ? uiBackgroundPressed_ColorId 548 const iBool isHover = (d->hoverItem == i);
516 : uiBackgroundFramelessHover_ColorId); 549 setClip_Paint(&p, intersect_Rect(itemRect, bufBounds));
517 } 550 if (isHover) {
518 if (d->mode == documentOutline_SidebarMode) { 551 fillRect_Paint(&p,
519 const int fg = 552 itemRect,
520 isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) 553 isPressing ? uiBackgroundPressed_ColorId
521 : (tmHeading1_ColorId + item->indent / (4 * gap_UI)); 554 : uiBackgroundFramelessHover_ColorId);
522 drawRange_Text(font, init_I2(pos.x + 3 * gap_UI + item->indent, 555 }
523 mid_Rect(itemRect).y - lineHeight_Text(font) / 2), 556 if (d->mode == documentOutline_SidebarMode) {
524 fg, range_String(&item->label)); 557 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId
525 } 558 : uiTextFramelessHover_ColorId)
526 else if (d->mode == bookmarks_SidebarMode) { 559 : (tmHeading1_ColorId + item->indent / (4 * gap_UI));
527 const int fg = 560 drawRange_Text(font,
528 isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) 561 init_I2(pos.x + 3 * gap_UI + item->indent,
529 : uiText_ColorId; 562 mid_Rect(itemRect).y - lineHeight_Text(font) / 2),
530 iString str; 563 fg,
531 init_String(&str); 564 range_String(&item->label));
532 appendChar_String(&str, item->icon ? item->icon : 0x1f588); 565 }
533 const iRect iconArea = { addX_I2(pos, gap_UI), init_I2(7 * gap_UI, d->itemHeight) }; 566 else if (d->mode == bookmarks_SidebarMode) {
534 drawCentered_Text(font, 567 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId
535 iconArea, 568 : uiTextFramelessHover_ColorId)
536 iTrue, 569 : uiText_ColorId;
537 isHover 570 iString str;
538 ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) 571 init_String(&str);
539 : uiIcon_ColorId, 572 appendChar_String(&str, item->icon ? item->icon : 0x1f588);
540 "%s", 573 const iRect iconArea = { addX_I2(pos, gap_UI),
541 cstr_String(&str)); 574 init_I2(7 * gap_UI, d->itemHeight) };
542 deinit_String(&str); 575 drawCentered_Text(
543 iInt2 textPos = 576 font,
544 addY_I2(topRight_Rect(iconArea), (d->itemHeight - lineHeight_Text(font)) / 2); 577 iconArea,
545 drawRange_Text(font, textPos, fg, range_String(&item->label)); 578 iTrue,
579 isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId)
580 : uiIcon_ColorId,
581 "%s",
582 cstr_String(&str));
583 deinit_String(&str);
584 iInt2 textPos = addY_I2(topRight_Rect(iconArea),
585 (d->itemHeight - lineHeight_Text(font)) / 2);
586 drawRange_Text(font, textPos, fg, range_String(&item->label));
587 }
588 unsetClip_Paint(&p);
589 pos.y += d->itemHeight;
546 } 590 }
547 unsetClip_Paint(&p);
548 pos.y += d->itemHeight;
549 } 591 }
592 endTarget_Paint(&p);
593 iConstCast(iSidebarWidget *, d)->visBufferValid = iTrue;
550 } 594 }
595 SDL_RenderCopy(
596 renderer_Window(get_Window()), d->visBuffer, NULL, (const SDL_Rect *) &bounds);
551 draw_Widget(w); 597 draw_Widget(w);
552 drawVLine_Paint(&p, 598 drawVLine_Paint(&p,
553 addX_I2(topRight_Rect(bounds_Widget(w)), -1), 599 addX_I2(topRight_Rect(bounds_Widget(w)), -1),
diff --git a/src/ui/text.c b/src/ui/text.c
index f947c4df..1e702eee 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -120,7 +120,7 @@ static void initFonts_Text_(iText *d) {
120 int symbolsFont; 120 int symbolsFont;
121 } fontData[max_FontId] = { 121 } fontData[max_FontId] = {
122 { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, 122 { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId },
123 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.666f, defaultLargeSymbols_FontId }, 123 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId },
124 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId }, 124 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId },
125 { &fontFiraSansRegular_Embedded, textSize, symbols_FontId }, 125 { &fontFiraSansRegular_Embedded, textSize, symbols_FontId },
126 { &fontFiraMonoRegular_Embedded, textSize * 0.866f, smallSymbols_FontId }, 126 { &fontFiraMonoRegular_Embedded, textSize * 0.866f, smallSymbols_FontId },
@@ -133,14 +133,14 @@ static void initFonts_Text_(iText *d) {
133 { &fontFiraSansBold_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 133 { &fontFiraSansBold_Embedded, textSize * 2.000f, hugeSymbols_FontId },
134 { &fontFiraSansLight_Embedded, textSize * 1.666f, largeSymbols_FontId }, 134 { &fontFiraSansLight_Embedded, textSize * 1.666f, largeSymbols_FontId },
135 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId }, 135 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId },
136 { &fontSymbola_Embedded, fontSize_UI * 1.666f, defaultLargeSymbols_FontId }, 136 { &fontSymbola_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId },
137 { &fontSymbola_Embedded, textSize, symbols_FontId }, 137 { &fontSymbola_Embedded, textSize, symbols_FontId },
138 { &fontSymbola_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 138 { &fontSymbola_Embedded, textSize * 1.333f, mediumSymbols_FontId },
139 { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId }, 139 { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId },
140 { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 140 { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId },
141 { &fontSymbola_Embedded, textSize * 0.866f, smallSymbols_FontId }, 141 { &fontSymbola_Embedded, textSize * 0.866f, smallSymbols_FontId },
142 { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, 142 { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId },
143 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.666f, defaultLargeSymbols_FontId }, 143 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.150f, defaultMediumSymbols_FontId },
144 { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId }, 144 { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId },
145 { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 145 { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId },
146 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId }, 146 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId },
@@ -306,10 +306,11 @@ static void cache_Font_(iFont *d, iGlyph *glyph, int hoff) {
306 } 306 }
307 /* Determine placement in the glyph cache texture, advancing in rows. */ 307 /* Determine placement in the glyph cache texture, advancing in rows. */
308 glRect->pos = assignCachePos_Text_(txt, glRect->size); 308 glRect->pos = assignCachePos_Text_(txt, glRect->size);
309 SDL_Texture *oldTarget = SDL_GetRenderTarget(render);
309 SDL_SetRenderTarget(render, txt->cache); 310 SDL_SetRenderTarget(render, txt->cache);
310 const SDL_Rect dstRect = sdlRect_(*glRect); 311 const SDL_Rect dstRect = sdlRect_(*glRect);
311 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect); 312 SDL_RenderCopy(render, tex, &(SDL_Rect){ 0, 0, dstRect.w, dstRect.h }, &dstRect);
312 SDL_SetRenderTarget(render, NULL); 313 SDL_SetRenderTarget(render, oldTarget);
313 if (tex) { 314 if (tex) {
314 SDL_DestroyTexture(tex); 315 SDL_DestroyTexture(tex);
315 iAssert(surface); 316 iAssert(surface);
@@ -670,9 +671,10 @@ void init_TextBuf(iTextBuf *d, int font, const char *text) {
670 d->size.x, 671 d->size.x,
671 d->size.y); 672 d->size.y);
672 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); 673 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);
674 SDL_Texture *oldTarget = SDL_GetRenderTarget(render);
673 SDL_SetRenderTarget(render, d->texture); 675 SDL_SetRenderTarget(render, d->texture);
674 draw_Text_(font, zero_I2(), white_ColorId, range_CStr(text)); 676 draw_Text_(font, zero_I2(), white_ColorId, range_CStr(text));
675 SDL_SetRenderTarget(render, NULL); 677 SDL_SetRenderTarget(render, oldTarget);
676} 678}
677 679
678void deinit_TextBuf(iTextBuf *d) { 680void deinit_TextBuf(iTextBuf *d) {
diff --git a/src/ui/text.h b/src/ui/text.h
index 85ab44e2..2d49a1a6 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -7,7 +7,7 @@
7 7
8enum iFontId { 8enum iFontId {
9 default_FontId, 9 default_FontId,
10 defaultLarge_FontId, 10 defaultMedium_FontId,
11 defaultMonospace_FontId, 11 defaultMonospace_FontId,
12 regular_FontId, 12 regular_FontId,
13 monospace_FontId, 13 monospace_FontId,
@@ -21,7 +21,7 @@ enum iFontId {
21 largeLight_FontId, 21 largeLight_FontId,
22 /* symbol fonts */ 22 /* symbol fonts */
23 defaultSymbols_FontId, 23 defaultSymbols_FontId,
24 defaultLargeSymbols_FontId, 24 defaultMediumSymbols_FontId,
25 symbols_FontId, 25 symbols_FontId,
26 mediumSymbols_FontId, 26 mediumSymbols_FontId,
27 largeSymbols_FontId, 27 largeSymbols_FontId,
@@ -29,7 +29,7 @@ enum iFontId {
29 smallSymbols_FontId, 29 smallSymbols_FontId,
30 /* emoji fonts */ 30 /* emoji fonts */
31 defaultEmoji_FontId, 31 defaultEmoji_FontId,
32 defaultLargeEmoji_FontId, 32 defaultMediumEmoji_FontId,
33 emoji_FontId, 33 emoji_FontId,
34 mediumEmoji_FontId, 34 mediumEmoji_FontId,
35 largeEmoji_FontId, 35 largeEmoji_FontId,
@@ -42,7 +42,7 @@ enum iFontId {
42 uiLabel_FontId = default_FontId, 42 uiLabel_FontId = default_FontId,
43 uiShortcuts_FontId = default_FontId, 43 uiShortcuts_FontId = default_FontId,
44 uiInput_FontId = defaultMonospace_FontId, 44 uiInput_FontId = defaultMonospace_FontId,
45 uiBookmarkIcon_FontId = defaultLarge_FontId, 45 uiContent_FontId = defaultMedium_FontId,
46 /* Document fonts: */ 46 /* Document fonts: */
47 paragraph_FontId = regular_FontId, 47 paragraph_FontId = regular_FontId,
48 firstParagraph_FontId = medium_FontId, 48 firstParagraph_FontId = medium_FontId,