summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2022-02-09 10:42:23 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2022-02-09 10:42:23 +0200
commit0585c687a38f0d177ec4db48567ea08326094111 (patch)
treeba1feafdda3a1258f7baa18edbe7eeaf97ff5bd8 /src
parent59363d3e230a0e9702e31936309473fb576ecec5 (diff)
parent4ae755de3ff4a37763aacc22ea119edab2099e84 (diff)
Merge branch 'dev' into work/v1.11
# Conflicts: # res/lang/eo.bin # res/lang/ie.bin
Diffstat (limited to 'src')
-rw-r--r--src/app.c25
-rw-r--r--src/gmdocument.c11
-rw-r--r--src/gmdocument.h2
-rw-r--r--src/ui/documentwidget.c50
-rw-r--r--src/ui/inputwidget.c17
-rw-r--r--src/ui/inputwidget.h2
-rw-r--r--src/ui/linkinfo.c2
-rw-r--r--src/ui/mediaui.c4
-rw-r--r--src/ui/paint.c3
-rw-r--r--src/ui/scrollwidget.c20
-rw-r--r--src/ui/text.c61
-rw-r--r--src/ui/util.c2
-rw-r--r--src/ui/widget.c4
-rw-r--r--src/ui/window.c38
-rw-r--r--src/ui/window.h2
15 files changed, 174 insertions, 69 deletions
diff --git a/src/app.c b/src/app.c
index 1820905b..0f9249cc 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1257,7 +1257,7 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) {
1257 return iFalse; 1257 return iFalse;
1258 } 1258 }
1259#endif 1259#endif
1260 return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); 1260 return !isRefreshPending_App();
1261} 1261}
1262 1262
1263static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) { 1263static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *event) {
@@ -1564,7 +1564,6 @@ static void runTickers_App_(iApp *d) {
1564 /* Tickers may add themselves again, so we'll run off a copy. */ 1564 /* Tickers may add themselves again, so we'll run off a copy. */
1565 iSortedArray *pending = copy_SortedArray(&d->tickers); 1565 iSortedArray *pending = copy_SortedArray(&d->tickers);
1566 clear_SortedArray(&d->tickers); 1566 clear_SortedArray(&d->tickers);
1567 postRefresh_App();
1568 iConstForEach(Array, i, &pending->values) { 1567 iConstForEach(Array, i, &pending->values) {
1569 const iTicker *ticker = i.value; 1568 const iTicker *ticker = i.value;
1570 if (ticker->callback) { 1569 if (ticker->callback) {
@@ -1577,12 +1576,6 @@ static void runTickers_App_(iApp *d) {
1577 if (isEmpty_SortedArray(&d->tickers)) { 1576 if (isEmpty_SortedArray(&d->tickers)) {
1578 d->lastTickerTime = 0; 1577 d->lastTickerTime = 0;
1579 } 1578 }
1580// iForIndices(i, d->window->base.roots) {
1581// iRoot *root = d->window->base.roots[i];
1582// if (root) {
1583// notifyVisualOffsetChange_Root(root);
1584// }
1585// }
1586} 1579}
1587 1580
1588static int resizeWatcher_(void *user, SDL_Event *event) { 1581static int resizeWatcher_(void *user, SDL_Event *event) {
@@ -1649,19 +1642,20 @@ void refresh_App(void) {
1649 } 1642 }
1650 } 1643 }
1651 /* TODO: `pendingRefresh` should be window-specific. */ 1644 /* TODO: `pendingRefresh` should be window-specific. */
1652 if (exchange_Atomic(&d->pendingRefresh, iFalse)) { 1645 if (d->warmupFrames || exchange_Atomic(&d->pendingRefresh, iFalse)) {
1653 /* Draw each window. */ 1646 /* Draw each window. */
1654 iConstForEach(PtrArray, j, &windows) { 1647 iConstForEach(PtrArray, j, &windows) {
1655 iWindow *win = j.ptr; 1648 iWindow *win = j.ptr;
1656 setCurrent_Window(win); 1649 setCurrent_Window(win);
1657 switch (win->type) { 1650 switch (win->type) {
1658 case main_WindowType: 1651 case main_WindowType: {
1659 // iTime draw; 1652// iTime draw;
1660 // initCurrent_Time(&draw); 1653// initCurrent_Time(&draw);
1661 draw_MainWindow(as_MainWindow(win)); 1654 draw_MainWindow(as_MainWindow(win));
1662 // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); 1655// printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));
1663 // fflush(stdout); 1656// fflush(stdout);
1664 break; 1657 break;
1658 }
1665 default: 1659 default:
1666 draw_Window(win); 1660 draw_Window(win);
1667 break; 1661 break;
@@ -1675,7 +1669,8 @@ void refresh_App(void) {
1675} 1669}
1676 1670
1677iBool isRefreshPending_App(void) { 1671iBool isRefreshPending_App(void) {
1678 return value_Atomic(&app_.pendingRefresh); 1672 const iApp *d = &app_;
1673 return !isEmpty_SortedArray(&d->tickers) || value_Atomic(&app_.pendingRefresh);
1679} 1674}
1680 1675
1681iBool isFinishedLaunching_App(void) { 1676iBool isFinishedLaunching_App(void) {
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 69fe77f8..546dc6f5 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -316,7 +316,8 @@ static iBool isAllowedLinkIcon_Char_(iChar icon) {
316 icon == 0x2022 /* bullet */ || 316 icon == 0x2022 /* bullet */ ||
317 icon == 0x2139 /* info */ || 317 icon == 0x2139 /* info */ ||
318 (icon >= 0x2190 && icon <= 0x21ff /* arrows */) || 318 (icon >= 0x2190 && icon <= 0x21ff /* arrows */) ||
319 icon == 0x2a2f /* close X */ || icon == 0x2b50; 319 icon == 0x2a2f /* close X */ ||
320 (icon >= 0x2b00 && icon <= 0x2bff);
320} 321}
321 322
322static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) { 323static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *linkId) {
@@ -1097,7 +1098,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
1097 /* Image metadata caption. */ { 1098 /* Image metadata caption. */ {
1098 run.font = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentSmall_FontSize); 1099 run.font = FONT_ID(documentBody_FontId, semiBold_FontStyle, contentSmall_FontSize);
1099 run.color = tmQuoteIcon_ColorId; 1100 run.color = tmQuoteIcon_ColorId;
1100 run.flags = decoration_GmRunFlag; 1101 run.flags = decoration_GmRunFlag | caption_GmRunFlag;
1101 run.mediaId = 0; 1102 run.mediaId = 0;
1102 run.mediaType = 0; 1103 run.mediaType = 0;
1103 run.visBounds.pos.y = pos.y; 1104 run.visBounds.pos.y = pos.y;
@@ -2212,7 +2213,7 @@ const iGmRun *renderProgressive_GmDocument(const iGmDocument *d, const iGmRun *f
2212 setAnsiFlags_Text(d->theme.ansiEscapes); 2213 setAnsiFlags_Text(d->theme.ansiEscapes);
2213 const iGmRun *run = first; 2214 const iGmRun *run = first;
2214 while (isValidRun_GmDocument_(d, run)) { 2215 while (isValidRun_GmDocument_(d, run)) {
2215 if ((dir < 0 && bottom_Rect(run->visBounds) < visRangeY.start) || 2216 if ((dir < 0 && bottom_Rect(run->visBounds) <= visRangeY.start) ||
2216 (dir > 0 && top_Rect(run->visBounds) >= visRangeY.end)) { 2217 (dir > 0 && top_Rect(run->visBounds) >= visRangeY.end)) {
2217 break; 2218 break;
2218 } 2219 }
@@ -2263,6 +2264,10 @@ const iString *source_GmDocument(const iGmDocument *d) {
2263 return &d->source; 2264 return &d->source;
2264} 2265}
2265 2266
2267iGmRunRange runRange_GmDocument(const iGmDocument *d) {
2268 return (iGmRunRange){ constFront_Array(&d->layout), constEnd_Array(&d->layout) };
2269}
2270
2266size_t memorySize_GmDocument(const iGmDocument *d) { 2271size_t memorySize_GmDocument(const iGmDocument *d) {
2267 return size_String(&d->unormSource) + 2272 return size_String(&d->unormSource) +
2268 size_String(&d->source) + 2273 size_String(&d->source) +
diff --git a/src/gmdocument.h b/src/gmdocument.h
index eb02a26c..0969794c 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -122,6 +122,7 @@ enum iGmRunFlags {
122 endOfLine_GmRunFlag = iBit(3), 122 endOfLine_GmRunFlag = iBit(3),
123 quoteBorder_GmRunFlag = iBit(5), 123 quoteBorder_GmRunFlag = iBit(5),
124 wide_GmRunFlag = iBit(6), /* horizontally scrollable */ 124 wide_GmRunFlag = iBit(6), /* horizontally scrollable */
125 caption_GmRunFlag = iBit(7),
125 altText_GmRunFlag = iBit(8), 126 altText_GmRunFlag = iBit(8),
126}; 127};
127 128
@@ -211,6 +212,7 @@ const iGmRun * renderProgressive_GmDocument(const iGmDocument *d, const iGmRun
211iInt2 size_GmDocument (const iGmDocument *); 212iInt2 size_GmDocument (const iGmDocument *);
212const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ 213const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */
213const iString * source_GmDocument (const iGmDocument *); 214const iString * source_GmDocument (const iGmDocument *);
215iGmRunRange runRange_GmDocument (const iGmDocument *);
214size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ 216size_t memorySize_GmDocument (const iGmDocument *); /* bytes */
215int warnings_GmDocument (const iGmDocument *); 217int warnings_GmDocument (const iGmDocument *);
216 218
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index b19958b2..3d2e31b2 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1333,7 +1333,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1333 run->text); 1333 run->text);
1334 } 1334 }
1335 else { 1335 else {
1336 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 1336 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag &&
1337 ~run->flags & caption_GmRunFlag) {
1337 const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId); 1338 const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId);
1338 if (ord >= d->view->owner->ordinalBase) { 1339 if (ord >= d->view->owner->ordinalBase) {
1339 const iChar ordChar = 1340 const iChar ordChar =
@@ -1656,7 +1657,6 @@ static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBo
1656 ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); 1657 ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin);
1657 // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end); 1658 // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end);
1658 if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { 1659 if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) {
1659 didDraw = iTrue;
1660 if (isEmpty_Rangei(buf->validRange)) { 1660 if (isEmpty_Rangei(buf->validRange)) {
1661 /* Fill the required currently visible range (vis). */ 1661 /* Fill the required currently visible range (vis). */
1662 const iRangei bufVisRange = intersect_Rangei(bufRange, vis); 1662 const iRangei bufVisRange = intersect_Rangei(bufRange, vis);
@@ -1669,26 +1669,46 @@ static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBo
1669 extend_GmRunRange_(&meta->runsDrawn); 1669 extend_GmRunRange_(&meta->runsDrawn);
1670 buf->validRange = bufVisRange; 1670 buf->validRange = bufVisRange;
1671 // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); 1671 // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end);
1672 didDraw = iTrue;
1672 } 1673 }
1673 } 1674 }
1674 else { 1675 else {
1675 /* Progressively fill the required runs. */ 1676 /* Progressively fill the required runs. */
1676 if (meta->runsDrawn.start) { 1677 if (meta->runsDrawn.start && buf->validRange.start > bufRange.start) {
1677 beginTarget_Paint(p, buf->texture); 1678 beginTarget_Paint(p, buf->texture);
1678 meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, 1679 iZap(ctx->runsDrawn);
1679 -1, iInvalidSize, 1680 const iGmRun *newStart = renderProgressive_GmDocument(d->doc,
1680 bufVisRange, 1681 meta->runsDrawn.start,
1681 drawRun_DrawContext_, 1682 -1,
1682 ctx); 1683 iInvalidSize,
1683 buf->validRange.start = bufVisRange.start; 1684 bufVisRange,
1685 drawRun_DrawContext_,
1686 ctx);
1687 if (ctx->runsDrawn.start) {
1688 /* Something was actually drawn, so update the valid range. */
1689 const int newTop = top_Rect(ctx->runsDrawn.start->visBounds);
1690 if (newTop != buf->validRange.start) {
1691 didDraw = iTrue;
1692// printf("render: valid:%d->%d run:%p->%p\n",
1693// buf->validRange.start, newTop,
1694// meta->runsDrawn.start,
1695// ctx->runsDrawn.start); fflush(stdout);
1696 buf->validRange.start = newTop;
1697 }
1698 meta->runsDrawn.start = newStart;
1699 }
1684 } 1700 }
1685 if (meta->runsDrawn.end) { 1701 if (meta->runsDrawn.end) {
1686 beginTarget_Paint(p, buf->texture); 1702 beginTarget_Paint(p, buf->texture);
1703 iZap(ctx->runsDrawn);
1687 meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, 1704 meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end,
1688 +1, iInvalidSize, 1705 +1, iInvalidSize,
1689 bufVisRange, 1706 bufVisRange,
1690 drawRun_DrawContext_, 1707 drawRun_DrawContext_,
1691 ctx); 1708 ctx);
1709 if (ctx->runsDrawn.start) {
1710 didDraw = iTrue;
1711 }
1692 buf->validRange.end = bufVisRange.end; 1712 buf->validRange.end = bufVisRange.end;
1693 } 1713 }
1694 } 1714 }
@@ -3109,12 +3129,14 @@ static const char *humanReadableStatusCode_(enum iGmStatusCode code) {
3109 return format_CStr("%d ", code); 3129 return format_CStr("%d ", code);
3110} 3130}
3111 3131
3112static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { 3132static iBool setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
3113 url = canonicalUrl_String(url); 3133 url = canonicalUrl_String(url);
3114 if (!equal_String(d->mod.url, url)) { 3134 if (!equal_String(d->mod.url, url)) {
3115 d->flags |= urlChanged_DocumentWidgetFlag; 3135 d->flags |= urlChanged_DocumentWidgetFlag;
3116 set_String(d->mod.url, url); 3136 set_String(d->mod.url, url);
3137 return iTrue;
3117 } 3138 }
3139 return iFalse;
3118} 3140}
3119 3141
3120static void checkResponse_DocumentWidget_(iDocumentWidget *d) { 3142static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
@@ -3139,7 +3161,9 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3139 iMediaRequest *mr = newReused_MediaRequest(d, d->requestLinkId, d->request); 3161 iMediaRequest *mr = newReused_MediaRequest(d, d->requestLinkId, d->request);
3140 unlockResponse_GmRequest(d->request); 3162 unlockResponse_GmRequest(d->request);
3141 d->request = NULL; /* ownership moved */ 3163 d->request = NULL; /* ownership moved */
3142 postCommand_Widget(d, "document.request.cancelled doc:%p", d); 3164 if (!isFinished_GmRequest(mr->req)) {
3165 postCommand_Widget(d, "document.request.cancelled doc:%p", d);
3166 }
3143 pushBack_ObjectList(d->media, mr); 3167 pushBack_ObjectList(d->media, mr);
3144 iRelease(mr); 3168 iRelease(mr);
3145 /* Reset the fetch state, returning to the originating page. */ 3169 /* Reset the fetch state, returning to the originating page. */
@@ -3147,7 +3171,9 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3147 if (equal_String(&mostRecentUrl_History(d->mod.history)->url, url_GmRequest(mr->req))) { 3171 if (equal_String(&mostRecentUrl_History(d->mod.history)->url, url_GmRequest(mr->req))) {
3148 undo_History(d->mod.history); 3172 undo_History(d->mod.history);
3149 } 3173 }
3150 setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc)); 3174 if (setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc))) {
3175 postCommand_Widget(d, "!document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
3176 }
3151 updateFetchProgress_DocumentWidget_(d); 3177 updateFetchProgress_DocumentWidget_(d);
3152 postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr); 3178 postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr);
3153 if (isFinished_GmRequest(mr->req)) { 3179 if (isFinished_GmRequest(mr->req)) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index aa55f3f0..6a8d428a 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -1115,7 +1115,14 @@ static void updateBuffered_InputWidget_(iInputWidget *d) {
1115} 1115}
1116 1116
1117void setText_InputWidget(iInputWidget *d, const iString *text) { 1117void setText_InputWidget(iInputWidget *d, const iString *text) {
1118 setTextUndoable_InputWidget(d, text, iFalse);
1119}
1120
1121void setTextUndoable_InputWidget(iInputWidget *d, const iString *text, iBool isUndoable) {
1118 if (!d) return; 1122 if (!d) return;
1123 if (isUndoable) {
1124 pushUndo_InputWidget_(d);
1125 }
1119 if (d->inFlags & isUrl_InputWidgetFlag) { 1126 if (d->inFlags & isUrl_InputWidgetFlag) {
1120 if (prefs_App()->decodeUserVisibleURLs) { 1127 if (prefs_App()->decodeUserVisibleURLs) {
1121 iString *enc = collect_String(copy_String(text)); 1128 iString *enc = collect_String(copy_String(text));
@@ -1139,7 +1146,9 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
1139 iString *nfcText = collect_String(copy_String(text)); 1146 iString *nfcText = collect_String(copy_String(text));
1140 normalize_String(nfcText); 1147 normalize_String(nfcText);
1141#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT 1148#if !LAGRANGE_USE_SYSTEM_TEXT_INPUT
1142 clearUndo_InputWidget_(d); 1149 if (!isUndoable) {
1150 clearUndo_InputWidget_(d);
1151 }
1143 splitToLines_(nfcText, &d->lines); 1152 splitToLines_(nfcText, &d->lines);
1144 iAssert(!isEmpty_Array(&d->lines)); 1153 iAssert(!isEmpty_Array(&d->lines));
1145 iForEach(Array, i, &d->lines) { 1154 iForEach(Array, i, &d->lines) {
@@ -1175,6 +1184,12 @@ void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
1175 delete_String(str); 1184 delete_String(str);
1176} 1185}
1177 1186
1187void setTextUndoableCStr_InputWidget(iInputWidget *d, const char *cstr, iBool isUndoable) {
1188 iString *str = newCStr_String(cstr);
1189 setTextUndoable_InputWidget(d, str, isUndoable);
1190 delete_String(str);
1191}
1192
1178void selectAll_InputWidget(iInputWidget *d) { 1193void selectAll_InputWidget(iInputWidget *d) {
1179#if LAGRANGE_USE_SYSTEM_TEXT_INPUT 1194#if LAGRANGE_USE_SYSTEM_TEXT_INPUT
1180 if (d->sysCtrl) { 1195 if (d->sysCtrl) {
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 5a61ec22..000fa4b7 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -46,6 +46,8 @@ void setMode_InputWidget (iInputWidget *, enum iInputMode mode);
46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); 46void setMaxLen_InputWidget (iInputWidget *, size_t maxLen);
47void setText_InputWidget (iInputWidget *, const iString *text); 47void setText_InputWidget (iInputWidget *, const iString *text);
48void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 48void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
49void setTextUndoable_InputWidget (iInputWidget *, const iString *text, iBool isUndoable);
50void setTextUndoableCStr_InputWidget (iInputWidget *, const char *cstr, iBool isUndoable);
49void setFont_InputWidget (iInputWidget *, int fontId); 51void setFont_InputWidget (iInputWidget *, int fontId);
50void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 52void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
51void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines); 53void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines);
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c
index 8974a486..72d76678 100644
--- a/src/ui/linkinfo.c
+++ b/src/ui/linkinfo.c
@@ -101,7 +101,7 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o
101 } 101 }
102 else if (scheme != gemini_GmLinkScheme) { 102 else if (scheme != gemini_GmLinkScheme) {
103 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); 103 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " ");
104 append_String(text_out, url); 104 appendCStrN_String(text_out, cstr_String(url), iMin(500, size_String(url)));
105 } 105 }
106 else { 106 else {
107 appendCStr_String(text_out, "\x1b[1m"); 107 appendCStr_String(text_out, "\x1b[1m");
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c
index f0070688..2ddebb57 100644
--- a/src/ui/mediaui.c
+++ b/src/ui/mediaui.c
@@ -104,7 +104,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) {
104 if (align == right_Alignment) { 104 if (align == right_Alignment) {
105 pos.x -= size.x; 105 pos.x -= size.x;
106 } 106 }
107 drawRange_Text(font, addY_I2(pos, gap_UI / 2), color, range_String(&num)); 107 drawRange_Text(font, addY_I2(pos, 0/*gap_UI / 2*/), color, range_String(&num));
108 deinit_String(&num); 108 deinit_String(&num);
109 return size.x; 109 return size.x;
110} 110}
@@ -316,7 +316,7 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
316 isFinished ? uiTextAction_ColorId : uiTextDim_ColorId, 316 isFinished ? uiTextAction_ColorId : uiTextDim_ColorId,
317 cstr_Lang(isFinished ? "media.download.complete" : "media.download.warnclose")); 317 cstr_Lang(isFinished ? "media.download.complete" : "media.download.warnclose"));
318 const int x2 = right_Rect(rect); 318 const int x2 = right_Rect(rect);
319 drawSevenSegmentBytes_MediaUI(uiLabel_FontId, init_I2(x2, y1), 319 drawSevenSegmentBytes_MediaUI(uiContent_FontId, init_I2(x2, y1),
320 uiTextStrong_ColorId, uiTextDim_ColorId, 320 uiTextStrong_ColorId, uiTextDim_ColorId,
321 info.numBytes); 321 info.numBytes);
322 const iInt2 pos = init_I2(x2, y2); 322 const iInt2 pos = init_I2(x2, y2);
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 67b509fb..5f19ec3e 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -77,7 +77,8 @@ void setClip_Paint(iPaint *d, iRect rect) {
77 SDL_QueryTexture(target, NULL, NULL, &targetRect.size.x, &targetRect.size.y); 77 SDL_QueryTexture(target, NULL, NULL, &targetRect.size.x, &targetRect.size.y);
78 rect = intersect_Rect(rect, targetRect); 78 rect = intersect_Rect(rect, targetRect);
79 } 79 }
80 else { 80 /* The origin is non-zero when drawing into a widget's own buffer. */
81 if (isEqual_I2(zero_I2(), origin_Paint)) {
81 rect = intersect_Rect(rect, rect_Root(get_Root())); 82 rect = intersect_Rect(rect, rect_Root(get_Root()));
82 } 83 }
83 if (isEmpty_Rect(rect)) { 84 if (isEmpty_Rect(rect)) {
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c
index 651669c6..71e4873f 100644
--- a/src/ui/scrollwidget.c
+++ b/src/ui/scrollwidget.c
@@ -239,15 +239,17 @@ static void draw_ScrollWidget_(const iScrollWidget *d) {
239 init_Paint(&p); 239 init_Paint(&p);
240 /* Blend if opacity is not at maximum. */ 240 /* Blend if opacity is not at maximum. */
241 p.alpha = 255 * value_Anim(&d->opacity); 241 p.alpha = 255 * value_Anim(&d->opacity);
242 SDL_Renderer *render = renderer_Window(get_Window()); 242 if (p.alpha > 0) {
243 if (p.alpha < 255) { 243 SDL_Renderer *render = renderer_Window(get_Window());
244 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND); 244 if (p.alpha < 255) {
245 } 245 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_BLEND);
246 const iRect thumbRect = shrunk_Rect( 246 }
247 thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2)); 247 const iRect thumbRect = shrunk_Rect(
248 fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId); 248 thumbRect_ScrollWidget_(d), init_I2(isPressed ? gap_UI : (gap_UI * 4 / 3), gap_UI / 2));
249 if (p.alpha < 255) { 249 fillRect_Paint(&p, thumbRect, isPressed ? uiBackgroundPressed_ColorId : tmQuote_ColorId);
250 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); 250 if (p.alpha < 255) {
251 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
252 }
251 } 253 }
252 } 254 }
253} 255}
diff --git a/src/ui/text.c b/src/ui/text.c
index ab2af2b2..51531057 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -246,10 +246,22 @@ struct Impl_CacheRow {
246 iInt2 pos; 246 iInt2 pos;
247}; 247};
248 248
249iDeclareType(PrioMapItem)
250struct Impl_PrioMapItem {
251 int priority;
252 uint32_t fontIndex;
253};
254
255static int cmp_PrioMapItem_(const void *a, const void *b) {
256 const iPrioMapItem *i = a, *j = b;
257 return -iCmp(i->priority, j->priority);
258}
259
249struct Impl_Text { 260struct Impl_Text {
250 float contentFontSize; 261 float contentFontSize;
251 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ 262 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
252 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ 263 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
264 iArray fontPriorityOrder;
253 SDL_Renderer * render; 265 SDL_Renderer * render;
254 SDL_Texture * cache; 266 SDL_Texture * cache;
255 iInt2 cacheSize; 267 iInt2 cacheSize;
@@ -283,8 +295,9 @@ static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId)
283 /* This is the highest priority override font. */ 295 /* This is the highest priority override font. */
284 d->overrideFontId = baseId; 296 d->overrideFontId = baseId;
285 } 297 }
298 pushBack_Array(&d->fontPriorityOrder, &(iPrioMapItem){ spec->priority, baseId });
286 for (enum iFontStyle style = 0; style < max_FontStyle; style++) { 299 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
287 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) { 300 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
288 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)), 301 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)),
289 spec, 302 spec,
290 spec->styles[style], 303 spec->styles[style],
@@ -321,6 +334,7 @@ static void initFonts_Text_(iText *d) {
321 and styles for each available font. Indices to `fonts` act as font runtime IDs. */ 334 and styles for each available font. Indices to `fonts` act as font runtime IDs. */
322 /* First the mandatory fonts. */ 335 /* First the mandatory fonts. */
323 d->overrideFontId = -1; 336 d->overrideFontId = -1;
337 clear_Array(&d->fontPriorityOrder);
324 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */ 338 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */
325 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId); 339 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId);
326 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId); 340 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId);
@@ -330,12 +344,14 @@ static void initFonts_Text_(iText *d) {
330 /* Check if there are auxiliary fonts available and set those up, too. */ 344 /* Check if there are auxiliary fonts available and set those up, too. */
331 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) { 345 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
332 const iFontSpec *spec = s.ptr; 346 const iFontSpec *spec = s.ptr;
347// printf("spec '%s': prio=%d\n", cstr_String(&spec->name), spec->priority);
333 if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) { 348 if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) {
334 const int fontId = size_Array(&d->fonts); 349 const int fontId = size_Array(&d->fonts);
335 resize_Array(&d->fonts, fontId + maxVariants_Fonts); 350 resize_Array(&d->fonts, fontId + maxVariants_Fonts);
336 setupFontVariants_Text_(d, spec, fontId); 351 setupFontVariants_Text_(d, spec, fontId);
337 } 352 }
338 } 353 }
354 sort_Array(&d->fontPriorityOrder, cmp_PrioMapItem_);
339#if !defined (NDEBUG) 355#if !defined (NDEBUG)
340 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts)); 356 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts));
341#endif 357#endif
@@ -403,6 +419,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
403 iText *oldActive = activeText_; 419 iText *oldActive = activeText_;
404 activeText_ = d; 420 activeText_ = d;
405 init_Array(&d->fonts, sizeof(iFont)); 421 init_Array(&d->fonts, sizeof(iFont));
422 init_Array(&d->fontPriorityOrder, sizeof(iPrioMapItem));
406 d->contentFontSize = contentScale_Text_; 423 d->contentFontSize = contentScale_Text_;
407 d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */); 424 d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */);
408 d->baseFontId = -1; 425 d->baseFontId = -1;
@@ -438,6 +455,7 @@ void deinit_Text(iText *d) {
438 deinitCache_Text_(d); 455 deinitCache_Text_(d);
439 d->render = NULL; 456 d->render = NULL;
440 iRelease(d->ansiEscape); 457 iRelease(d->ansiEscape);
458 deinit_Array(&d->fontPriorityOrder);
441 deinit_Array(&d->fonts); 459 deinit_Array(&d->fonts);
442} 460}
443 461
@@ -573,25 +591,24 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
573 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 591 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
574 return d; 592 return d;
575 } 593 }
576 /* As a fallback, check all other available fonts of this size. */ 594 /* As a fallback, check all other available fonts of this size in priority order. */
577 for (int aux = 0; aux < 2; aux++) { 595 iConstForEach(Array, i, &activeText_->fontPriorityOrder) {
578 for (iFont *font = font_Text_(FONT_ID(0, styleId, sizeId)); 596 iFont *font = font_Text_(FONT_ID(((const iPrioMapItem *) i.value)->fontIndex,
579 font < (iFont *) end_Array(&activeText_->fonts); 597 styleId, sizeId));
580 font += maxVariants_Fonts) { 598 if (font == d || font == overrideFont) {
581 const iBool isAuxiliary = (font->fontSpec->flags & auxiliary_FontSpecFlag) ? 1 : 0; 599 continue; /* already checked this one */
582 if (aux == isAuxiliary) { 600 }
583 /* First try auxiliary fonts, then other remaining fonts. */ 601 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) {
584 continue; 602#if 0
585 } 603 printf("using '%s' (pr:%d) for %lc (%x) => %d [missing in '%s']\n",
586 if (font == d || font == overrideFont) { 604 cstr_String(&font->fontSpec->id),
587 continue; /* already checked this one */ 605 font->fontSpec->priority,
588 } 606 (int) ch,
589 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) { 607 ch,
590// printf("using %s[%f] for %lc (%x) => %d\n", 608 glyphIndex_Font_(font, ch),
591// cstr_String(&font->fontSpec->name), font->fontSpec->scaling, 609 cstr_String(&d->fontSpec->id));
592// (int) ch, ch, glyphIndex_Font_(font, ch)); 610#endif
593 return font; 611 return font;
594 }
595 } 612 }
596 } 613 }
597 if (!*glyphIndex) { 614 if (!*glyphIndex) {
@@ -1450,7 +1467,7 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont)
1450 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) { 1467 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) {
1451 const iChar ch = d->logicalText[info->cluster]; 1468 const iChar ch = d->logicalText[info->cluster];
1452 if (isPictograph_Char(ch) || isEmoji_Char(ch)) { 1469 if (isPictograph_Char(ch) || isEmoji_Char(ch)) {
1453 const float dw = d->font->xScale * d->glyphPos[i].x_advance - monoAdvance; 1470 const float dw = d->font->xScale * d->glyphPos[i].x_advance - (isEmoji_Char(ch) ? 2 : 1) * monoAdvance;
1454 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1; 1471 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1;
1455 d->glyphPos[i].x_advance -= dw / d->font->xScale - 1; 1472 d->glyphPos[i].x_advance -= dw / d->font->xScale - 1;
1456 } 1473 }
diff --git a/src/ui/util.c b/src/ui/util.c
index d0d24eb2..41f8eaa9 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1780,7 +1780,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
1780 } 1780 }
1781 else if (equal_Command(cmd, "valueinput.set")) { 1781 else if (equal_Command(cmd, "valueinput.set")) {
1782 iInputWidget *input = findChild_Widget(dlg, "input"); 1782 iInputWidget *input = findChild_Widget(dlg, "input");
1783 setTextCStr_InputWidget(input, suffixPtr_Command(cmd, "text")); 1783 setTextUndoableCStr_InputWidget(input, suffixPtr_Command(cmd, "text"), iTrue);
1784 validate_InputWidget(input); 1784 validate_InputWidget(input);
1785 return iTrue; 1785 return iTrue;
1786 } 1786 }
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 9f67b1c7..fc754b7a 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -1290,7 +1290,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1290 /* TODO: Motion events occur frequently. Maybe it would help if these were handled 1290 /* TODO: Motion events occur frequently. Maybe it would help if these were handled
1291 via audiences that specifically register to listen for motion, to minimize the 1291 via audiences that specifically register to listen for motion, to minimize the
1292 number of widgets that need to process them. */ 1292 number of widgets that need to process them. */
1293 const int hoverScrollLimit = 1.5f * lineHeight_Text(default_FontId); 1293 const int hoverScrollLimit = 3.0f * lineHeight_Text(default_FontId);
1294 float speed = 0.0f; 1294 float speed = 0.0f;
1295 if (ev->motion.y < hoverScrollLimit) { 1295 if (ev->motion.y < hoverScrollLimit) {
1296 speed = (hoverScrollLimit - ev->motion.y) / (float) hoverScrollLimit; 1296 speed = (hoverScrollLimit - ev->motion.y) / (float) hoverScrollLimit;
@@ -1315,7 +1315,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1315 if (elapsed > 100) { 1315 if (elapsed > 100) {
1316 elapsed = 16; 1316 elapsed = 16;
1317 } 1317 }
1318 int step = elapsed * gap_UI / 16 * iClamp(speed, -1.0f, 1.0f); 1318 int step = elapsed * gap_UI / 8 * iClamp(speed, -1.0f, 1.0f);
1319 if (step != 0) { 1319 if (step != 0) {
1320 lastHoverOverflowMotionTime_ = nowTime; 1320 lastHoverOverflowMotionTime_ = nowTime;
1321 scrollOverflow_Widget(d, step); 1321 scrollOverflow_Widget(d, step);
diff --git a/src/ui/window.c b/src/ui/window.c
index 13abc5fa..47abf878 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -539,10 +539,19 @@ void deinit_Window(iWindow *d) {
539void init_MainWindow(iMainWindow *d, iRect rect) { 539void init_MainWindow(iMainWindow *d, iRect rect) {
540 theWindow_ = &d->base; 540 theWindow_ = &d->base;
541 theMainWindow_ = d; 541 theMainWindow_ = d;
542 d->enableBackBuf = iFalse;
542 uint32_t flags = 0; 543 uint32_t flags = 0;
543#if defined (iPlatformAppleDesktop) 544#if defined (iPlatformAppleDesktop)
544 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); 545 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
545 flags |= shouldDefaultToMetalRenderer_MacOS() ? SDL_WINDOW_METAL : SDL_WINDOW_OPENGL; 546 flags |= shouldDefaultToMetalRenderer_MacOS() ? SDL_WINDOW_METAL : SDL_WINDOW_OPENGL;
547 if (flags & SDL_WINDOW_METAL) {
548 /* There are some really odd refresh glitches that only occur with the Metal
549 backend. It's perhaps related to it not expecting refresh to stop intermittently
550 to wait for input events. If forcing constant refreshing at full frame rate, the
551 problems seem to go away... Rendering everything to a separate render target
552 appears to sidestep some of the glitches. */
553 d->enableBackBuf = iTrue;
554 }
546#elif defined (iPlatformAppleMobile) 555#elif defined (iPlatformAppleMobile)
547 SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); 556 SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal");
548 flags |= SDL_WINDOW_METAL; 557 flags |= SDL_WINDOW_METAL;
@@ -566,6 +575,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
566 d->place.lastNotifiedSize = zero_I2(); 575 d->place.lastNotifiedSize = zero_I2();
567 d->place.snap = 0; 576 d->place.snap = 0;
568 d->keyboardHeight = 0; 577 d->keyboardHeight = 0;
578 d->backBuf = NULL;
569#if defined(iPlatformMobile) 579#if defined(iPlatformMobile)
570 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ 580 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */
571#else 581#else
@@ -637,6 +647,9 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
637} 647}
638 648
639void deinit_MainWindow(iMainWindow *d) { 649void deinit_MainWindow(iMainWindow *d) {
650 if (d->backBuf) {
651 SDL_DestroyTexture(d->backBuf);
652 }
640 deinitRoots_Window_(as_Window(d)); 653 deinitRoots_Window_(as_Window(d));
641 if (theWindow_ == as_Window(d)) { 654 if (theWindow_ == as_Window(d)) {
642 theWindow_ = NULL; 655 theWindow_ = NULL;
@@ -682,6 +695,10 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) {
682static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { 695static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {
683 if (d && (!d->base.isInvalidated || forced)) { 696 if (d && (!d->base.isInvalidated || forced)) {
684 d->base.isInvalidated = iTrue; 697 d->base.isInvalidated = iTrue;
698 if (d->enableBackBuf && d->backBuf) {
699 SDL_DestroyTexture(d->backBuf);
700 d->backBuf = NULL;
701 }
685 resetFonts_Text(text_Window(d)); 702 resetFonts_Text(text_Window(d));
686 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ 703 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */
687 } 704 }
@@ -1272,11 +1289,28 @@ void draw_MainWindow(iMainWindow *d) {
1272 d->maxDrawableHeight = renderSize.y; 1289 d->maxDrawableHeight = renderSize.y;
1273 } 1290 }
1274 } 1291 }
1292 if (d->enableBackBuf) {
1293 /* Possible resize the backing buffer. */
1294 if (!d->backBuf || !isEqual_I2(size_SDLTexture(d->backBuf), renderSize)) {
1295 if (d->backBuf) {
1296 SDL_DestroyTexture(d->backBuf);
1297 }
1298 d->backBuf = SDL_CreateTexture(d->base.render,
1299 SDL_PIXELFORMAT_RGB888,
1300 SDL_TEXTUREACCESS_TARGET,
1301 renderSize.x,
1302 renderSize.y);
1303// printf("NEW BACKING: %dx%d %p\n", renderSize.x, renderSize.y, d->backBuf); fflush(stdout);
1304 }
1305 }
1275 } 1306 }
1276 const int winFlags = SDL_GetWindowFlags(d->base.win); 1307 const int winFlags = SDL_GetWindowFlags(d->base.win);
1277 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; 1308 const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0;
1278 iPaint p; 1309 iPaint p;
1279 init_Paint(&p); 1310 init_Paint(&p);
1311 if (d->backBuf) {
1312 SDL_SetRenderTarget(d->base.render, d->backBuf);
1313 }
1280 /* Clear the window. The clear color is visible as a border around the window 1314 /* Clear the window. The clear color is visible as a border around the window
1281 when the custom frame is being used. */ { 1315 when the custom frame is being used. */ {
1282 setCurrent_Root(w->roots[0]); 1316 setCurrent_Root(w->roots[0]);
@@ -1359,6 +1393,10 @@ void draw_MainWindow(iMainWindow *d) {
1359 drawCount_ = 0; 1393 drawCount_ = 0;
1360#endif 1394#endif
1361 } 1395 }
1396 if (d->backBuf) {
1397 SDL_SetRenderTarget(d->base.render, NULL);
1398 SDL_RenderCopy(d->base.render, d->backBuf, NULL, NULL);
1399 }
1362#if 0 1400#if 0
1363 /* Text cache debugging. */ { 1401 /* Text cache debugging. */ {
1364 SDL_Rect rect = { d->roots[0]->widget->rect.size.x - 640, 0, 640, 2.5 * 640 }; 1402 SDL_Rect rect = { d->roots[0]->widget->rect.size.x - 640, 0, 640, 2.5 * 640 };
diff --git a/src/ui/window.h b/src/ui/window.h
index b4e348d2..5abf23eb 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -118,6 +118,8 @@ struct Impl_MainWindow {
118 SDL_Texture * appIcon; 118 SDL_Texture * appIcon;
119 int keyboardHeight; /* mobile software keyboards */ 119 int keyboardHeight; /* mobile software keyboards */
120 int maxDrawableHeight; 120 int maxDrawableHeight;
121 iBool enableBackBuf; /* only used on macOS with Metal (helps with refresh glitches for some reason??) */
122 SDL_Texture * backBuf; /* enables refreshing the window without redrawing anything */
121}; 123};
122 124
123iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { 125iLocalDef enum iWindowType type_Window(const iAnyWindow *d) {