summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-04-14 16:19:42 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-04-15 10:23:06 +0300
commitfefbd1c259c912fe126a5f34245a8b4c494cb753 (patch)
tree4f1add012361fc9c8430ee43987562252d123bb9
parentd3b4292242dd938503a31ba68066d53b29f364a4 (diff)
Progressive document rendering
VisBuf now guarantees that all the buffers are sequentially ordered. This ensure complete utilization of all the buffers. Previously some buffers were not used at all, or allocated to the same origin as another one (!). DocumentWidget is able to progressively fill up a buffer while scrolling. After any change to the visible region has been detected (and there are no ongoing changes), prerender the remaining runs one at a time. This solves the issue where one text run would be always unnecessarily rendered while scrolling, even if the scroll distance was just one pixel.
-rw-r--r--src/app.c2
-rw-r--r--src/gmdocument.c24
-rw-r--r--src/gmdocument.h6
-rw-r--r--src/periodic.c13
-rw-r--r--src/periodic.h6
-rw-r--r--src/ui/documentwidget.c276
-rw-r--r--src/ui/visbuf.c149
-rw-r--r--src/ui/visbuf.h10
8 files changed, 369 insertions, 117 deletions
diff --git a/src/app.c b/src/app.c
index 11c79bcb..bc15183a 100644
--- a/src/app.c
+++ b/src/app.c
@@ -906,7 +906,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
906 iApp *d = &app_; 906 iApp *d = &app_;
907 SDL_Event ev; 907 SDL_Event ev;
908 iBool gotEvents = iFalse; 908 iBool gotEvents = iFalse;
909 postCommands_Periodic(&d->periodic); 909 dispatchCommands_Periodic(&d->periodic);
910 while (nextEvent_App_(d, eventMode, &ev)) { 910 while (nextEvent_App_(d, eventMode, &ev)) {
911#if defined (iPlatformAppleMobile) 911#if defined (iPlatformAppleMobile)
912 if (processEvent_iOS(&ev)) { 912 if (processEvent_iOS(&ev)) {
diff --git a/src/gmdocument.c b/src/gmdocument.c
index e756a9d9..1e05f438 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -1494,6 +1494,30 @@ void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRende
1494 } 1494 }
1495} 1495}
1496 1496
1497static iBool isValidRun_GmDocument_(const iGmDocument *d, const iGmRun *run) {
1498 return run >= (const iGmRun *) constAt_Array(&d->layout, 0) &&
1499 run < (const iGmRun *) constEnd_Array(&d->layout);
1500}
1501
1502const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir,
1503 size_t maxCount,
1504 iRangei visRangeY, iGmDocumentRenderFunc render,
1505 void *context) {
1506 const iGmRun *run = first;
1507 while (isValidRun_GmDocument_(d, run)) {
1508 if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) ||
1509 (dir > 0 && top_Rect(run->visBounds) >= visRangeY.end)) {
1510 break;
1511 }
1512 if (maxCount-- == 0) {
1513 break;
1514 }
1515 render(context, run);
1516 run += dir;
1517 }
1518 return isValidRun_GmDocument_(d, run) ? run : NULL;
1519}
1520
1497iInt2 size_GmDocument(const iGmDocument *d) { 1521iInt2 size_GmDocument(const iGmDocument *d) {
1498 return d->size; 1522 return d->size;
1499} 1523}
diff --git a/src/gmdocument.h b/src/gmdocument.h
index ce148164..5d34dbaf 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -176,7 +176,11 @@ iMedia * media_GmDocument (iGmDocument *);
176const iMedia * constMedia_GmDocument (const iGmDocument *); 176const iMedia * constMedia_GmDocument (const iGmDocument *);
177 177
178void render_GmDocument (const iGmDocument *, iRangei visRangeY, 178void render_GmDocument (const iGmDocument *, iRangei visRangeY,
179 iGmDocumentRenderFunc render, void *); 179 iGmDocumentRenderFunc render, void *); /* includes partial overlaps */
180const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *first, int dir,
181 size_t maxCount,
182 iRangei visRangeY, iGmDocumentRenderFunc render,
183 void *context);
180iInt2 size_GmDocument (const iGmDocument *); 184iInt2 size_GmDocument (const iGmDocument *);
181const iGmRun * siteBanner_GmDocument (const iGmDocument *); 185const iGmRun * siteBanner_GmDocument (const iGmDocument *);
182iBool hasSiteBanner_GmDocument (const iGmDocument *); 186iBool hasSiteBanner_GmDocument (const iGmDocument *);
diff --git a/src/periodic.c b/src/periodic.c
index 29c26de9..9e9dfdba 100644
--- a/src/periodic.c
+++ b/src/periodic.c
@@ -21,10 +21,12 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "periodic.h" 23#include "periodic.h"
24#include "ui/widget.h"
24#include "app.h" 25#include "app.h"
25 26
26#include <the_Foundation/string.h> 27#include <the_Foundation/string.h>
27#include <the_Foundation/thread.h> 28#include <the_Foundation/thread.h>
29#include <SDL_events.h>
28#include <SDL_timer.h> 30#include <SDL_timer.h>
29 31
30iDeclareType(PeriodicCommand) 32iDeclareType(PeriodicCommand)
@@ -54,7 +56,7 @@ iDefineTypeConstructionArgs(PeriodicCommand, (iAny *ctx, const char *cmd), ctx,
54 56
55static const uint32_t postingInterval_Periodic_ = 500; 57static const uint32_t postingInterval_Periodic_ = 500;
56 58
57iBool postCommands_Periodic(iPeriodic *d) { 59iBool dispatchCommands_Periodic(iPeriodic *d) {
58 const uint32_t now = SDL_GetTicks(); 60 const uint32_t now = SDL_GetTicks();
59 if (now - d->lastPostTime < postingInterval_Periodic_) { 61 if (now - d->lastPostTime < postingInterval_Periodic_) {
60 return iFalse; 62 return iFalse;
@@ -63,7 +65,14 @@ iBool postCommands_Periodic(iPeriodic *d) {
63 iBool wasPosted = iFalse; 65 iBool wasPosted = iFalse;
64 lock_Mutex(d->mutex); 66 lock_Mutex(d->mutex);
65 iConstForEach(Array, i, &d->commands.values) { 67 iConstForEach(Array, i, &d->commands.values) {
66 postCommandString_App(&((const iPeriodicCommand *) i.value)->command); 68 const iPeriodicCommand *pc = i.value;
69 const SDL_UserEvent ev = {
70 .type = SDL_USEREVENT,
71 .code = command_UserEventCode,
72 .data1 = (void *) cstr_String(&pc->command)
73 };
74 iAssert(isInstance_Object(pc->context, &Class_Widget));
75 dispatchEvent_Widget(pc->context, (const SDL_Event *) &ev);
67 wasPosted = iTrue; 76 wasPosted = iTrue;
68 } 77 }
69 unlock_Mutex(d->mutex); 78 unlock_Mutex(d->mutex);
diff --git a/src/periodic.h b/src/periodic.h
index db90b848..8886617b 100644
--- a/src/periodic.h
+++ b/src/periodic.h
@@ -40,7 +40,7 @@ iLocalDef iBool isEmpty_Periodic(const iPeriodic *d) {
40 return isEmpty_SortedArray(&d->commands); 40 return isEmpty_SortedArray(&d->commands);
41} 41}
42 42
43void add_Periodic (iPeriodic *, iAny *context, const char *command); 43void add_Periodic (iPeriodic *, iAnyObject *context, const char *command);
44void remove_Periodic (iPeriodic *, iAny *context); 44void remove_Periodic (iPeriodic *, iAnyObject *context);
45 45
46iBool postCommands_Periodic (iPeriodic *); 46iBool dispatchCommands_Periodic( iPeriodic *);
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 70499ed5..ddca557b 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -20,7 +20,7 @@ ANY 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 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. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23/* TODO: This file is a little too large. DocumentWidget could be split into 23/* TODO: This file is a little (!) too large. DocumentWidget could be split into
24 a couple of smaller objects. One for rendering the document, for instance. */ 24 a couple of smaller objects. One for rendering the document, for instance. */
25 25
26#include "documentwidget.h" 26#include "documentwidget.h"
@@ -41,6 +41,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
41#include "labelwidget.h" 41#include "labelwidget.h"
42#include "media.h" 42#include "media.h"
43#include "paint.h" 43#include "paint.h"
44#include "periodic.h"
44#include "mediaui.h" 45#include "mediaui.h"
45#include "scrollwidget.h" 46#include "scrollwidget.h"
46#include "touch.h" 47#include "touch.h"
@@ -156,12 +157,14 @@ struct Impl_DrawBufs {
156 int flags; 157 int flags;
157 SDL_Texture * sideIconBuf; 158 SDL_Texture * sideIconBuf;
158 iTextBuf * timestampBuf; 159 iTextBuf * timestampBuf;
160 iRangei lastVis;
159}; 161};
160 162
161static void init_DrawBufs(iDrawBufs *d) { 163static void init_DrawBufs(iDrawBufs *d) {
162 d->flags = 0; 164 d->flags = 0;
163 d->sideIconBuf = NULL; 165 d->sideIconBuf = NULL;
164 d->timestampBuf = NULL; 166 d->timestampBuf = NULL;
167 iZap(d->lastVis);
165} 168}
166 169
167static void deinit_DrawBufs(iDrawBufs *d) { 170static void deinit_DrawBufs(iDrawBufs *d) {
@@ -175,9 +178,23 @@ iDefineTypeConstruction(DrawBufs)
175 178
176/*----------------------------------------------------------------------------------------------*/ 179/*----------------------------------------------------------------------------------------------*/
177 180
181iDeclareType(VisBufMeta)
182
183struct Impl_VisBufMeta {
184 iGmRunRange runsDrawn;
185};
186
187static void visBufInvalidated_(iVisBuf *d, size_t index) {
188 iVisBufMeta *meta = d->buffers[index].user;
189 iZap(meta->runsDrawn);
190}
191
192/*----------------------------------------------------------------------------------------------*/
193
178static void animate_DocumentWidget_ (void *ticker); 194static void animate_DocumentWidget_ (void *ticker);
179static void animateMedia_DocumentWidget_ (iDocumentWidget *d); 195static void animateMedia_DocumentWidget_ (iDocumentWidget *d);
180static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d); 196static void updateSideIconBuf_DocumentWidget_ (const iDocumentWidget *d);
197static void prerender_DocumentWidget_ (iAny *);
181 198
182static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */ 199static const int smoothDuration_DocumentWidget_ = 600; /* milliseconds */
183static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ 200static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */
@@ -251,8 +268,8 @@ struct Impl_DocumentWidget {
251 const iGmRun * hoverAltPre; /* for drawing alt text */ 268 const iGmRun * hoverAltPre; /* for drawing alt text */
252 const iGmRun * hoverLink; 269 const iGmRun * hoverLink;
253 const iGmRun * contextLink; 270 const iGmRun * contextLink;
254 const iGmRun * firstVisibleRun; 271 iGmRunRange visibleRuns;
255 const iGmRun * lastVisibleRun; 272 iGmRunRange renderRuns;
256 iClick click; 273 iClick click;
257 iInt2 contextPos; /* coordinates of latest right click */ 274 iInt2 contextPos; /* coordinates of latest right click */
258 iString pendingGotoHeading; 275 iString pendingGotoHeading;
@@ -265,6 +282,7 @@ struct Impl_DocumentWidget {
265 iWidget * playerMenu; 282 iWidget * playerMenu;
266 iWidget * copyMenu; 283 iWidget * copyMenu;
267 iVisBuf * visBuf; 284 iVisBuf * visBuf;
285 iGmRunRange * visBufMeta;
268 iPtrSet * invalidRuns; 286 iPtrSet * invalidRuns;
269 iDrawBufs * drawBufs; /* dynamic state for drawing */ 287 iDrawBufs * drawBufs; /* dynamic state for drawing */
270 iTranslation * translation; 288 iTranslation * translation;
@@ -306,10 +324,17 @@ void init_DocumentWidget(iDocumentWidget *d) {
306 d->hoverAltPre = NULL; 324 d->hoverAltPre = NULL;
307 d->hoverLink = NULL; 325 d->hoverLink = NULL;
308 d->contextLink = NULL; 326 d->contextLink = NULL;
309 d->firstVisibleRun = NULL; 327 iZap(d->renderRuns);
310 d->lastVisibleRun = NULL; 328 iZap(d->visibleRuns);
311 d->visBuf = new_VisBuf(); 329 d->visBuf = new_VisBuf(); {
312 d->invalidRuns = new_PtrSet(); 330 d->visBufMeta = malloc(sizeof(iVisBufMeta) * numBuffers_VisBuf);
331 /* Additional metadata for each buffer. */
332 d->visBuf->bufferInvalidated = visBufInvalidated_;
333 for (size_t i = 0; i < numBuffers_VisBuf; i++) {
334 d->visBuf->buffers[i].user = d->visBufMeta + i;
335 }
336 }
337 d->invalidRuns = new_PtrSet();
313 init_Anim(&d->sideOpacity, 0); 338 init_Anim(&d->sideOpacity, 0);
314 init_Anim(&d->altTextOpacity, 0); 339 init_Anim(&d->altTextOpacity, 0);
315 d->sourceStatus = none_GmStatusCode; 340 d->sourceStatus = none_GmStatusCode;
@@ -349,9 +374,12 @@ void init_DocumentWidget(iDocumentWidget *d) {
349 374
350void deinit_DocumentWidget(iDocumentWidget *d) { 375void deinit_DocumentWidget(iDocumentWidget *d) {
351 removeTicker_App(animate_DocumentWidget_, d); 376 removeTicker_App(animate_DocumentWidget_, d);
377 removeTicker_App(prerender_DocumentWidget_, d);
378 remove_Periodic(periodic_App(), d);
352 delete_Translation(d->translation); 379 delete_Translation(d->translation);
353 delete_DrawBufs(d->drawBufs); 380 delete_DrawBufs(d->drawBufs);
354 delete_VisBuf(d->visBuf); 381 delete_VisBuf(d->visBuf);
382 free(d->visBufMeta);
355 delete_PtrSet(d->invalidRuns); 383 delete_PtrSet(d->invalidRuns);
356 iRelease(d->media); 384 iRelease(d->media);
357 iRelease(d->request); 385 iRelease(d->request);
@@ -486,10 +514,10 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
486static void addVisible_DocumentWidget_(void *context, const iGmRun *run) { 514static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
487 iDocumentWidget *d = context; 515 iDocumentWidget *d = context;
488 if (~run->flags & decoration_GmRunFlag && !run->mediaId) { 516 if (~run->flags & decoration_GmRunFlag && !run->mediaId) {
489 if (!d->firstVisibleRun) { 517 if (!d->visibleRuns.start) {
490 d->firstVisibleRun = run; 518 d->visibleRuns.start = run;
491 } 519 }
492 d->lastVisibleRun = run; 520 d->visibleRuns.end = run;
493 } 521 }
494 if (run->preId) { 522 if (run->preId) {
495 pushBack_PtrArray(&d->visiblePre, run); 523 pushBack_PtrArray(&d->visiblePre, run);
@@ -743,14 +771,14 @@ static void animateMedia_DocumentWidget_(iDocumentWidget *d) {
743 771
744static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { 772static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) {
745 iRangecc heading = iNullRange; 773 iRangecc heading = iNullRange;
746 if (d->firstVisibleRun) { 774 if (d->visibleRuns.start) {
747 iConstForEach(Array, i, headings_GmDocument(d->doc)) { 775 iConstForEach(Array, i, headings_GmDocument(d->doc)) {
748 const iGmHeading *head = i.value; 776 const iGmHeading *head = i.value;
749 if (head->level == 0) { 777 if (head->level == 0) {
750 if (head->text.start <= d->firstVisibleRun->text.start) { 778 if (head->text.start <= d->visibleRuns.start->text.start) {
751 heading = head->text; 779 heading = head->text;
752 } 780 }
753 if (d->lastVisibleRun && head->text.start > d->lastVisibleRun->text.start) { 781 if (d->visibleRuns.end && head->text.start > d->visibleRuns.end->text.start) {
754 break; 782 break;
755 } 783 }
756 } 784 }
@@ -766,6 +794,14 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
766 !isSuccess_GmStatusCode(d->sourceStatus)); 794 !isSuccess_GmStatusCode(d->sourceStatus));
767 const iRangei visRange = visibleRange_DocumentWidget_(d); 795 const iRangei visRange = visibleRange_DocumentWidget_(d);
768 const iRect bounds = bounds_Widget(as_Widget(d)); 796 const iRect bounds = bounds_Widget(as_Widget(d));
797 if (!equal_Rangei(visRange, d->drawBufs->lastVis)) {
798 /* After scrolling/resizing stops, begin prendering the visbuf contents.
799 `Periodic` is used for detecting when the visible range has been modified. */
800 removeTicker_App(prerender_DocumentWidget_, d);
801 remove_Periodic(periodic_App(), d);
802 add_Periodic(periodic_App(), d,
803 format_CStr("document.render from:%d to:%d", visRange.start, visRange.end));
804 }
769 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) }); 805 setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_DocumentWidget_(d) });
770 const int docSize = size_GmDocument(d->doc).y; 806 const int docSize = size_GmDocument(d->doc).y;
771 setThumb_ScrollWidget(d->scroll, 807 setThumb_ScrollWidget(d->scroll,
@@ -777,7 +813,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
777 clear_PtrArray(&d->visibleMedia); 813 clear_PtrArray(&d->visibleMedia);
778 const iRangecc oldHeading = currentHeading_DocumentWidget_(d); 814 const iRangecc oldHeading = currentHeading_DocumentWidget_(d);
779 /* Scan for visible runs. */ { 815 /* Scan for visible runs. */ {
780 d->firstVisibleRun = NULL; 816 iZap(d->visibleRuns);
781 render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d); 817 render_GmDocument(d->doc, visRange, addVisible_DocumentWidget_, d);
782 } 818 }
783 const iRangecc newHeading = currentHeading_DocumentWidget_(d); 819 const iRangecc newHeading = currentHeading_DocumentWidget_(d);
@@ -898,8 +934,8 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) {
898 d->hoverAltPre = NULL; 934 d->hoverAltPre = NULL;
899 d->hoverLink = NULL; 935 d->hoverLink = NULL;
900 d->contextLink = NULL; 936 d->contextLink = NULL;
901 d->firstVisibleRun = NULL; 937 iZap(d->visibleRuns);
902 d->lastVisibleRun = NULL; 938 iZap(d->renderRuns);
903} 939}
904 940
905void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 941void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
@@ -1675,7 +1711,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen
1675 } 1711 }
1676 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top 1712 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top
1677 of the visible area fixed. */ 1713 of the visible area fixed. */
1678 const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->firstVisibleRun; 1714 const iGmRun *run = keepCenter ? middleRun_DocumentWidget_(d) : d->visibleRuns.start;
1679 const char * runLoc = (run ? run->text.start : NULL); 1715 const char * runLoc = (run ? run->text.start : NULL);
1680 int voffset = 0; 1716 int voffset = 0;
1681 if (!keepCenter && run) { 1717 if (!keepCenter && run) {
@@ -1727,7 +1763,17 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
1727 1763
1728static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 1764static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
1729 iWidget *w = as_Widget(d); 1765 iWidget *w = as_Widget(d);
1730 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { 1766 if (equal_Command(cmd, "document.render")) {
1767 const iRangei vis = { argLabel_Command(cmd, "from"), argLabel_Command(cmd, "to") };
1768 const iRangei current = visibleRange_DocumentWidget_(d);
1769 if (equal_Rangei(vis, current)) {
1770 remove_Periodic(periodic_App(), d);
1771 /* Scrolling has stopped, begin filling up the buffer. */
1772 addTicker_App(prerender_DocumentWidget_, d);
1773 }
1774 return iTrue;
1775 }
1776 else if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) {
1731 /* Alt/Option key may be involved in window size changes. */ 1777 /* Alt/Option key may be involved in window size changes. */
1732 setLinkNumberMode_DocumentWidget_(d, iFalse); 1778 setLinkNumberMode_DocumentWidget_(d, iFalse);
1733 d->phoneToolbar = findWidget_App("toolbar"); 1779 d->phoneToolbar = findWidget_App("toolbar");
@@ -3103,6 +3149,8 @@ iDeclareType(DrawContext)
3103struct Impl_DrawContext { 3149struct Impl_DrawContext {
3104 const iDocumentWidget *widget; 3150 const iDocumentWidget *widget;
3105 iRect widgetBounds; 3151 iRect widgetBounds;
3152 iRect docBounds;
3153 iRangei vis;
3106 iInt2 viewPos; /* document area origin */ 3154 iInt2 viewPos; /* document area origin */
3107 iPaint paint; 3155 iPaint paint;
3108 iBool inSelectMark; 3156 iBool inSelectMark;
@@ -3110,6 +3158,7 @@ struct Impl_DrawContext {
3110 iBool showLinkNumbers; 3158 iBool showLinkNumbers;
3111 iRect firstMarkRect; 3159 iRect firstMarkRect;
3112 iRect lastMarkRect; 3160 iRect lastMarkRect;
3161 iGmRunRange runsDrawn;
3113}; 3162};
3114 3163
3115static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, 3164static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color,
@@ -3269,6 +3318,14 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
3269static void drawRun_DrawContext_(void *context, const iGmRun *run) { 3318static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3270 iDrawContext *d = context; 3319 iDrawContext *d = context;
3271 const iInt2 origin = d->viewPos; 3320 const iInt2 origin = d->viewPos;
3321 /* Keep track of the drawn visible runs. */ {
3322 if (!d->runsDrawn.start || run < d->runsDrawn.start) {
3323 d->runsDrawn.start = run;
3324 }
3325 if (!d->runsDrawn.end || run > d->runsDrawn.end) {
3326 d->runsDrawn.end = run;
3327 }
3328 }
3272 if (run->mediaType == image_GmRunMediaType) { 3329 if (run->mediaType == image_GmRunMediaType) {
3273 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); 3330 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId);
3274 const iRect dst = moved_Rect(run->visBounds, origin); 3331 const iRect dst = moved_Rect(run->visBounds, origin);
@@ -3291,6 +3348,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3291 /* Media UIs are drawn afterwards as a dynamic overlay. */ 3348 /* Media UIs are drawn afterwards as a dynamic overlay. */
3292 return; 3349 return;
3293 } 3350 }
3351// printf(" drawRun: {%s}\n", cstr_Rangecc(run->text));
3294 enum iColorId fg = run->color; 3352 enum iColorId fg = run->color;
3295 const iGmDocument *doc = d->widget->doc; 3353 const iGmDocument *doc = d->widget->doc;
3296 iBool isHover = 3354 iBool isHover =
@@ -3644,48 +3702,100 @@ static void drawPin_(iPaint *p, iRect rangeRect, int dir) {
3644 init1_I2(gap_UI * 2)), pinColor); 3702 init1_I2(gap_UI * 2)), pinColor);
3645} 3703}
3646 3704
3647static void draw_DocumentWidget_(const iDocumentWidget *d) { 3705static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, iBool prerenderExtra) {
3648 const iWidget *w = constAs_Widget(d); 3706 iBool didDraw = iFalse;
3649 const iRect bounds = bounds_Widget(w); 3707 const iRect bounds = bounds_Widget(constAs_Widget(d));
3650 iVisBuf * visBuf = d->visBuf; /* will be updated now */
3651 if (width_Rect(bounds) <= 0) {
3652 return;
3653 }
3654 draw_Widget(w);
3655 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) {
3656 updateTimestampBuf_DocumentWidget_(d);
3657 }
3658 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) {
3659 updateSideIconBuf_DocumentWidget_(d);
3660 }
3661 allocVisBuffer_DocumentWidget_(d);
3662 const iRect ctxWidgetBounds = init_Rect( 3708 const iRect ctxWidgetBounds = init_Rect(
3663 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds)); 3709 0, 0, width_Rect(bounds) - constAs_Widget(d->scroll)->rect.size.x, height_Rect(bounds));
3664 const iRect docBounds = documentBounds_DocumentWidget_(d);
3665 iDrawContext ctx = {
3666 .widget = d,
3667 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0,
3668 };
3669 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
3670 /* Currently visible region. */
3671 const iRangei vis = visibleRange_DocumentWidget_(d);
3672 const iRangei full = { 0, size_GmDocument(d->doc).y }; 3710 const iRangei full = { 0, size_GmDocument(d->doc).y };
3711 const iRangei vis = ctx->vis;
3712 iVisBuf *visBuf = d->visBuf; /* will be updated now */
3713 /* Swap buffers around to have room available both before and after the visible region. */
3714 allocVisBuffer_DocumentWidget_(d);
3673 reposition_VisBuf(visBuf, vis); 3715 reposition_VisBuf(visBuf, vis);
3674 iRangei invalidRange[iElemCount(d->visBuf->buffers)];
3675 invalidRanges_VisBuf(visBuf, full, invalidRange);
3676 /* Redraw the invalid ranges. */ { 3716 /* Redraw the invalid ranges. */ {
3677 iPaint *p = &ctx.paint; 3717 iPaint *p = &ctx->paint;
3678 init_Paint(p); 3718 init_Paint(p);
3679 iForIndices(i, visBuf->buffers) { 3719 iForIndices(i, visBuf->buffers) {
3680 iVisBufTexture *buf = &visBuf->buffers[i]; 3720 iVisBufTexture *buf = &visBuf->buffers[i];
3681 ctx.widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin)); 3721 iVisBufMeta * meta = buf->user;
3682 ctx.viewPos = init_I2(left_Rect(docBounds) - left_Rect(bounds), -buf->origin); 3722 const iRangei bufRange = bufferRange_VisBuf(visBuf, i);
3683 if (!isEmpty_Rangei(invalidRange[i])) { 3723 const iRangei bufVisRange = intersect_Rangei(bufRange, vis);
3684 beginTarget_Paint(p, buf->texture); 3724 ctx->widgetBounds = moved_Rect(ctxWidgetBounds, init_I2(0, -buf->origin));
3725 ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin);
3726 //printf(" buffer %zu: invalid range %d...%d\n", i, invalidRange[i].start, invalidRange[i].end);
3727 if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) {
3728 didDraw = iTrue;
3685 if (isEmpty_Rangei(buf->validRange)) { 3729 if (isEmpty_Rangei(buf->validRange)) {
3686 fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId); 3730 /* Fill the required currently visible range (vis). */
3731 const iRangei bufVisRange = intersect_Rangei(bufRange, vis);
3732 if (!isEmpty_Range(&bufVisRange)) {
3733 beginTarget_Paint(p, buf->texture);
3734 fillRect_Paint(p, (iRect){ zero_I2(), visBuf->texSize }, tmBackground_ColorId);
3735 iZap(ctx->runsDrawn);
3736 render_GmDocument(d->doc, bufVisRange, drawRun_DrawContext_, ctx);
3737 meta->runsDrawn = ctx->runsDrawn;
3738 meta->runsDrawn.start--;
3739 meta->runsDrawn.end++;
3740 buf->validRange = bufVisRange;
3741 // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end);
3742 }
3743 }
3744 else {
3745 /* Progressively fill the required runs. */
3746 if (meta->runsDrawn.start) {
3747 beginTarget_Paint(p, buf->texture);
3748 meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start,
3749 -1, iInvalidSize,
3750 bufVisRange,
3751 drawRun_DrawContext_,
3752 ctx);
3753 buf->validRange.start = bufVisRange.start;
3754 }
3755 if (meta->runsDrawn.end) {
3756 beginTarget_Paint(p, buf->texture);
3757 meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end,
3758 +1, iInvalidSize,
3759 bufVisRange,
3760 drawRun_DrawContext_,
3761 ctx);
3762 buf->validRange.end = bufVisRange.end;
3763 }
3764 }
3765 }
3766 /* Progressively draw the rest of the buffer if it isn't fully valid. */
3767 if (prerenderExtra && !equal_Rangei(bufRange, buf->validRange)) {
3768 const iGmRun *next;
3769 if (meta->runsDrawn.start) {
3770 const iRangei upper = intersect_Rangei(bufRange, (iRangei){ full.start, buf->validRange.start });
3771 if (upper.end > upper.start) {
3772 beginTarget_Paint(p, buf->texture);
3773 next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start,
3774 -1, 1, upper,
3775 drawRun_DrawContext_,
3776 ctx);
3777 if (meta->runsDrawn.start != next) {
3778 meta->runsDrawn.start = next;
3779 didDraw = iTrue;
3780 }
3781 buf->validRange.start = bufRange.start;
3782 }
3783 }
3784 if (meta->runsDrawn.end) {
3785 const iRangei lower = intersect_Rangei(bufRange, (iRangei){ buf->validRange.end, full.end });
3786 if (lower.end > lower.start) {
3787 beginTarget_Paint(p, buf->texture);
3788 next = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end,
3789 +1, 1, lower,
3790 drawRun_DrawContext_,
3791 ctx);
3792 if (meta->runsDrawn.end != next) {
3793 meta->runsDrawn.end = next;
3794 didDraw = iTrue;
3795 }
3796 buf->validRange.end = bufRange.end;
3797 }
3687 } 3798 }
3688 render_GmDocument(d->doc, invalidRange[i], drawRun_DrawContext_, &ctx);
3689 } 3799 }
3690 /* Draw any invalidated runs that fall within this buffer. */ { 3800 /* Draw any invalidated runs that fall within this buffer. */ {
3691 const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y }; 3801 const iRangei bufRange = { buf->origin, buf->origin + visBuf->texSize.y };
@@ -3694,7 +3804,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
3694 const iGmRun *run = *r.value; 3804 const iGmRun *run = *r.value;
3695 if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { 3805 if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) {
3696 beginTarget_Paint(p, buf->texture); 3806 beginTarget_Paint(p, buf->texture);
3697 fillRect_Paint(&ctx.paint, 3807 fillRect_Paint(p,
3698 init_Rect(0, 3808 init_Rect(0,
3699 run->visBounds.pos.y - buf->origin, 3809 run->visBounds.pos.y - buf->origin,
3700 visBuf->texSize.x, 3810 visBuf->texSize.x,
@@ -3707,21 +3817,61 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
3707 const iGmRun *run = *r.value; 3817 const iGmRun *run = *r.value;
3708 if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) { 3818 if (isOverlapping_Rangei(bufRange, ySpan_Rect(run->visBounds))) {
3709 beginTarget_Paint(p, buf->texture); 3819 beginTarget_Paint(p, buf->texture);
3710 drawRun_DrawContext_(&ctx, run); 3820 drawRun_DrawContext_(ctx, run);
3711 } 3821 }
3712 } 3822 }
3713 } 3823 }
3714 endTarget_Paint(&ctx.paint); 3824 endTarget_Paint(p);
3715 } 3825 }
3716 validate_VisBuf(visBuf);
3717 clear_PtrSet(d->invalidRuns); 3826 clear_PtrSet(d->invalidRuns);
3718 } 3827 }
3828 return didDraw;
3829}
3830
3831static void prerender_DocumentWidget_(iAny *context) {
3832 const iDocumentWidget *d = context;
3833 iDrawContext ctx = {
3834 .widget = d,
3835 .docBounds = documentBounds_DocumentWidget_(d),
3836 .vis = visibleRange_DocumentWidget_(d),
3837 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0
3838 };
3839// printf("%u prerendering\n", SDL_GetTicks());
3840 if (render_DocumentWidget_(d, &ctx, iTrue /* just fill up progressively */)) {
3841 /* Something was drawn, should check if there is still more to do. */
3842 addTicker_App(prerender_DocumentWidget_, context);
3843 }
3844}
3845
3846static void draw_DocumentWidget_(const iDocumentWidget *d) {
3847 const iWidget *w = constAs_Widget(d);
3848 const iRect bounds = bounds_Widget(w);
3849 if (width_Rect(bounds) <= 0) {
3850 return;
3851 }
3852 draw_Widget(w);
3853 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) {
3854 updateTimestampBuf_DocumentWidget_(d);
3855 }
3856 if (d->drawBufs->flags & updateSideBuf_DrawBufsFlag) {
3857 updateSideIconBuf_DocumentWidget_(d);
3858 }
3859 const iRect docBounds = documentBounds_DocumentWidget_(d);
3860 const iRangei vis = visibleRange_DocumentWidget_(d);
3861 iDrawContext ctx = {
3862 .widget = d,
3863 .docBounds = docBounds,
3864 .vis = vis,
3865 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0,
3866 };
3867 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
3719 setClip_Paint(&ctx.paint, bounds); 3868 setClip_Paint(&ctx.paint, bounds);
3720 const int yTop = docBounds.pos.y - value_Anim(&d->scrollY); 3869 const int yTop = docBounds.pos.y - value_Anim(&d->scrollY);
3721 draw_VisBuf(visBuf, init_I2(bounds.pos.x, yTop)); 3870 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop));
3722 /* Text markers. */ 3871 /* Text markers. */
3872 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
3723 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { 3873 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) {
3724 SDL_Renderer *render = renderer_Window(get_Window()); 3874 SDL_Renderer *render = renderer_Window(get_Window());
3725 ctx.firstMarkRect = zero_Rect(); 3875 ctx.firstMarkRect = zero_Rect();
3726 ctx.lastMarkRect = zero_Rect(); 3876 ctx.lastMarkRect = zero_Rect();
3727 SDL_SetRenderDrawBlendMode(render, 3877 SDL_SetRenderDrawBlendMode(render,
@@ -3729,15 +3879,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
3729 : SDL_BLENDMODE_BLEND); 3879 : SDL_BLENDMODE_BLEND);
3730 ctx.viewPos = topLeft_Rect(docBounds); 3880 ctx.viewPos = topLeft_Rect(docBounds);
3731 /* Marker starting outside the visible range? */ 3881 /* Marker starting outside the visible range? */
3732 if (d->firstVisibleRun) { 3882 if (d->visibleRuns.start) {
3733 if (!isEmpty_Range(&d->selectMark) && 3883 if (!isEmpty_Range(&d->selectMark) &&
3734 d->selectMark.start < d->firstVisibleRun->text.start && 3884 d->selectMark.start < d->visibleRuns.start->text.start &&
3735 d->selectMark.end > d->firstVisibleRun->text.start) { 3885 d->selectMark.end > d->visibleRuns.start->text.start) {
3736 ctx.inSelectMark = iTrue; 3886 ctx.inSelectMark = iTrue;
3737 } 3887 }
3738 if (isEmpty_Range(&d->foundMark) && 3888 if (isEmpty_Range(&d->foundMark) &&
3739 d->foundMark.start < d->firstVisibleRun->text.start && 3889 d->foundMark.start < d->visibleRuns.start->text.start &&
3740 d->foundMark.end > d->firstVisibleRun->text.start) { 3890 d->foundMark.end > d->visibleRuns.start->text.start) {
3741 ctx.inFoundMark = iTrue; 3891 ctx.inFoundMark = iTrue;
3742 } 3892 }
3743 } 3893 }
diff --git a/src/ui/visbuf.c b/src/ui/visbuf.c
index 198ad574..a9443b34 100644
--- a/src/ui/visbuf.c
+++ b/src/ui/visbuf.c
@@ -29,6 +29,8 @@ iDefineTypeConstruction(VisBuf)
29void init_VisBuf(iVisBuf *d) { 29void init_VisBuf(iVisBuf *d) {
30 d->texSize = zero_I2(); 30 d->texSize = zero_I2();
31 iZap(d->buffers); 31 iZap(d->buffers);
32 iZap(d->vis);
33 d->bufferInvalidated = NULL;
32} 34}
33 35
34void deinit_VisBuf(iVisBuf *d) { 36void deinit_VisBuf(iVisBuf *d) {
@@ -36,9 +38,14 @@ void deinit_VisBuf(iVisBuf *d) {
36} 38}
37 39
38void invalidate_VisBuf(iVisBuf *d) { 40void invalidate_VisBuf(iVisBuf *d) {
41 int origin = d->vis.start - d->texSize.y;
39 iForIndices(i, d->buffers) { 42 iForIndices(i, d->buffers) {
40 d->buffers[i].origin = i * d->texSize.y; 43 d->buffers[i].origin = origin;
44 origin += d->texSize.y;
41 iZap(d->buffers[i].validRange); 45 iZap(d->buffers[i].validRange);
46 if (d->bufferInvalidated) {
47 d->bufferInvalidated(d, i);
48 }
42 } 49 }
43} 50}
44 51
@@ -58,9 +65,13 @@ void alloc_VisBuf(iVisBuf *d, const iInt2 size, int granularity) {
58 texSize.x, 65 texSize.x,
59 texSize.y); 66 texSize.y);
60 SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE); 67 SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE);
61 tex->origin = i * texSize.y; 68// tex->origin = i * texSize.y;
62 iZap(tex->validRange); 69// iZap(tex->validRange);
70// if (d->invalidUserData) {
71// d->invalidUserData(i, d->buffers[i].user);
72// }
63 } 73 }
74 invalidate_VisBuf(d);
64 } 75 }
65} 76}
66 77
@@ -72,53 +83,74 @@ void dealloc_VisBuf(iVisBuf *d) {
72 } 83 }
73} 84}
74 85
75void reposition_VisBuf(iVisBuf *d, const iRangei vis) { 86#if 0
76 d->vis = vis; 87static size_t findMostDistant_VisBuf_(const iVisBuf *d, const size_t *avail, size_t numAvail,
77 iRangei good = { 0, 0 }; 88 const iRangei vis) {
78 size_t avail[iElemCount(d->buffers)], numAvail = 0; 89 size_t chosen = 0;
79 /* Check which buffers are available for reuse. */ { 90 int distChosen = iAbsi(d->buffers[0].origin - vis.start);
80 iForIndices(i, d->buffers) { 91 printf(" avail (got %zu): %zu", numAvail, avail[0]);
81 iVisBufTexture *buf = d->buffers + i; 92 for (size_t i = 1; i < numAvail; i++) {
82 const iRangei region = { buf->origin, buf->origin + d->texSize.y }; 93 printf(" %zu", avail[i]);
83 if (isEmpty_Rangei(buf->validRange) || 94 const int dist = iAbsi(d->buffers[i].origin - vis.start);
84 buf->validRange.start >= vis.end || buf->validRange.end <= vis.start) { 95 if (dist > distChosen) {
85 avail[numAvail++] = i; 96 chosen = i;
86 iZap(buf->validRange); 97 distChosen = dist;
87 }
88 else {
89 good = union_Rangei(good, region);
90 }
91 } 98 }
92 } 99 }
100 printf("\n chose index %zu (%d)\n", chosen, distChosen);
101 return chosen;
102}
103
104static size_t take_(size_t *avail, size_t *numAvail, size_t index) {
105 const size_t value = avail[index];
106 memmove(avail + index, avail + index + 1, sizeof(size_t) * (*numAvail - index - 1));
107 (*numAvail)--;
108 return value;
109}
110#endif
111
112iBool reposition_VisBuf(iVisBuf *d, const iRangei vis) {
113 if (equal_Rangei(vis, d->vis)) {
114 return iFalse;
115 }
116 d->vis = vis;
93 iBool wasChanged = iFalse; 117 iBool wasChanged = iFalse;
94 iBool doReset = (numAvail == iElemCount(d->buffers)); 118 const size_t lastPos = iElemCount(d->buffers) - 1;
95 /* Try to extend to cover the visible range. */ 119 if (d->buffers[0].origin > vis.end || d->buffers[lastPos].origin + d->texSize.y <= vis.start) {
96 while (!doReset && vis.start < good.start) { 120 /* All buffers outside the visible region. */
97 if (numAvail == 0) { 121 invalidate_VisBuf(d);
98 doReset = iTrue;
99 break;
100 }
101 good.start -= d->texSize.y;
102 d->buffers[avail[--numAvail]].origin = good.start;
103 wasChanged = iTrue; 122 wasChanged = iTrue;
104 } 123 }
105 while (!doReset && vis.end > good.end) { 124 else {
106 if (numAvail == 0) { 125 /* Roll up. */
107 doReset = iTrue; 126 while (d->buffers[0].origin > vis.start) {
108 break; 127 SDL_Texture *last = d->buffers[lastPos].texture;
128 void * user = d->buffers[lastPos].user;
129 memmove(d->buffers + 1, d->buffers, sizeof(iVisBufTexture) * lastPos);
130 d->buffers[0].texture = last;
131 d->buffers[0].user = user;
132 d->buffers[0].origin = d->buffers[1].origin - d->texSize.y;
133 iZap(d->buffers[0].validRange);
134 if (d->bufferInvalidated) {
135 d->bufferInvalidated(d, 0);
136 }
137 wasChanged = iTrue;
109 } 138 }
110 d->buffers[avail[--numAvail]].origin = good.end; 139 if (!wasChanged) {
111 good.end += d->texSize.y; 140 /* Roll down. */
112 wasChanged = iTrue; 141 while (d->buffers[lastPos].origin + d->texSize.y < vis.end) {
113 } 142 SDL_Texture *first = d->buffers[0].texture;
114 if (doReset) { 143 void * user = d->buffers[0].user;
115// puts("VisBuf reset!"); 144 memmove(d->buffers, d->buffers + 1, sizeof(iVisBufTexture) * lastPos);
116// fflush(stdout); 145 d->buffers[lastPos].texture = first;
117 wasChanged = iTrue; 146 d->buffers[lastPos].user = user;
118 int pos = -1; 147 d->buffers[lastPos].origin = d->buffers[lastPos - 1].origin + d->texSize.y;
119 iForIndices(i, d->buffers) { 148 iZap(d->buffers[lastPos].validRange);
120 iZap(d->buffers[i].validRange); 149 if (d->bufferInvalidated) {
121 d->buffers[i].origin = vis.start + pos++ * d->texSize.y; 150 d->bufferInvalidated(d, lastPos);
151 }
152 wasChanged = iTrue;
153 }
122 } 154 }
123 } 155 }
124#if 0 156#if 0
@@ -134,6 +166,33 @@ void reposition_VisBuf(iVisBuf *d, const iRangei vis) {
134 fflush(stdout); 166 fflush(stdout);
135 } 167 }
136#endif 168#endif
169#if !defined (NDEBUG)
170 /* Buffers must not overlap. */
171 iForIndices(m, d->buffers) {
172 const iRangei M = { d->buffers[m].origin, d->buffers[m].origin + d->texSize.y };
173 iForIndices(n, d->buffers) {
174 if (m == n) continue;
175 const iRangei N = { d->buffers[n].origin, d->buffers[n].origin + d->texSize.y };
176 const iRangei is = intersect_Rangei(M, N);
177 if (size_Range(&is) != 0) {
178 printf("buffers %zu (%i) and %zu (%i) overlap\n",
179 m, M.start, n, N.start);
180 fflush(stdout);
181 }
182 iAssert(size_Range(&is) == 0);
183 }
184 }
185#endif
186 return iTrue; /* at least the visible range changed */
187}
188
189iRangei allocRange_VisBuf(const iVisBuf *d) {
190 return (iRangei){ d->buffers[0].origin,
191 d->buffers[iElemCount(d->buffers) - 1].origin + d->texSize.y };
192}
193
194iRangei bufferRange_VisBuf(const iVisBuf *d, size_t index) {
195 return (iRangei){ d->buffers[index].origin, d->buffers[index].origin + d->texSize.y };
137} 196}
138 197
139void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) { 198void invalidRanges_VisBuf(const iVisBuf *d, const iRangei full, iRangei *out_invalidRanges) {
@@ -177,5 +236,5 @@ void draw_VisBuf(const iVisBuf *d, iInt2 topLeft) {
177 dst.y += get_Window()->root->rect.size.y / 4; 236 dst.y += get_Window()->root->rect.size.y / 4;
178#endif 237#endif
179 SDL_RenderCopy(render, buf->texture, NULL, &dst); 238 SDL_RenderCopy(render, buf->texture, NULL, &dst);
180 } 239 }
181} 240}
diff --git a/src/ui/visbuf.h b/src/ui/visbuf.h
index b285e90b..7efa36a7 100644
--- a/src/ui/visbuf.h
+++ b/src/ui/visbuf.h
@@ -33,12 +33,16 @@ struct Impl_VisBufTexture {
33 SDL_Texture *texture; 33 SDL_Texture *texture;
34 int origin; 34 int origin;
35 iRangei validRange; 35 iRangei validRange;
36 void *user;
36}; 37};
37 38
39#define numBuffers_VisBuf ((size_t) 4)
40
38struct Impl_VisBuf { 41struct Impl_VisBuf {
39 iInt2 texSize; 42 iInt2 texSize;
40 iRangei vis; 43 iRangei vis;
41 iVisBufTexture buffers[4]; 44 iVisBufTexture buffers[numBuffers_VisBuf];
45 void (*bufferInvalidated)(iVisBuf *, size_t index);
42}; 46};
43 47
44iDeclareTypeConstruction(VisBuf) 48iDeclareTypeConstruction(VisBuf)
@@ -46,8 +50,10 @@ iDeclareTypeConstruction(VisBuf)
46void invalidate_VisBuf (iVisBuf *); 50void invalidate_VisBuf (iVisBuf *);
47void alloc_VisBuf (iVisBuf *, const iInt2 size, int granularity); 51void alloc_VisBuf (iVisBuf *, const iInt2 size, int granularity);
48void dealloc_VisBuf (iVisBuf *); 52void dealloc_VisBuf (iVisBuf *);
49void reposition_VisBuf (iVisBuf *, const iRangei vis); 53iBool reposition_VisBuf (iVisBuf *, const iRangei vis); /* returns true if `vis` changes */
50void validate_VisBuf (iVisBuf *); 54void validate_VisBuf (iVisBuf *);
51 55
56iRangei allocRange_VisBuf (const iVisBuf *);
57iRangei bufferRange_VisBuf (const iVisBuf *, size_t index);
52void invalidRanges_VisBuf (const iVisBuf *, const iRangei full, iRangei *out_invalidRanges); 58void invalidRanges_VisBuf (const iVisBuf *, const iRangei full, iRangei *out_invalidRanges);
53void draw_VisBuf (const iVisBuf *, iInt2 topLeft); 59void draw_VisBuf (const iVisBuf *, iInt2 topLeft);