diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 60 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 17 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 2 | ||||
-rw-r--r-- | src/ui/keys.c | 1 | ||||
-rw-r--r-- | src/ui/linkinfo.c | 6 | ||||
-rw-r--r-- | src/ui/mediaui.c | 4 | ||||
-rw-r--r-- | src/ui/paint.c | 9 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 20 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 61 | ||||
-rw-r--r-- | src/ui/text.c | 61 | ||||
-rw-r--r-- | src/ui/util.c | 2 | ||||
-rw-r--r-- | src/ui/widget.c | 4 | ||||
-rw-r--r-- | src/ui/window.c | 38 | ||||
-rw-r--r-- | src/ui/window.h | 2 |
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 | ||
3098 | static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | 3118 | static 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 | ||
3106 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 3128 | static 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 | ||
1117 | void setText_InputWidget(iInputWidget *d, const iString *text) { | 1117 | void setText_InputWidget(iInputWidget *d, const iString *text) { |
1118 | setTextUndoable_InputWidget(d, text, iFalse); | ||
1119 | } | ||
1120 | |||
1121 | void 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 | ||
1187 | void 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 | |||
1178 | void selectAll_InputWidget(iInputWidget *d) { | 1193 | void 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); | |||
46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); | 46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); |
47 | void setText_InputWidget (iInputWidget *, const iString *text); | 47 | void setText_InputWidget (iInputWidget *, const iString *text); |
48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | 48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); |
49 | void setTextUndoable_InputWidget (iInputWidget *, const iString *text, iBool isUndoable); | ||
50 | void setTextUndoableCStr_InputWidget (iInputWidget *, const char *cstr, iBool isUndoable); | ||
49 | void setFont_InputWidget (iInputWidget *, int fontId); | 51 | void setFont_InputWidget (iInputWidget *, int fontId); |
50 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ | 52 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ |
51 | void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines); | 53 | void 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 | ||
72 | void setClip_Paint(iPaint *d, iRect rect) { | 72 | void 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 | ||
249 | iDeclareType(PrioMapItem) | ||
250 | struct Impl_PrioMapItem { | ||
251 | int priority; | ||
252 | uint32_t fontIndex; | ||
253 | }; | ||
254 | |||
255 | static 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 | |||
249 | struct Impl_Text { | 260 | struct 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) { | |||
539 | void init_MainWindow(iMainWindow *d, iRect rect) { | 539 | void 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 | ||
639 | void deinit_MainWindow(iMainWindow *d) { | 649 | void 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) { | |||
682 | static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { | 695 | static 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 | ||
123 | iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { | 125 | iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { |