summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c60
-rw-r--r--src/ui/inputwidget.c17
-rw-r--r--src/ui/inputwidget.h2
-rw-r--r--src/ui/keys.c1
-rw-r--r--src/ui/linkinfo.c6
-rw-r--r--src/ui/mediaui.c4
-rw-r--r--src/ui/paint.c9
-rw-r--r--src/ui/scrollwidget.c20
-rw-r--r--src/ui/sidebarwidget.c61
-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
14 files changed, 204 insertions, 83 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 25559890..a52e99af 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1332,7 +1332,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1332 run->text); 1332 run->text);
1333 } 1333 }
1334 else { 1334 else {
1335 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag) { 1335 if (d->showLinkNumbers && run->linkId && run->flags & decoration_GmRunFlag &&
1336 ~run->flags & caption_GmRunFlag) {
1336 const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId); 1337 const size_t ord = visibleLinkOrdinal_DocumentView_(d->view, run->linkId);
1337 if (ord >= d->view->owner->ordinalBase) { 1338 if (ord >= d->view->owner->ordinalBase) {
1338 const iChar ordChar = 1339 const iChar ordChar =
@@ -1655,7 +1656,6 @@ static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBo
1655 ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin); 1656 ctx->viewPos = init_I2(left_Rect(ctx->docBounds) - left_Rect(bounds), -buf->origin);
1656 // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end); 1657 // printf(" buffer %zu: buf vis range %d...%d\n", i, bufVisRange.start, bufVisRange.end);
1657 if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) { 1658 if (!prerenderExtra && !isEmpty_Range(&bufVisRange)) {
1658 didDraw = iTrue;
1659 if (isEmpty_Rangei(buf->validRange)) { 1659 if (isEmpty_Rangei(buf->validRange)) {
1660 /* Fill the required currently visible range (vis). */ 1660 /* Fill the required currently visible range (vis). */
1661 const iRangei bufVisRange = intersect_Rangei(bufRange, vis); 1661 const iRangei bufVisRange = intersect_Rangei(bufRange, vis);
@@ -1668,26 +1668,46 @@ static iBool render_DocumentView_(const iDocumentView *d, iDrawContext *ctx, iBo
1668 extend_GmRunRange_(&meta->runsDrawn); 1668 extend_GmRunRange_(&meta->runsDrawn);
1669 buf->validRange = bufVisRange; 1669 buf->validRange = bufVisRange;
1670 // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end); 1670 // printf(" buffer %zu valid %d...%d\n", i, bufRange.start, bufRange.end);
1671 didDraw = iTrue;
1671 } 1672 }
1672 } 1673 }
1673 else { 1674 else {
1674 /* Progressively fill the required runs. */ 1675 /* Progressively fill the required runs. */
1675 if (meta->runsDrawn.start) { 1676 if (meta->runsDrawn.start && buf->validRange.start > bufRange.start) {
1676 beginTarget_Paint(p, buf->texture); 1677 beginTarget_Paint(p, buf->texture);
1677 meta->runsDrawn.start = renderProgressive_GmDocument(d->doc, meta->runsDrawn.start, 1678 iZap(ctx->runsDrawn);
1678 -1, iInvalidSize, 1679 const iGmRun *newStart = renderProgressive_GmDocument(d->doc,
1679 bufVisRange, 1680 meta->runsDrawn.start,
1680 drawRun_DrawContext_, 1681 -1,
1681 ctx); 1682 iInvalidSize,
1682 buf->validRange.start = bufVisRange.start; 1683 bufVisRange,
1684 drawRun_DrawContext_,
1685 ctx);
1686 if (ctx->runsDrawn.start) {
1687 /* Something was actually drawn, so update the valid range. */
1688 const int newTop = top_Rect(ctx->runsDrawn.start->visBounds);
1689 if (newTop != buf->validRange.start) {
1690 didDraw = iTrue;
1691// printf("render: valid:%d->%d run:%p->%p\n",
1692// buf->validRange.start, newTop,
1693// meta->runsDrawn.start,
1694// ctx->runsDrawn.start); fflush(stdout);
1695 buf->validRange.start = newTop;
1696 }
1697 meta->runsDrawn.start = newStart;
1698 }
1683 } 1699 }
1684 if (meta->runsDrawn.end) { 1700 if (meta->runsDrawn.end) {
1685 beginTarget_Paint(p, buf->texture); 1701 beginTarget_Paint(p, buf->texture);
1702 iZap(ctx->runsDrawn);
1686 meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end, 1703 meta->runsDrawn.end = renderProgressive_GmDocument(d->doc, meta->runsDrawn.end,
1687 +1, iInvalidSize, 1704 +1, iInvalidSize,
1688 bufVisRange, 1705 bufVisRange,
1689 drawRun_DrawContext_, 1706 drawRun_DrawContext_,
1690 ctx); 1707 ctx);
1708 if (ctx->runsDrawn.start) {
1709 didDraw = iTrue;
1710 }
1691 buf->validRange.end = bufVisRange.end; 1711 buf->validRange.end = bufVisRange.end;
1692 } 1712 }
1693 } 1713 }
@@ -2711,7 +2731,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
2711 : "media.untitled.audio"); 2731 : "media.untitled.audio");
2712 iUrl parts; 2732 iUrl parts;
2713 init_Url(&parts, d->mod.url); 2733 init_Url(&parts, d->mod.url);
2714 if (!isEmpty_Range(&parts.path)) { 2734 if (!isEmpty_Range(&parts.path) && !equalCase_Rangecc(parts.scheme, "data")) {
2715 linkTitle = 2735 linkTitle =
2716 baseName_Path(collect_String(newRange_String(parts.path))).start; 2736 baseName_Path(collect_String(newRange_String(parts.path))).start;
2717 } 2737 }
@@ -3095,12 +3115,14 @@ static const char *humanReadableStatusCode_(enum iGmStatusCode code) {
3095 return format_CStr("%d ", code); 3115 return format_CStr("%d ", code);
3096} 3116}
3097 3117
3098static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { 3118static iBool setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
3099 url = canonicalUrl_String(url); 3119 url = canonicalUrl_String(url);
3100 if (!equal_String(d->mod.url, url)) { 3120 if (!equal_String(d->mod.url, url)) {
3101 d->flags |= urlChanged_DocumentWidgetFlag; 3121 d->flags |= urlChanged_DocumentWidgetFlag;
3102 set_String(d->mod.url, url); 3122 set_String(d->mod.url, url);
3123 return iTrue;
3103 } 3124 }
3125 return iFalse;
3104} 3126}
3105 3127
3106static void checkResponse_DocumentWidget_(iDocumentWidget *d) { 3128static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
@@ -3125,7 +3147,9 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3125 iMediaRequest *mr = newReused_MediaRequest(d, d->requestLinkId, d->request); 3147 iMediaRequest *mr = newReused_MediaRequest(d, d->requestLinkId, d->request);
3126 unlockResponse_GmRequest(d->request); 3148 unlockResponse_GmRequest(d->request);
3127 d->request = NULL; /* ownership moved */ 3149 d->request = NULL; /* ownership moved */
3128 postCommand_Widget(d, "document.request.cancelled doc:%p", d); 3150 if (!isFinished_GmRequest(mr->req)) {
3151 postCommand_Widget(d, "document.request.cancelled doc:%p", d);
3152 }
3129 pushBack_ObjectList(d->media, mr); 3153 pushBack_ObjectList(d->media, mr);
3130 iRelease(mr); 3154 iRelease(mr);
3131 /* Reset the fetch state, returning to the originating page. */ 3155 /* Reset the fetch state, returning to the originating page. */
@@ -3133,9 +3157,14 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3133 if (equal_String(&mostRecentUrl_History(d->mod.history)->url, url_GmRequest(mr->req))) { 3157 if (equal_String(&mostRecentUrl_History(d->mod.history)->url, url_GmRequest(mr->req))) {
3134 undo_History(d->mod.history); 3158 undo_History(d->mod.history);
3135 } 3159 }
3136 setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc)); 3160 if (setUrl_DocumentWidget_(d, url_GmDocument(d->view.doc))) {
3161 postCommand_Widget(d, "!document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
3162 }
3137 updateFetchProgress_DocumentWidget_(d); 3163 updateFetchProgress_DocumentWidget_(d);
3138 postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr); 3164 postCommand_Widget(d, "media.updated link:%u request:%p", d->requestLinkId, mr);
3165 if (isFinished_GmRequest(mr->req)) {
3166 postCommand_Widget(d, "media.finished link:%u request:%p", d->requestLinkId, mr);
3167 }
3139 return; 3168 return;
3140 } 3169 }
3141 /* Get ready for the incoming new document. */ 3170 /* Get ready for the incoming new document. */
@@ -3211,9 +3240,10 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
3211 addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos); 3240 addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos);
3212 } 3241 }
3213 /* Menu for additional actions, past entries. */ { 3242 /* Menu for additional actions, past entries. */ {
3243 const iBinding *bind = findCommand_Keys("input.precedingline");
3214 iMenuItem items[] = { { "${menu.input.precedingline}", 3244 iMenuItem items[] = { { "${menu.input.precedingline}",
3215 SDLK_v, 3245 bind->key,
3216 KMOD_PRIMARY | KMOD_SHIFT, 3246 bind->mods,
3217 format_CStr("!valueinput.set ptr:%p text:%s", 3247 format_CStr("!valueinput.set ptr:%p text:%s",
3218 buttons, 3248 buttons,
3219 cstr_String(&d->linePrecedingLink)) } }; 3249 cstr_String(&d->linePrecedingLink)) } };
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/keys.c b/src/ui/keys.c
index d4d9320e..33f39633 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -241,6 +241,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
241 { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, 241 { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 },
242 { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 }, 242 { 120,{ "${keys.upload}", SDLK_u, KMOD_PRIMARY, "document.upload" }, 0 },
243 { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 }, 243 { 121,{ "${keys.upload.edit}", SDLK_e, KMOD_PRIMARY, "document.upload copy:1" }, 0 },
244 { 130,{ "${keys.input.precedingline}", SDLK_v, KMOD_PRIMARY | KMOD_SHIFT, "input.precedingline" }, 0 },
244 /* The following cannot currently be changed (built-in duplicates). */ 245 /* The following cannot currently be changed (built-in duplicates). */
245#if defined (iPlatformApple) 246#if defined (iPlatformApple)
246 { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 }, 247 { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 },
diff --git a/src/ui/linkinfo.c b/src/ui/linkinfo.c
index 5102f9b3..36ab00c8 100644
--- a/src/ui/linkinfo.c
+++ b/src/ui/linkinfo.c
@@ -92,8 +92,12 @@ void infoText_LinkInfo(const iGmDocument *doc, iGmLinkId linkId, iString *text_o
92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) }); 92 appendRange_String(text_out, (iRangecc){ parts.path.start, constEnd_String(url) });
93 } 93 }
94 else if (scheme != gemini_GmLinkScheme) { 94 else if (scheme != gemini_GmLinkScheme) {
95 const size_t maxDispLen = 300;
95 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " "); 96 appendCStr_String(text_out, scheme == file_GmLinkScheme ? "" : globe_Icon " ");
96 append_String(text_out, url); 97 appendCStrN_String(text_out, cstr_String(url), iMin(maxDispLen, size_String(url)));
98 if (size_String(url) > maxDispLen) {
99 appendCStr_String(text_out, "...");
100 }
97 } 101 }
98 else { 102 else {
99 appendCStr_String(text_out, "\x1b[1m"); 103 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 5e66f521..5f19ec3e 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -71,18 +71,19 @@ void endTarget_Paint(iPaint *d) {
71 71
72void setClip_Paint(iPaint *d, iRect rect) { 72void setClip_Paint(iPaint *d, iRect rect) {
73 addv_I2(&rect.pos, origin_Paint); 73 addv_I2(&rect.pos, origin_Paint);
74 if (isEmpty_Rect(rect)) {
75 rect = init_Rect(0, 0, 1, 1);
76 }
77 iRect targetRect = zero_Rect(); 74 iRect targetRect = zero_Rect();
78 SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d)); 75 SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d));
79 if (target) { 76 if (target) {
80 SDL_QueryTexture(target, NULL, NULL, &targetRect.size.x, &targetRect.size.y); 77 SDL_QueryTexture(target, NULL, NULL, &targetRect.size.x, &targetRect.size.y);
81 rect = intersect_Rect(rect, targetRect); 78 rect = intersect_Rect(rect, targetRect);
82 } 79 }
83 else { 80 /* The origin is non-zero when drawing into a widget's own buffer. */
81 if (isEqual_I2(zero_I2(), origin_Paint)) {
84 rect = intersect_Rect(rect, rect_Root(get_Root())); 82 rect = intersect_Rect(rect, rect_Root(get_Root()));
85 } 83 }
84 if (isEmpty_Rect(rect)) {
85 rect = init_Rect(0, 0, 1, 1);
86 }
86 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); 87 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect);
87} 88}
88 89
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/sidebarwidget.c b/src/ui/sidebarwidget.c
index 16677f9e..da377ac2 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -1181,39 +1181,43 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1181 argLabel_Command(cmd, "noanim") == 0 && 1181 argLabel_Command(cmd, "noanim") == 0 &&
1182 (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); 1182 (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType);
1183 int visX = 0; 1183 int visX = 0;
1184 int visY = 0; 1184// int visY = 0;
1185 if (isVisible_Widget(w)) { 1185 if (isVisible_Widget(w)) {
1186 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); 1186 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect);
1187 visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect); 1187// visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect);
1188 } 1188 }
1189 const iBool isHiding = isVisible_Widget(w); 1189 const iBool isHiding = isVisible_Widget(w);
1190 setFlags_Widget(w, hidden_WidgetFlag, isHiding); 1190 setFlags_Widget(w, hidden_WidgetFlag, isHiding);
1191 /* Safe area inset for mobile. */ 1191 /* Safe area inset for mobile. */
1192 const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); 1192 const int safePad =
1193 deviceType_App() == desktop_AppDeviceType
1194 ? 0
1195 : (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0);
1193 const int animFlags = easeOut_AnimFlag | softer_AnimFlag; 1196 const int animFlags = easeOut_AnimFlag | softer_AnimFlag;
1194 if (!isPortraitPhone_App()) { 1197 if (!isPortraitPhone_App()) {
1195 if (!isHiding) { 1198 if (!isHiding) {
1196 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); 1199 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse);
1197 w->rect.size.x = d->widthAsGaps * gap_UI; 1200 w->rect.size.x = d->widthAsGaps * gap_UI;
1198 invalidate_ListWidget(d->list); 1201 invalidate_ListWidget(d->list);
1199 if (isAnimated) { 1202 if (isAnimated) {
1200 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1203 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1201 setVisualOffset_Widget( 1204 setVisualOffset_Widget(w,
1202 w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); 1205 (d->side == left_SidebarSide ? -1 : 1) *
1206 (w->rect.size.x + safePad),
1207 0,
1208 0);
1203 setVisualOffset_Widget(w, 0, 300, animFlags); 1209 setVisualOffset_Widget(w, 0, 300, animFlags);
1210 }
1204 } 1211 }
1205 } 1212 else if (isAnimated) {
1206 else if (isAnimated) { 1213 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
1207 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 1214 if (d->side == right_SidebarSide) {
1208 if (d->side == right_SidebarSide) { 1215 setVisualOffset_Widget(w, visX, 0, 0);
1209 setVisualOffset_Widget(w, visX, 0, 0); 1216 setVisualOffset_Widget(w, visX + w->rect.size.x + safePad, 300, animFlags);
1210 setVisualOffset_Widget( 1217 }
1211 w, visX + w->rect.size.x + safePad, 300, animFlags); 1218 else {
1212 } 1219 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
1213 else { 1220 setVisualOffset_Widget(w, -w->rect.size.x - safePad, 300, animFlags);
1214 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
1215 setVisualOffset_Widget(
1216 w, -w->rect.size.x - safePad, 300, animFlags);
1217 } 1221 }
1218 } 1222 }
1219 setScrollMode_ListWidget(d->list, normal_ScrollMode); 1223 setScrollMode_ListWidget(d->list, normal_ScrollMode);
@@ -1226,15 +1230,16 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1226 w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight; 1230 w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight;
1227 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0); 1231 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0);
1228 setVisualOffset_Widget(w, 0, 300, animFlags); 1232 setVisualOffset_Widget(w, 0, 300, animFlags);
1229 //animateSlidingSheetHeight_SidebarWidget_(d); 1233 // animateSlidingSheetHeight_SidebarWidget_(d);
1230 setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); 1234 setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode);
1231 } 1235 }
1232 else { 1236 else {
1233 setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); 1237 setVisualOffset_Widget(
1238 w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags);
1234 if (d->isEditing) { 1239 if (d->isEditing) {
1235 setMobileEditMode_SidebarWidget_(d, iFalse); 1240 setMobileEditMode_SidebarWidget_(d, iFalse);
1241 }
1236 } 1242 }
1237 }
1238 showToolbar_Root(w->root, isHiding); 1243 showToolbar_Root(w->root, isHiding);
1239 } 1244 }
1240 updateToolbarColors_Root(w->root); 1245 updateToolbarColors_Root(w->root);
@@ -1242,7 +1247,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
1242 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ 1247 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */
1243 arrange_Widget(w); 1248 arrange_Widget(w);
1244 if (!isPortraitPhone_App()) { 1249 if (!isPortraitPhone_App()) {
1245 updateSize_DocumentWidget(document_App()); 1250 updateSize_DocumentWidget(document_App());
1246 } 1251 }
1247 if (isVisible_Widget(w)) { 1252 if (isVisible_Widget(w)) {
1248 updateItems_SidebarWidget_(d); 1253 updateItems_SidebarWidget_(d);
@@ -1387,6 +1392,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1387 } 1392 }
1388 } 1393 }
1389 } 1394 }
1395 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) {
1396 updateItems_SidebarWidget_(d);
1397 return iTrue;
1398 }
1390 else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide && 1399 else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide &&
1391 equal_Command(cmd, "swipe.forward")) { 1400 equal_Command(cmd, "swipe.forward")) {
1392 postCommand_App("sidebar.toggle"); 1401 postCommand_App("sidebar.toggle");
diff --git a/src/ui/text.c b/src/ui/text.c
index 200108ed..c19aed2f 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -246,12 +246,24 @@ 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// enum iTextFont contentFont; 261// enum iTextFont contentFont;
251// enum iTextFont headingFont; 262// enum iTextFont headingFont;
252 float contentFontSize; 263 float contentFontSize;
253 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */ 264 iArray fonts; /* fonts currently selected for use (incl. all styles/sizes) */
254 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */ 265 int overrideFontId; /* always checked for glyphs first, regardless of which font is used */
266 iArray fontPriorityOrder;
255 SDL_Renderer * render; 267 SDL_Renderer * render;
256 SDL_Texture * cache; 268 SDL_Texture * cache;
257 iInt2 cacheSize; 269 iInt2 cacheSize;
@@ -284,8 +296,9 @@ static void setupFontVariants_Text_(iText *d, const iFontSpec *spec, int baseId)
284 /* This is the highest priority override font. */ 296 /* This is the highest priority override font. */
285 d->overrideFontId = baseId; 297 d->overrideFontId = baseId;
286 } 298 }
299 pushBack_Array(&d->fontPriorityOrder, &(iPrioMapItem){ spec->priority, baseId });
287 for (enum iFontStyle style = 0; style < max_FontStyle; style++) { 300 for (enum iFontStyle style = 0; style < max_FontStyle; style++) {
288 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) { 301 for (enum iFontSize sizeId = 0; sizeId < max_FontSize; sizeId++) {
289 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)), 302 init_Font(font_Text_(FONT_ID(baseId, style, sizeId)),
290 spec, 303 spec,
291 spec->styles[style], 304 spec->styles[style],
@@ -322,6 +335,7 @@ static void initFonts_Text_(iText *d) {
322 and styles for each available font. Indices to `fonts` act as font runtime IDs. */ 335 and styles for each available font. Indices to `fonts` act as font runtime IDs. */
323 /* First the mandatory fonts. */ 336 /* First the mandatory fonts. */
324 d->overrideFontId = -1; 337 d->overrideFontId = -1;
338 clear_Array(&d->fontPriorityOrder);
325 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */ 339 resize_Array(&d->fonts, auxiliary_FontId); /* room for the built-ins */
326 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId); 340 setupFontVariants_Text_(d, tryFindSpec_(uiFont_PrefsString, "default"), default_FontId);
327 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId); 341 setupFontVariants_Text_(d, tryFindSpec_(monospaceFont_PrefsString, "iosevka"), monospace_FontId);
@@ -331,12 +345,14 @@ static void initFonts_Text_(iText *d) {
331 /* Check if there are auxiliary fonts available and set those up, too. */ 345 /* Check if there are auxiliary fonts available and set those up, too. */
332 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) { 346 iConstForEach(PtrArray, s, listSpecsByPriority_Fonts()) {
333 const iFontSpec *spec = s.ptr; 347 const iFontSpec *spec = s.ptr;
348// printf("spec '%s': prio=%d\n", cstr_String(&spec->name), spec->priority);
334 if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) { 349 if (spec->flags & (auxiliary_FontSpecFlag | user_FontSpecFlag)) {
335 const int fontId = size_Array(&d->fonts); 350 const int fontId = size_Array(&d->fonts);
336 resize_Array(&d->fonts, fontId + maxVariants_Fonts); 351 resize_Array(&d->fonts, fontId + maxVariants_Fonts);
337 setupFontVariants_Text_(d, spec, fontId); 352 setupFontVariants_Text_(d, spec, fontId);
338 } 353 }
339 } 354 }
355 sort_Array(&d->fontPriorityOrder, cmp_PrioMapItem_);
340#if !defined (NDEBUG) 356#if !defined (NDEBUG)
341 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts)); 357 printf("[Text] %zu font variants ready\n", size_Array(&d->fonts));
342#endif 358#endif
@@ -402,6 +418,7 @@ void init_Text(iText *d, SDL_Renderer *render) {
402 iText *oldActive = activeText_; 418 iText *oldActive = activeText_;
403 activeText_ = d; 419 activeText_ = d;
404 init_Array(&d->fonts, sizeof(iFont)); 420 init_Array(&d->fonts, sizeof(iFont));
421 init_Array(&d->fontPriorityOrder, sizeof(iPrioMapItem));
405 d->contentFontSize = contentScale_Text_; 422 d->contentFontSize = contentScale_Text_;
406 d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */); 423 d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */);
407 d->baseFontId = -1; 424 d->baseFontId = -1;
@@ -436,6 +453,7 @@ void deinit_Text(iText *d) {
436 deinitCache_Text_(d); 453 deinitCache_Text_(d);
437 d->render = NULL; 454 d->render = NULL;
438 iRelease(d->ansiEscape); 455 iRelease(d->ansiEscape);
456 deinit_Array(&d->fontPriorityOrder);
439 deinit_Array(&d->fonts); 457 deinit_Array(&d->fonts);
440} 458}
441 459
@@ -571,25 +589,24 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
571 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 589 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
572 return d; 590 return d;
573 } 591 }
574 /* As a fallback, check all other available fonts of this size. */ 592 /* As a fallback, check all other available fonts of this size in priority order. */
575 for (int aux = 0; aux < 2; aux++) { 593 iConstForEach(Array, i, &activeText_->fontPriorityOrder) {
576 for (iFont *font = font_Text_(FONT_ID(0, styleId, sizeId)); 594 iFont *font = font_Text_(FONT_ID(((const iPrioMapItem *) i.value)->fontIndex,
577 font < (iFont *) end_Array(&activeText_->fonts); 595 styleId, sizeId));
578 font += maxVariants_Fonts) { 596 if (font == d || font == overrideFont) {
579 const iBool isAuxiliary = (font->fontSpec->flags & auxiliary_FontSpecFlag) ? 1 : 0; 597 continue; /* already checked this one */
580 if (aux == isAuxiliary) { 598 }
581 /* First try auxiliary fonts, then other remaining fonts. */ 599 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) {
582 continue; 600#if 0
583 } 601 printf("using '%s' (pr:%d) for %lc (%x) => %d [missing in '%s']\n",
584 if (font == d || font == overrideFont) { 602 cstr_String(&font->fontSpec->id),
585 continue; /* already checked this one */ 603 font->fontSpec->priority,
586 } 604 (int) ch,
587 if ((*glyphIndex = glyphIndex_Font_(font, ch)) != 0) { 605 ch,
588// printf("using %s[%f] for %lc (%x) => %d\n", 606 glyphIndex_Font_(font, ch),
589// cstr_String(&font->fontSpec->name), font->fontSpec->scaling, 607 cstr_String(&d->fontSpec->id));
590// (int) ch, ch, glyphIndex_Font_(font, ch)); 608#endif
591 return font; 609 return font;
592 }
593 } 610 }
594 } 611 }
595 if (!*glyphIndex) { 612 if (!*glyphIndex) {
@@ -1433,7 +1450,7 @@ static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont)
1433 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) { 1450 if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) {
1434 const iChar ch = d->logicalText[info->cluster]; 1451 const iChar ch = d->logicalText[info->cluster];
1435 if (isPictograph_Char(ch) || isEmoji_Char(ch)) { 1452 if (isPictograph_Char(ch) || isEmoji_Char(ch)) {
1436 const float dw = d->font->xScale * d->glyphPos[i].x_advance - monoAdvance; 1453 const float dw = d->font->xScale * d->glyphPos[i].x_advance - (isEmoji_Char(ch) ? 2 : 1) * monoAdvance;
1437 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1; 1454 d->glyphPos[i].x_offset -= dw / 2 / d->font->xScale - 1;
1438 d->glyphPos[i].x_advance -= dw / d->font->xScale - 1; 1455 d->glyphPos[i].x_advance -= dw / d->font->xScale - 1;
1439 } 1456 }
diff --git a/src/ui/util.c b/src/ui/util.c
index 31907721..5dd8a0bd 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1778,7 +1778,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) {
1778 } 1778 }
1779 else if (equal_Command(cmd, "valueinput.set")) { 1779 else if (equal_Command(cmd, "valueinput.set")) {
1780 iInputWidget *input = findChild_Widget(dlg, "input"); 1780 iInputWidget *input = findChild_Widget(dlg, "input");
1781 setTextCStr_InputWidget(input, suffixPtr_Command(cmd, "text")); 1781 setTextUndoableCStr_InputWidget(input, suffixPtr_Command(cmd, "text"), iTrue);
1782 validate_InputWidget(input); 1782 validate_InputWidget(input);
1783 return iTrue; 1783 return iTrue;
1784 } 1784 }
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) {