summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/certimportwidget.c8
-rw-r--r--src/ui/color.c86
-rw-r--r--src/ui/color.h42
-rw-r--r--src/ui/documentwidget.c374
-rw-r--r--src/ui/documentwidget.h8
-rw-r--r--src/ui/indicatorwidget.c2
-rw-r--r--src/ui/inputwidget.c41
-rw-r--r--src/ui/inputwidget.h27
-rw-r--r--src/ui/labelwidget.c57
-rw-r--r--src/ui/labelwidget.h6
-rw-r--r--src/ui/mediaui.c2
-rw-r--r--src/ui/mobile.c20
-rw-r--r--src/ui/root.c141
-rw-r--r--src/ui/root.h3
-rw-r--r--src/ui/sidebarwidget.c91
-rw-r--r--src/ui/sidebarwidget.h4
-rw-r--r--src/ui/text.c79
-rw-r--r--src/ui/text.h6
-rw-r--r--src/ui/touch.c98
-rw-r--r--src/ui/util.c104
-rw-r--r--src/ui/widget.c110
-rw-r--r--src/ui/widget.h17
-rw-r--r--src/ui/window.c8
23 files changed, 985 insertions, 349 deletions
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c
index e121b4d0..6e818137 100644
--- a/src/ui/certimportwidget.c
+++ b/src/ui/certimportwidget.c
@@ -215,6 +215,14 @@ static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Even
215 return iTrue; 215 return iTrue;
216 } 216 }
217 } 217 }
218 if (isCommand_UserEvent(ev, "input.paste")) {
219 if (!tryImportFromClipboard_CertImportWidget_(d)) {
220 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.pasted}",
221 "${dlg.certimport.notfound}");
222 }
223 postRefresh_App();
224 return iTrue;
225 }
218 if (isCommand_UserEvent(ev, "certimport.paste")) { 226 if (isCommand_UserEvent(ev, "certimport.paste")) {
219 tryImportFromClipboard_CertImportWidget_(d); 227 tryImportFromClipboard_CertImportWidget_(d);
220 return iTrue; 228 return iTrue;
diff --git a/src/ui/color.c b/src/ui/color.c
index a6ba18e4..05ec1f6f 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -24,11 +24,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
24#include "root.h" 24#include "root.h"
25#include "app.h" 25#include "app.h"
26 26
27#include <the_Foundation/file.h>
28#include <the_Foundation/path.h>
27#include <the_Foundation/string.h> 29#include <the_Foundation/string.h>
28 30
29static const iColor transparent_; 31static const iColor transparent_;
30 32
31static const iColor darkPalette_[] = { 33static iColor darkPalette_[] = {
32 { 0, 0, 0, 255 }, 34 { 0, 0, 0, 255 },
33 { 40, 40, 40, 255 }, 35 { 40, 40, 40, 255 },
34 { 80, 80, 80, 255 }, 36 { 80, 80, 80, 255 },
@@ -47,7 +49,7 @@ static const iColor darkPalette_[] = {
47 { 0, 200, 0, 255 }, 49 { 0, 200, 0, 255 },
48}; 50};
49 51
50static const iColor lightPalette_[] = { 52static iColor lightPalette_[] = {
51 { 0, 0, 0, 255 }, 53 { 0, 0, 0, 255 },
52 { 75, 75, 75, 255 }, 54 { 75, 75, 75, 255 },
53 { 150, 150, 150, 255 }, 55 { 150, 150, 150, 255 },
@@ -468,12 +470,12 @@ const char *escape_Color(int color) {
468 return esc[color]; 470 return esc[color];
469 } 471 }
470 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ 472 /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */
471 /* Double-\r is used for range extension. */ 473 /* Double-\v is used for range extension. */
472 if (color + asciiBase_ColorEscape > 127) { 474 if (color + asciiBase_ColorEscape > 127) {
473 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); 475 iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127);
474 return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); 476 return format_CStr("\v\v%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape);
475 } 477 }
476 return format_CStr("\r%c", color + asciiBase_ColorEscape); 478 return format_CStr("\v%c", color + asciiBase_ColorEscape);
477} 479}
478 480
479iHSLColor setSat_HSLColor(iHSLColor d, float sat) { 481iHSLColor setSat_HSLColor(iHSLColor d, float sat) {
@@ -804,3 +806,77 @@ iColor ansiForeground_Color(iRangecc escapeSequence, int fallback) {
804 } 806 }
805 return clr; 807 return clr;
806} 808}
809
810iBool loadPalette_Color(const char *path) {
811 iBool wasLoaded = iFalse;
812 iFile *f = newCStr_File(concatPath_CStr(path, "palette.txt"));
813 if (open_File(f, text_FileMode | readOnly_FileMode)) {
814 iColor *dstPal = darkPalette_;
815 iRangecc srcLine = iNullRange;
816 const iBlock *src = collect_Block(readAll_File(f));
817 while (nextSplit_Rangecc(range_Block(src), "\n", &srcLine)) {
818 iRangecc line = srcLine;
819 trim_Rangecc(&line);
820 if (isEmpty_Range(&line)) {
821 continue;
822 }
823 if (*line.start == '#') {
824 /* Control directive. */
825 line.start++;
826 trim_Rangecc(&line);
827 if (equalCase_Rangecc(line, "dark")) {
828 dstPal = darkPalette_;
829 }
830 else if (equalCase_Rangecc(line, "light")) {
831 dstPal = lightPalette_;
832 }
833 continue;
834 }
835 static const struct {
836 const char *label;
837 int paletteIndex;
838 } colors_[] = {
839 { "black:", 0 }, { "gray25:", 1 }, { "gray50:", 2 }, { "gray75:", 3 },
840 { "white:", 4 }, { "brown:", 5 }, { "orange:", 6 }, { "teal:", 7 },
841 { "cyan:", 8 }, { "yellow:", 9 }, { "red:", 10 }, { "magenta:", 11 },
842 { "blue:", 12 }, { "green:", 13 },
843 };
844 iForIndices(i, colors_) {
845 if (startsWithCase_Rangecc(line, colors_[i].label)) {
846 iColor *dst = &dstPal[colors_[i].paletteIndex];
847 line.start += strlen(colors_[i].label);
848 trim_Rangecc(&line);
849 if (!isEmpty_Range(&line)) {
850 if (*line.start == '#') {
851 /* Hexadecimal color. */
852 line.start++;
853 if (size_Range(&line) == 6) {
854 iBlock *vals = hexDecode_Rangecc(line);
855 iAssert(size_Block(vals) == 3);
856 const uint8_t *rgb = constData_Block(vals);
857 *dst = (iColor){ rgb[0], rgb[1], rgb[2], 255 };
858 delete_Block(vals);
859 }
860 else {
861 fprintf(stderr, "[Color] invalid custom color: %s\n",
862 cstr_Rangecc(line));
863 }
864 }
865 else {
866 unsigned int red = 0, green = 0, blue = 0;
867 sscanf(line.start, "%u %u %u", &red, &green, &blue);
868 if (red > 255 || green > 255 || blue > 255) {
869 fprintf(stderr, "[Color] RGB value(s) out of range: %s\n",
870 cstr_Rangecc(line));
871 }
872 *dst = (iColor){ red, green, blue, 255 };
873 }
874 }
875 }
876 }
877 }
878 wasLoaded = iTrue;
879 }
880 iRelease(f);
881 return wasLoaded;
882}
diff --git a/src/ui/color.h b/src/ui/color.h
index d2fa3c00..37ec49eb 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -187,26 +187,26 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
187#define asciiBase_ColorEscape 33 187#define asciiBase_ColorEscape 33
188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) 188#define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape)
189 189
190#define restore_ColorEscape "\r\x24" /* ASCII Cancel */ 190#define restore_ColorEscape "\v\x24" /* ASCII Cancel */
191#define black_ColorEscape "\r!" 191#define black_ColorEscape "\v!"
192#define gray25_ColorEscape "\r\"" 192#define gray25_ColorEscape "\v\""
193#define gray50_ColorEscape "\r#" 193#define gray50_ColorEscape "\v#"
194#define gray75_ColorEscape "\r$" 194#define gray75_ColorEscape "\v$"
195#define white_ColorEscape "\r%" 195#define white_ColorEscape "\v%"
196#define brown_ColorEscape "\r&" 196#define brown_ColorEscape "\v&"
197#define orange_ColorEscape "\r'" 197#define orange_ColorEscape "\v'"
198#define teal_ColorEscape "\r(" 198#define teal_ColorEscape "\v("
199#define cyan_ColorEscape "\r)" 199#define cyan_ColorEscape "\v)"
200#define yellow_ColorEscape "\r*" 200#define yellow_ColorEscape "\v*"
201#define red_ColorEscape "\r+" 201#define red_ColorEscape "\v+"
202#define magenta_ColorEscape "\r," 202#define magenta_ColorEscape "\v,"
203#define blue_ColorEscape "\r-" 203#define blue_ColorEscape "\v-"
204#define green_ColorEscape "\r." 204#define green_ColorEscape "\v."
205#define uiText_ColorEscape "\r4" 205#define uiText_ColorEscape "\v4"
206#define uiTextAction_ColorEscape "\r<" 206#define uiTextAction_ColorEscape "\v<"
207#define uiTextCaution_ColorEscape "\r=" 207#define uiTextCaution_ColorEscape "\v="
208#define uiTextStrong_ColorEscape "\r:" 208#define uiTextStrong_ColorEscape "\v:"
209#define uiHeading_ColorEscape "\rR" 209#define uiHeading_ColorEscape "\vR"
210 210
211iDeclareType(Color) 211iDeclareType(Color)
212iDeclareType(HSLColor) 212iDeclareType(HSLColor)
@@ -244,7 +244,9 @@ iLocalDef void setHsl_Color(int color, iHSLColor hsl) {
244 set_Color(color, rgb_HSLColor(hsl)); 244 set_Color(color, rgb_HSLColor(hsl));
245} 245}
246 246
247iBool loadPalette_Color (const char *path);
247void setThemePalette_Color (enum iColorTheme theme); 248void setThemePalette_Color (enum iColorTheme theme);
248 249
249iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); 250iColor ansiForeground_Color (iRangecc escapeSequence, int fallback);
250const char * escape_Color (int color); 251const char * escape_Color (int color);
252
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 6184a75a..cb1fde28 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -130,7 +130,8 @@ void deinit_PersistentDocumentState(iPersistentDocumentState *d) {
130 130
131void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) { 131void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) {
132 serialize_String(d->url, outs); 132 serialize_String(d->url, outs);
133 writeU16_Stream(outs, d->reloadInterval & 7); 133 uint16_t params = d->reloadInterval & 7;
134 writeU16_Stream(outs, params);
134 serialize_History(d->history, outs); 135 serialize_History(d->history, outs);
135} 136}
136 137
@@ -223,6 +224,7 @@ enum iDocumentWidgetFlag {
223 movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), 224 movingSelectMarkEnd_DocumentWidgetFlag = iBit(11),
224 otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ 225 otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */
225 urlChanged_DocumentWidgetFlag = iBit(13), 226 urlChanged_DocumentWidgetFlag = iBit(13),
227 openedFromSidebar_DocumentWidgetFlag = iBit(14),
226}; 228};
227 229
228enum iDocumentLinkOrdinalMode { 230enum iDocumentLinkOrdinalMode {
@@ -315,6 +317,10 @@ void init_DocumentWidget(iDocumentWidget *d) {
315 init_Widget(w); 317 init_Widget(w);
316 setId_Widget(w, format_CStr("document%03d", ++docEnum_)); 318 setId_Widget(w, format_CStr("document%03d", ++docEnum_));
317 setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); 319 setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue);
320 if (deviceType_App() != desktop_AppDeviceType) {
321 setFlags_Widget(w, leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag |
322 horizontalOffset_WidgetFlag, iTrue);
323 }
318 init_PersistentDocumentState(&d->mod); 324 init_PersistentDocumentState(&d->mod);
319 d->flags = 0; 325 d->flags = 0;
320 d->phoneToolbar = NULL; 326 d->phoneToolbar = NULL;
@@ -392,6 +398,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
392} 398}
393 399
394void deinit_DocumentWidget(iDocumentWidget *d) { 400void deinit_DocumentWidget(iDocumentWidget *d) {
401 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
395 removeTicker_App(animate_DocumentWidget_, d); 402 removeTicker_App(animate_DocumentWidget_, d);
396 removeTicker_App(prerender_DocumentWidget_, d); 403 removeTicker_App(prerender_DocumentWidget_, d);
397 remove_Periodic(periodic_App(), d); 404 remove_Periodic(periodic_App(), d);
@@ -976,6 +983,9 @@ static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) {
976} 983}
977 984
978static void invalidate_DocumentWidget_(iDocumentWidget *d) { 985static void invalidate_DocumentWidget_(iDocumentWidget *d) {
986 if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) {
987 return;
988 }
979 invalidate_VisBuf(d->visBuf); 989 invalidate_VisBuf(d->visBuf);
980 clear_PtrSet(d->invalidRuns); 990 clear_PtrSet(d->invalidRuns);
981} 991}
@@ -1016,9 +1026,7 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) {
1016 isPinned_DocumentWidget_(d)); 1026 isPinned_DocumentWidget_(d));
1017} 1027}
1018 1028
1019void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { 1029static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) {
1020 setUrl_GmDocument(d->doc, d->mod.url);
1021 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
1022 documentRunsInvalidated_DocumentWidget_(d); 1030 documentRunsInvalidated_DocumentWidget_(d);
1023 updateWindowTitle_DocumentWidget_(d); 1031 updateWindowTitle_DocumentWidget_(d);
1024 updateVisible_DocumentWidget_(d); 1032 updateVisible_DocumentWidget_(d);
@@ -1035,6 +1043,26 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1035 } 1043 }
1036 } 1044 }
1037 showOrHidePinningIndicator_DocumentWidget_(d); 1045 showOrHidePinningIndicator_DocumentWidget_(d);
1046 setCachedDocument_History(d->mod.history,
1047 d->doc, /* keeps a ref */
1048 (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1049}
1050
1051void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
1052 setUrl_GmDocument(d->doc, d->mod.url);
1053 setSource_GmDocument(d->doc,
1054 source,
1055 documentWidth_DocumentWidget_(d),
1056 isFinished_GmRequest(d->request) ? final_GmDocumentUpdate
1057 : partial_GmDocumentUpdate);
1058 documentWasChanged_DocumentWidget_(d);
1059}
1060
1061static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) {
1062 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1063 iRelease(d->doc);
1064 d->doc = ref_Object(newDoc);
1065 documentWasChanged_DocumentWidget_(d);
1038} 1066}
1039 1067
1040static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 1068static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
@@ -1143,16 +1171,22 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
1143 { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, 1171 { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } },
1144 2); 1172 2);
1145 } 1173 }
1146 setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); 1174 /* Make a new document for the error page.*/ {
1147 setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); 1175 iGmDocument *errorDoc = new_GmDocument();
1176 setUrl_GmDocument(errorDoc, d->mod.url);
1177 setBanner_GmDocument(errorDoc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner);
1178 setFormat_GmDocument(errorDoc, gemini_SourceFormat);
1179 replaceDocument_DocumentWidget_(d, errorDoc);
1180 iRelease(errorDoc);
1181 }
1148 translate_Lang(src); 1182 translate_Lang(src);
1183 d->state = ready_RequestState;
1149 setSource_DocumentWidget(d, src); 1184 setSource_DocumentWidget(d, src);
1150 updateTheme_DocumentWidget_(d); 1185 updateTheme_DocumentWidget_(d);
1151 reset_SmoothScroll(&d->scrollY); 1186 reset_SmoothScroll(&d->scrollY);
1152 init_Anim(&d->sideOpacity, 0); 1187 init_Anim(&d->sideOpacity, 0);
1153 init_Anim(&d->altTextOpacity, 0); 1188 init_Anim(&d->altTextOpacity, 0);
1154 resetWideRuns_DocumentWidget_(d); 1189 resetWideRuns_DocumentWidget_(d);
1155 d->state = ready_RequestState;
1156} 1190}
1157 1191
1158static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { 1192static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) {
@@ -1264,9 +1298,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1264 2); 1298 2);
1265 } 1299 }
1266 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { 1300 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1267 redoLayout_GmDocument(d->doc); 1301 redoLayout_GmDocument(d->doc);
1268 updateVisible_DocumentWidget_(d); 1302 updateVisible_DocumentWidget_(d);
1269 invalidate_DocumentWidget_(d); 1303 invalidate_DocumentWidget_(d);
1270 } 1304 }
1271 } 1305 }
1272 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1306 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
@@ -1348,7 +1382,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool
1348 } 1382 }
1349} 1383}
1350 1384
1351static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, 1385static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1386 const iGmResponse *response,
1387 iGmDocument *cachedDoc,
1352 const iBool isInitialUpdate) { 1388 const iBool isInitialUpdate) {
1353 if (d->state == ready_RequestState) { 1389 if (d->state == ready_RequestState) {
1354 return; 1390 return;
@@ -1371,7 +1407,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1371 if (isSuccess_GmStatusCode(statusCode)) { 1407 if (isSuccess_GmStatusCode(statusCode)) {
1372 /* Check the MIME type. */ 1408 /* Check the MIME type. */
1373 iRangecc charset = range_CStr("utf-8"); 1409 iRangecc charset = range_CStr("utf-8");
1374 enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; 1410 enum iSourceFormat docFormat = undefined_SourceFormat;
1375 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ 1411 const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */
1376 set_String(&d->sourceMime, mimeStr); 1412 set_String(&d->sourceMime, mimeStr);
1377 iRangecc mime = range_String(mimeStr); 1413 iRangecc mime = range_String(mimeStr);
@@ -1380,20 +1416,20 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1380 iRangecc param = seg; 1416 iRangecc param = seg;
1381 trim_Rangecc(&param); 1417 trim_Rangecc(&param);
1382 if (equal_Rangecc(param, "text/gemini")) { 1418 if (equal_Rangecc(param, "text/gemini")) {
1383 docFormat = gemini_GmDocumentFormat; 1419 docFormat = gemini_SourceFormat;
1384 setRange_String(&d->sourceMime, param); 1420 setRange_String(&d->sourceMime, param);
1385 } 1421 }
1386 else if (startsWith_Rangecc(param, "text/") || 1422 else if (startsWith_Rangecc(param, "text/") ||
1387 equal_Rangecc(param, "application/json") || 1423 equal_Rangecc(param, "application/json") ||
1388 equal_Rangecc(param, "application/x-pem-file") || 1424 equal_Rangecc(param, "application/x-pem-file") ||
1389 equal_Rangecc(param, "application/pem-certificate-chain")) { 1425 equal_Rangecc(param, "application/pem-certificate-chain")) {
1390 docFormat = plainText_GmDocumentFormat; 1426 docFormat = plainText_SourceFormat;
1391 setRange_String(&d->sourceMime, param); 1427 setRange_String(&d->sourceMime, param);
1392 } 1428 }
1393 else if (equal_Rangecc(param, "application/zip") || 1429 else if (equal_Rangecc(param, "application/zip") ||
1394 (startsWith_Rangecc(param, "application/") && 1430 (startsWith_Rangecc(param, "application/") &&
1395 endsWithCase_Rangecc(param, "+zip"))) { 1431 endsWithCase_Rangecc(param, "+zip"))) {
1396 docFormat = gemini_GmDocumentFormat; 1432 docFormat = gemini_SourceFormat;
1397 setRange_String(&d->sourceMime, param); 1433 setRange_String(&d->sourceMime, param);
1398 iString *key = collectNew_String(); 1434 iString *key = collectNew_String();
1399 toString_Sym(SDLK_s, KMOD_PRIMARY, key); 1435 toString_Sym(SDLK_s, KMOD_PRIMARY, key);
@@ -1420,9 +1456,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1420 startsWith_Rangecc(param, "audio/")) { 1456 startsWith_Rangecc(param, "audio/")) {
1421 const iBool isAudio = startsWith_Rangecc(param, "audio/"); 1457 const iBool isAudio = startsWith_Rangecc(param, "audio/");
1422 /* Make a simple document with an image or audio player. */ 1458 /* Make a simple document with an image or audio player. */
1423 docFormat = gemini_GmDocumentFormat; 1459 docFormat = gemini_SourceFormat;
1424 setRange_String(&d->sourceMime, param); 1460 setRange_String(&d->sourceMime, param);
1425 const iGmLinkId imgLinkId = 1; /* there's only the one link */ 1461 const iGmLinkId imgLinkId = 1; /* there's only the one link */
1462 /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */
1426 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { 1463 if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) {
1427 const char *linkTitle = 1464 const char *linkTitle =
1428 startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; 1465 startsWith_String(mimeStr, "image/") ? "Image" : "Audio";
@@ -1432,7 +1469,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1432 linkTitle = 1469 linkTitle =
1433 baseName_Path(collect_String(newRange_String(parts.path))).start; 1470 baseName_Path(collect_String(newRange_String(parts.path))).start;
1434 } 1471 }
1435 format_String(&str, "=> %s %s\n", cstr_String(d->mod.url), linkTitle); 1472 format_String(&str, "=> %s %s\n",
1473 cstr_String(withSpacesEncoded_String(d->mod.url)),
1474 linkTitle);
1436 setData_Media(media_GmDocument(d->doc), 1475 setData_Media(media_GmDocument(d->doc),
1437 imgLinkId, 1476 imgLinkId,
1438 mimeStr, 1477 mimeStr,
@@ -1464,7 +1503,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1464 } 1503 }
1465 } 1504 }
1466 } 1505 }
1467 if (docFormat == undefined_GmDocumentFormat) { 1506 if (docFormat == undefined_SourceFormat) {
1468 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); 1507 showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta);
1469 deinit_String(&str); 1508 deinit_String(&str);
1470 return; 1509 return;
@@ -1476,7 +1515,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
1476 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); 1515 collect_String(decode_Block(&str.chars, cstr_Rangecc(charset))));
1477 } 1516 }
1478 } 1517 }
1479 if (setSource) { 1518 if (cachedDoc) {
1519 replaceDocument_DocumentWidget_(d, cachedDoc);
1520 }
1521 else if (setSource) {
1480 setSource_DocumentWidget(d, &str); 1522 setSource_DocumentWidget(d, &str);
1481 } 1523 }
1482 deinit_String(&str); 1524 deinit_String(&str);
@@ -1556,14 +1598,16 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) {
1556} 1598}
1557 1599
1558static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, 1600static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY,
1559 const iGmResponse *resp) { 1601 const iGmResponse *resp, iGmDocument *cachedDoc) {
1560 setLinkNumberMode_DocumentWidget_(d, iFalse); 1602 setLinkNumberMode_DocumentWidget_(d, iFalse);
1561 clear_ObjectList(d->media); 1603 clear_ObjectList(d->media);
1562 delete_Gempub(d->sourceGempub); 1604 delete_Gempub(d->sourceGempub);
1563 d->sourceGempub = NULL; 1605 d->sourceGempub = NULL;
1564 reset_GmDocument(d->doc); 1606 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1607 iRelease(d->doc);
1565 destroy_Widget(d->footerButtons); 1608 destroy_Widget(d->footerButtons);
1566 d->footerButtons = NULL; 1609 d->footerButtons = NULL;
1610 d->doc = new_GmDocument();
1567 resetWideRuns_DocumentWidget_(d); 1611 resetWideRuns_DocumentWidget_(d);
1568 d->state = fetching_RequestState; 1612 d->state = fetching_RequestState;
1569 /* Do the fetch. */ { 1613 /* Do the fetch. */ {
@@ -1574,10 +1618,12 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1574 d->sourceStatus = success_GmStatusCode; 1618 d->sourceStatus = success_GmStatusCode;
1575 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); 1619 format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached"));
1576 set_Block(&d->sourceContent, &resp->body); 1620 set_Block(&d->sourceContent, &resp->body);
1577 updateDocument_DocumentWidget_(d, resp, iTrue); 1621 updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue);
1578 postProcessRequestContent_DocumentWidget_(d, iTrue); 1622// setCachedDocument_History(d->mod.history, d->doc,
1623// (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0);
1579 } 1624 }
1580 d->state = ready_RequestState; 1625 d->state = ready_RequestState;
1626 postProcessRequestContent_DocumentWidget_(d, iTrue);
1581 init_Anim(&d->altTextOpacity, 0); 1627 init_Anim(&d->altTextOpacity, 0);
1582 reset_SmoothScroll(&d->scrollY); 1628 reset_SmoothScroll(&d->scrollY);
1583 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); 1629 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y);
@@ -1587,13 +1633,18 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n
1587 cacheDocumentGlyphs_DocumentWidget_(d); 1633 cacheDocumentGlyphs_DocumentWidget_(d);
1588 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; 1634 d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
1589 d->flags &= ~urlChanged_DocumentWidgetFlag; 1635 d->flags &= ~urlChanged_DocumentWidgetFlag;
1590 postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 1636 postCommandf_Root(
1637 as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
1591} 1638}
1592 1639
1593static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { 1640static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
1594 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); 1641 const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url));
1595 if (recent && recent->cachedResponse) { 1642 if (recent && recent->cachedResponse) {
1596 updateFromCachedResponse_DocumentWidget_(d, recent->normScrollY, recent->cachedResponse); 1643 iChangeFlags(d->flags,
1644 openedFromSidebar_DocumentWidgetFlag,
1645 recent->flags.openedFromSidebar);
1646 updateFromCachedResponse_DocumentWidget_(
1647 d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc);
1597 return iTrue; 1648 return iTrue;
1598 } 1649 }
1599 else if (!isEmpty_String(d->mod.url)) { 1650 else if (!isEmpty_String(d->mod.url)) {
@@ -1634,7 +1685,7 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
1634 if (deviceType_App() == phone_AppDeviceType) { 1685 if (deviceType_App() == phone_AppDeviceType) {
1635 const float normPos = normScrollPos_DocumentWidget_(d); 1686 const float normPos = normScrollPos_DocumentWidget_(d);
1636 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { 1687 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) {
1637 showToolbars_Root(as_Widget(d)->root, offset < 0); 1688 showToolbar_Root(as_Widget(d)->root, offset < 0);
1638 } 1689 }
1639 } 1690 }
1640 updateVisible_DocumentWidget_(d); 1691 updateVisible_DocumentWidget_(d);
@@ -1843,13 +1894,15 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1843 /* Keep scroll position when reloading the same page. */ 1894 /* Keep scroll position when reloading the same page. */
1844 reset_SmoothScroll(&d->scrollY); 1895 reset_SmoothScroll(&d->scrollY);
1845 } 1896 }
1846 reset_GmDocument(d->doc); /* new content incoming */ 1897 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
1898 iRelease(d->doc); /* new content incoming */
1899 d->doc = new_GmDocument();
1847 delete_Gempub(d->sourceGempub); 1900 delete_Gempub(d->sourceGempub);
1848 d->sourceGempub = NULL; 1901 d->sourceGempub = NULL;
1849 destroy_Widget(d->footerButtons); 1902 destroy_Widget(d->footerButtons);
1850 d->footerButtons = NULL; 1903 d->footerButtons = NULL;
1851 resetWideRuns_DocumentWidget_(d); 1904 resetWideRuns_DocumentWidget_(d);
1852 updateDocument_DocumentWidget_(d, resp, iTrue); 1905 updateDocument_DocumentWidget_(d, resp, NULL, iTrue);
1853 break; 1906 break;
1854 case categoryRedirect_GmStatusCode: 1907 case categoryRedirect_GmStatusCode:
1855 if (isEmpty_String(&resp->meta)) { 1908 if (isEmpty_String(&resp->meta)) {
@@ -1900,7 +1953,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1900 switch (category_GmStatusCode(statusCode)) { 1953 switch (category_GmStatusCode(statusCode)) {
1901 case categorySuccess_GmStatusCode: 1954 case categorySuccess_GmStatusCode:
1902 /* More content available. */ 1955 /* More content available. */
1903 updateDocument_DocumentWidget_(d, resp, iFalse); 1956 updateDocument_DocumentWidget_(d, resp, NULL, iFalse);
1904 break; 1957 break;
1905 default: 1958 default:
1906 break; 1959 break;
@@ -2085,16 +2138,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime,
2085 exportDownloadedFile_iOS(savePath); 2138 exportDownloadedFile_iOS(savePath);
2086#else 2139#else
2087 if (showDialog) { 2140 if (showDialog) {
2088 const iMenuItem items[2] = { 2141 const iMenuItem items[2] = {
2089 { "${dlg.save.opendownload}", 0, 0, 2142 { "${dlg.save.opendownload}", 0, 0,
2090 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, 2143 format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) },
2091 { "${dlg.message.ok}", 0, 0, "message.ok" }, 2144 { "${dlg.message.ok}", 0, 0, "message.ok" },
2092 }; 2145 };
2093 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", 2146 makeMessage_Widget(uiHeading_ColorEscape "${heading.save}",
2094 format_CStr("%s\n${dlg.save.size} %.3f %s", 2147 format_CStr("%s\n${dlg.save.size} %.3f %s",
2095 cstr_String(path_File(f)), 2148 cstr_String(path_File(f)),
2096 isMega ? size / 1.0e6f : (size / 1.0e3f), 2149 isMega ? size / 1.0e6f : (size / 1.0e3f),
2097 isMega ? "${mb}" : "${kb}"), 2150 isMega ? "${mb}" : "${kb}"),
2098 items, 2151 items,
2099 iElemCount(items)); 2152 iElemCount(items));
2100 } 2153 }
@@ -2208,6 +2261,147 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2208 return iTrue; 2261 return iTrue;
2209} 2262}
2210 2263
2264static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc,
2265 iDocumentWidget *swapBuffersWith) {
2266 if (doc) {
2267 iAssert(isInstance_Object(doc, &Class_GmDocument));
2268 iGmDocument *copy = ref_Object(doc);
2269 iRelease(d->doc);
2270 d->doc = copy;
2271 d->scrollY = swapBuffersWith->scrollY;
2272 updateVisible_DocumentWidget_(d);
2273 iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf);
2274 iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta);
2275 iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs);
2276 invalidate_DocumentWidget_(swapBuffersWith);
2277 }
2278}
2279
2280static iWidget *swipeParent_DocumentWidget_(iDocumentWidget *d) {
2281 return findChild_Widget(as_Widget(d)->root->widget, "doctabs");
2282}
2283
2284static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2285 iWidget *w = as_Widget(d);
2286 /* Swipe animations are rather complex and utilize both cached GmDocument content
2287 and temporary DocumentWidgets. Depending on the swipe direction, this DocumentWidget
2288 may wait until the finger is released to actually perform the navigation action. */
2289 if (equal_Command(cmd, "edgeswipe.moved")) {
2290 //printf("[%p] responds to edgeswipe.moved\n", d);
2291 as_Widget(d)->offsetRef = NULL;
2292 const int side = argLabel_Command(cmd, "side");
2293 const int offset = arg_Command(cmd);
2294 if (side == 1) { /* left edge */
2295 if (atOldest_History(d->mod.history)) {
2296 return iTrue;
2297 }
2298 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2299 /* The temporary "swipeIn" will display the previous page until the finger is lifted. */
2300 iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein");
2301 if (!swipeIn) {
2302 const iBool sidebarSwipe = (isPortraitPhone_App() &&
2303 d->flags & openedFromSidebar_DocumentWidgetFlag &&
2304 !isVisible_Widget(findWidget_App("sidebar")));
2305 swipeIn = new_DocumentWidget();
2306 setId_Widget(as_Widget(swipeIn), "swipein");
2307 setFlags_Widget(as_Widget(swipeIn),
2308 disabled_WidgetFlag | refChildrenOffset_WidgetFlag |
2309 fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2310 swipeIn->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos));
2311 swipeIn->widget.rect.size = d->widget.rect.size;
2312 swipeIn->widget.offsetRef = parent_Widget(w);
2313 if (!sidebarSwipe) {
2314 iRecentUrl *recent = new_RecentUrl();
2315 preceding_History(d->mod.history, recent);
2316 if (recent->cachedDoc) {
2317 iChangeRef(swipeIn->doc, recent->cachedDoc);
2318 updateScrollMax_DocumentWidget_(d);
2319 setValue_Anim(&swipeIn->scrollY.pos, size_GmDocument(swipeIn->doc).y * recent->normScrollY, 0);
2320 updateVisible_DocumentWidget_(swipeIn);
2321 swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag;
2322 }
2323 delete_RecentUrl(recent);
2324 }
2325 addChildPos_Widget(swipeParent, iClob(swipeIn), front_WidgetAddPos);
2326 }
2327 }
2328 if (side == 2) { /* right edge */
2329 if (offset < -get_Window()->pixelRatio * 10) {
2330 int animSpan = 10;
2331 if (!atLatest_History(d->mod.history) &&
2332 ~flags_Widget(w) & dragged_WidgetFlag) {
2333 animSpan = 0;
2334 postCommand_Widget(d, "navigate.forward");
2335 setFlags_Widget(w, dragged_WidgetFlag, iTrue);
2336 /* Set up the swipe dummy. */
2337 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2338 iDocumentWidget *target = new_DocumentWidget();
2339 setId_Widget(as_Widget(target), "swipeout");
2340 /* The target takes the old document and jumps on top. */
2341 target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos));
2342 target->widget.rect.size = d->widget.rect.size;
2343 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2344 swap_DocumentWidget_(target, d->doc, d);
2345 addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos);
2346 setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue);
2347 as_Widget(target)->offsetRef = parent_Widget(w);
2348 destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */
2349 }
2350 if (flags_Widget(w) & dragged_WidgetFlag) {
2351 setVisualOffset_Widget(w, width_Widget(w) +
2352 width_Widget(d) * offset / size_Root(w->root).x,
2353 animSpan, 0);
2354 }
2355 else {
2356 setVisualOffset_Widget(w, offset / 4, animSpan, 0);
2357 }
2358 }
2359 return iTrue;
2360 }
2361 }
2362 if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 2) {
2363 if (argLabel_Command(cmd, "abort") && flags_Widget(w) & dragged_WidgetFlag) {
2364 postCommand_Widget(d, "navigate.back");
2365 }
2366 setFlags_Widget(w, dragged_WidgetFlag, iFalse);
2367 setVisualOffset_Widget(w, 0, 100, 0);
2368 return iTrue;
2369 }
2370 if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 1) {
2371 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2372 iWidget *swipeIn = findChild_Widget(swipeParent, "swipein");
2373 if (swipeIn) {
2374 swipeIn->offsetRef = NULL;
2375 destroy_Widget(swipeIn);
2376 }
2377 }
2378 if (equal_Command(cmd, "swipe.back")) {
2379 if (atOldest_History(d->mod.history)) {
2380 setVisualOffset_Widget(w, 0, 100, 0);
2381 return iTrue;
2382 }
2383 iWidget *swipeParent = swipeParent_DocumentWidget_(d);
2384 iDocumentWidget *target = new_DocumentWidget();
2385 setId_Widget(as_Widget(target), "swipeout");
2386 /* The target takes the old document and jumps on top. */
2387 target->widget.rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2()));
2388 /* Note: `innerToWindow_Widget` does not apply visual offset. */
2389 target->widget.rect.size = w->rect.size;
2390 setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
2391 swap_DocumentWidget_(target, d->doc, d);
2392 addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos);
2393 setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue);
2394 as_Widget(d)->offsetRef = swipeParent;
2395 setVisualOffset_Widget(as_Widget(target), value_Anim(&w->visualOffset), 0, 0);
2396 setVisualOffset_Widget(as_Widget(target), width_Widget(target), 150, 0);
2397 destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */
2398 setVisualOffset_Widget(w, 0, 0, 0);
2399 postCommand_Widget(d, "navigate.back");
2400 return iTrue;
2401 }
2402 return iFalse;
2403}
2404
2211static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2405static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
2212 iWidget *w = as_Widget(d); 2406 iWidget *w = as_Widget(d);
2213 if (equal_Command(cmd, "document.openurls.changed")) { 2407 if (equal_Command(cmd, "document.openurls.changed")) {
@@ -2256,6 +2450,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2256 return iFalse; 2450 return iFalse;
2257 } 2451 }
2258 else if (equal_Command(cmd, "theme.changed") && document_App() == d) { 2452 else if (equal_Command(cmd, "theme.changed") && document_App() == d) {
2453// invalidateTheme_History(d->mod.history); /* cached colors */
2259 updateTheme_DocumentWidget_(d); 2454 updateTheme_DocumentWidget_(d);
2260 updateVisible_DocumentWidget_(d); 2455 updateVisible_DocumentWidget_(d);
2261 updateTrust_DocumentWidget_(d, NULL); 2456 updateTrust_DocumentWidget_(d, NULL);
@@ -2345,6 +2540,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2345 msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent))); 2540 msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent)));
2346 } 2541 }
2347 } 2542 }
2543 /* TODO: On mobile, omit the CA status. */
2348 appendFormat_String( 2544 appendFormat_String(
2349 msg, 2545 msg,
2350 "\n%s${pageinfo.cert.status}\n" 2546 "\n%s${pageinfo.cert.status}\n"
@@ -2649,6 +2845,18 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2649 return iTrue; 2845 return iTrue;
2650 } 2846 }
2651 else if (equal_Command(cmd, "navigate.back") && document_App() == d) { 2847 else if (equal_Command(cmd, "navigate.back") && document_App() == d) {
2848 if (isPortraitPhone_App()) {
2849 if (d->flags & openedFromSidebar_DocumentWidgetFlag &&
2850 !isVisible_Widget(findWidget_App("sidebar"))) {
2851 postCommand_App("sidebar.toggle");
2852 showToolbar_Root(get_Root(), iTrue);
2853#if defined (iPlatformAppleMobile)
2854 playHapticEffect_iOS(gentleTap_HapticEffect);
2855#endif
2856 return iTrue;
2857 }
2858 d->flags &= ~openedFromSidebar_DocumentWidgetFlag;
2859 }
2652 if (d->request) { 2860 if (d->request) {
2653 postCommandf_Root(w->root, 2861 postCommandf_Root(w->root,
2654 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 2862 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
@@ -2811,7 +3019,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2811 uiHeading_ColorEscape "${heading.import.bookmarks}", 3019 uiHeading_ColorEscape "${heading.import.bookmarks}",
2812 formatCStrs_Lang("dlg.import.found.n", count), 3020 formatCStrs_Lang("dlg.import.found.n", count),
2813 (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, 3021 (iMenuItem[]){ { "${cancel}", 0, 0, NULL },
2814 { format_CStr(cstrCount_Lang("dlg.import.add.n", count), 3022 { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count),
2815 uiTextAction_ColorEscape, 3023 uiTextAction_ColorEscape,
2816 count), 3024 count),
2817 0, 3025 0,
@@ -2870,6 +3078,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2870 else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) { 3078 else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) {
2871 return handlePinch_DocumentWidget_(d, cmd); 3079 return handlePinch_DocumentWidget_(d, cmd);
2872 } 3080 }
3081 else if ((startsWith_CStr(cmd, "edgeswipe.") || startsWith_CStr(cmd, "swipe.")) &&
3082 document_App() == d) {
3083 return handleSwipe_DocumentWidget_(d, cmd);
3084 }
2873 return iFalse; 3085 return iFalse;
2874} 3086}
2875 3087
@@ -3318,6 +3530,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3318 d->contextLink->linkId) }); 3530 d->contextLink->linkId) });
3319 } 3531 }
3320 } 3532 }
3533 if (equalCase_Rangecc(scheme, "file")) {
3534 /* Local files may be deleted. */
3535 pushBack_Array(
3536 &items,
3537 &(iMenuItem){ delete_Icon " " uiTextCaution_ColorEscape
3538 "${link.file.delete}",
3539 0,
3540 0,
3541 format_CStr("!file.delete confirm:1 path:%s",
3542 cstrCollect_String(
3543 localFilePathFromUrl_String(linkUrl))) });
3544 }
3321 } 3545 }
3322 else if (deviceType_App() == desktop_AppDeviceType) { 3546 else if (deviceType_App() == desktop_AppDeviceType) {
3323 if (!isEmpty_Range(&d->selectMark)) { 3547 if (!isEmpty_Range(&d->selectMark)) {
@@ -3870,14 +4094,14 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3870 } 4094 }
3871#endif 4095#endif
3872 /* Fill the background. */ { 4096 /* Fill the background. */ {
3873 if (run->linkId && linkFlags & isOpen_GmLinkFlag) { 4097 if (run->linkId && linkFlags & isOpen_GmLinkFlag && ~linkFlags & content_GmLinkFlag) {
3874 /* Open links get a highlighted background. */ 4098 /* Open links get a highlighted background. */
3875 int bg = tmBackgroundOpenLink_ColorId; 4099 int bg = tmBackgroundOpenLink_ColorId;
3876 const int frame = tmFrameOpenLink_ColorId; 4100 const int frame = tmFrameOpenLink_ColorId;
3877 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), 4101 iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y),
3878 init_I2(width_Rect(d->widgetBounds) + 4102 init_I2(width_Rect(d->widgetBounds) +
3879 width_Widget(d->widget->scroll), 4103 width_Widget(d->widget->scroll),
3880 height_Rect(run->visBounds)) }; 4104 height_Rect(run->visBounds)) };
3881 /* The first line is composed of two runs that may be drawn in either order, so 4105 /* The first line is composed of two runs that may be drawn in either order, so
3882 only draw half of the background. */ 4106 only draw half of the background. */
3883 if (run->flags & decoration_GmRunFlag) { 4107 if (run->flags & decoration_GmRunFlag) {
@@ -3931,16 +4155,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
3931 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4155 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase);
3932 if (ordChar) { 4156 if (ordChar) {
3933 const char *circle = "\u25ef"; /* Large Circle */ 4157 const char *circle = "\u25ef"; /* Large Circle */
4158 const int circleFont = defaultContentRegular_FontId;
3934 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 4159 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
3935 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(run->font)) }; 4160 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) };
3936 drawRange_Text( 4161 drawRange_Text(
3937 run->font, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); 4162 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle));
3938 iRect circleArea = visualBounds_Text(run->font, range_CStr(circle)); 4163 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle));
3939 addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); 4164 addv_I2(&circleArea.pos, topLeft_Rect(nbArea));
3940 drawCentered_Text(defaultContentSmall_FontId, 4165 drawCentered_Text(defaultContentSmall_FontId,
3941 circleArea, 4166 circleArea,
3942 iTrue, 4167 iTrue,
3943 tmQuote_ColorId, 4168 tmQuote_ColorId,
3944 "%lc", 4169 "%lc",
3945 (int) ordChar); 4170 (int) ordChar);
3946 goto runDrawn; 4171 goto runDrawn;
@@ -4178,7 +4403,7 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4178 iDrawBufs * dbuf = d->drawBufs; 4403 iDrawBufs * dbuf = d->drawBufs;
4179 iPaint p; 4404 iPaint p;
4180 init_Paint(&p); 4405 init_Paint(&p);
4181 setClip_Paint(&p, bounds); 4406 setClip_Paint(&p, boundsWithoutVisualOffset_Widget(w));
4182 /* Side icon and current heading. */ 4407 /* Side icon and current heading. */
4183 if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { 4408 if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) {
4184 const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); 4409 const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf);
@@ -4263,7 +4488,8 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx,
4263 /* Swap buffers around to have room available both before and after the visible region. */ 4488 /* Swap buffers around to have room available both before and after the visible region. */
4264 allocVisBuffer_DocumentWidget_(d); 4489 allocVisBuffer_DocumentWidget_(d);
4265 reposition_VisBuf(visBuf, vis); 4490 reposition_VisBuf(visBuf, vis);
4266 /* Redraw the invalid ranges. */ { 4491 /* Redraw the invalid ranges. */
4492 if (~flags_Widget(constAs_Widget(d)) & destroyPending_WidgetFlag) {
4267 iPaint *p = &ctx->paint; 4493 iPaint *p = &ctx->paint;
4268 init_Paint(p); 4494 init_Paint(p);
4269 iForIndices(i, visBuf->buffers) { 4495 iForIndices(i, visBuf->buffers) {
@@ -4427,12 +4653,17 @@ static void prerender_DocumentWidget_(iAny *context) {
4427} 4653}
4428 4654
4429static void draw_DocumentWidget_(const iDocumentWidget *d) { 4655static void draw_DocumentWidget_(const iDocumentWidget *d) {
4430 const iWidget *w = constAs_Widget(d); 4656 const iWidget *w = constAs_Widget(d);
4431 const iRect bounds = bounds_Widget(w); 4657 const iRect bounds = bounds_Widget(w);
4658 const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w);
4659 const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff);
4432 if (width_Rect(bounds) <= 0) { 4660 if (width_Rect(bounds) <= 0) {
4433 return; 4661 return;
4434 } 4662 }
4435// draw_Widget(w); 4663 /* TODO: Come up with a better palette caching system.
4664 It should be able to recompute cached colors in `History` when the theme has changed.
4665 Cache the theme seed in `GmDocument`? */
4666// makePaletteGlobal_GmDocument(d->doc);
4436 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { 4667 if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) {
4437 updateTimestampBuf_DocumentWidget_(d); 4668 updateTimestampBuf_DocumentWidget_(d);
4438 } 4669 }
@@ -4447,14 +4678,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4447 .vis = vis, 4678 .vis = vis,
4448 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, 4679 .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0,
4449 }; 4680 };
4681 init_Paint(&ctx.paint);
4450 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 4682 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
4451 setClip_Paint(&ctx.paint, bounds); 4683 setClip_Paint(&ctx.paint, clipBounds);
4452 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); 4684 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY);
4453 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); 4685 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
4454 /* Text markers. */ 4686 /* Text markers. */
4455 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 4687 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
4456 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { 4688 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) {
4457 SDL_Renderer *render = renderer_Window(get_Window()); 4689 SDL_Renderer *render = renderer_Window(get_Window());
4458 ctx.firstMarkRect = zero_Rect(); 4690 ctx.firstMarkRect = zero_Rect();
4459 ctx.lastMarkRect = zero_Rect(); 4691 ctx.lastMarkRect = zero_Rect();
4460 SDL_SetRenderDrawBlendMode(render, 4692 SDL_SetRenderDrawBlendMode(render,
@@ -4483,7 +4715,6 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4483 } 4715 }
4484 } 4716 }
4485 drawMedia_DocumentWidget_(d, &ctx.paint); 4717 drawMedia_DocumentWidget_(d, &ctx.paint);
4486 unsetClip_Paint(&ctx.paint);
4487 /* Fill the top and bottom, in case the document is short. */ 4718 /* Fill the top and bottom, in case the document is short. */
4488 if (yTop > top_Rect(bounds)) { 4719 if (yTop > top_Rect(bounds)) {
4489 fillRect_Paint(&ctx.paint, 4720 fillRect_Paint(&ctx.paint,
@@ -4497,6 +4728,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4497 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), 4728 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom),
4498 tmBackground_ColorId); 4729 tmBackground_ColorId);
4499 } 4730 }
4731 unsetClip_Paint(&ctx.paint);
4500 drawSideElements_DocumentWidget_(d); 4732 drawSideElements_DocumentWidget_(d);
4501 if (prefs_App()->hoverLink && d->hoverLink) { 4733 if (prefs_App()->hoverLink && d->hoverLink) {
4502 const int font = uiLabel_FontId; 4734 const int font = uiLabel_FontId;
@@ -4558,6 +4790,23 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4558 drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected", 4790 drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected",
4559 size_Range(&mark)); 4791 size_Range(&mark));
4560 } 4792 }
4793 if (w->offsetRef) {
4794 const int offX = visualOffsetByReference_Widget(w);
4795 if (offX) {
4796 setClip_Paint(&ctx.paint, clipBounds);
4797 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
4798 ctx.paint.alpha = iAbs(offX) / (float) get_Window()->size.x * 300;
4799 fillRect_Paint(&ctx.paint, bounds, backgroundFadeColor_Widget());
4800 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
4801 unsetClip_Paint(&ctx.paint);
4802 }
4803 else {
4804 /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */
4805 iWidget *mut = iConstCast(iWidget *, w);
4806 mut->offsetRef = NULL;
4807 mut->flags &= ~refChildrenOffset_WidgetFlag;
4808 }
4809 }
4561} 4810}
4562 4811
4563/*----------------------------------------------------------------------------------------------*/ 4812/*----------------------------------------------------------------------------------------------*/
@@ -4625,9 +4874,12 @@ static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) {
4625 d->flags |= urlChanged_DocumentWidgetFlag; 4874 d->flags |= urlChanged_DocumentWidgetFlag;
4626 set_String(d->mod.url, url); 4875 set_String(d->mod.url, url);
4627 } 4876 }
4628} 4877}
4629 4878
4630void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 4879void setUrlFlags_DocumentWidget(iDocumentWidget *d, const iString *url, int setUrlFlags) {
4880 iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag,
4881 (setUrlFlags & openedFromSidebar_DocumentWidgetSetUrlFlag) != 0);
4882 const iBool isFromCache = (setUrlFlags & useCachedContentIfAvailable_DocumentWidgetSetUrlFlag) != 0;
4631 setLinkNumberMode_DocumentWidget_(d, iFalse); 4883 setLinkNumberMode_DocumentWidget_(d, iFalse);
4632 setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); 4884 setUrl_DocumentWidget_(d, urlFragmentStripped_String(url));
4633 /* See if there a username in the URL. */ 4885 /* See if there a username in the URL. */
@@ -4639,6 +4891,7 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo
4639 4891
4640void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, 4892void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime,
4641 const iBlock *source) { 4893 const iBlock *source) {
4894 d->flags &= ~openedFromSidebar_DocumentWidgetFlag;
4642 setLinkNumberMode_DocumentWidget_(d, iFalse); 4895 setLinkNumberMode_DocumentWidget_(d, iFalse);
4643 setUrl_DocumentWidget_(d, url); 4896 setUrl_DocumentWidget_(d, url);
4644 parseUser_DocumentWidget_(d); 4897 parseUser_DocumentWidget_(d);
@@ -4647,7 +4900,7 @@ void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, cons
4647 initCurrent_Time(&resp->when); 4900 initCurrent_Time(&resp->when);
4648 set_String(&resp->meta, mime); 4901 set_String(&resp->meta, mime);
4649 set_Block(&resp->body, source); 4902 set_Block(&resp->body, source);
4650 updateFromCachedResponse_DocumentWidget_(d, 0, resp); 4903 updateFromCachedResponse_DocumentWidget_(d, 0, resp, NULL);
4651 delete_GmResponse(resp); 4904 delete_GmResponse(resp);
4652} 4905}
4653 4906
@@ -4656,12 +4909,12 @@ iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) {
4656 delete_History(d->mod.history); 4909 delete_History(d->mod.history);
4657 d->initNormScrollY = normScrollPos_DocumentWidget_(d); 4910 d->initNormScrollY = normScrollPos_DocumentWidget_(d);
4658 d->mod.history = copy_History(orig->mod.history); 4911 d->mod.history = copy_History(orig->mod.history);
4659 setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue); 4912 setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag);
4660 return d; 4913 return d;
4661} 4914}
4662 4915
4663void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { 4916void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
4664 setUrlFromCache_DocumentWidget(d, url, iFalse); 4917 setUrlFlags_DocumentWidget(d, url, 0);
4665} 4918}
4666 4919
4667void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) { 4920void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) {
@@ -4672,6 +4925,11 @@ void setRedirectCount_DocumentWidget(iDocumentWidget *d, int count) {
4672 d->redirectCount = count; 4925 d->redirectCount = count;
4673} 4926}
4674 4927
4928void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *d, iBool fromSidebar) {
4929 iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, fromSidebar);
4930// setCachedDocument_History(d->mod.history, d->doc, fromSidebar);
4931}
4932
4675iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { 4933iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
4676 return d->request != NULL; 4934 return d->request != NULL;
4677} 4935}
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index c038f981..1921b25a 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -45,11 +45,17 @@ const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *);
45const iString * feedTitle_DocumentWidget (const iDocumentWidget *); 45const iString * feedTitle_DocumentWidget (const iDocumentWidget *);
46int documentWidth_DocumentWidget (const iDocumentWidget *); 46int documentWidth_DocumentWidget (const iDocumentWidget *);
47 47
48enum iDocumentWidgetSetUrlFlags {
49 useCachedContentIfAvailable_DocumentWidgetSetUrlFlag = iBit(1),
50 openedFromSidebar_DocumentWidgetSetUrlFlag = iBit(2),
51};
52
48void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); 53void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
49void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache); 54void setUrlFlags_DocumentWidget (iDocumentWidget *, const iString *url, int setUrlFlags);
50void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); 55void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source);
51void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ 56void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */
52void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); 57void setRedirectCount_DocumentWidget (iDocumentWidget *, int count);
53void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); 58void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText);
59void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *, iBool fromSidebar);
54 60
55void updateSize_DocumentWidget (iDocumentWidget *); 61void updateSize_DocumentWidget (iDocumentWidget *);
diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c
index 4a829ae3..bc0bd0fa 100644
--- a/src/ui/indicatorwidget.c
+++ b/src/ui/indicatorwidget.c
@@ -109,7 +109,7 @@ void draw_IndicatorWidget_(const iIndicatorWidget *d) {
109 colors[0] = black_ColorId; 109 colors[0] = black_ColorId;
110 } 110 }
111 fillRect_Paint(&p, 111 fillRect_Paint(&p,
112 (iRect){ topLeft_Rect(rect), init_I2(pos * width_Rect(rect), gap_UI / 4)}, 112 (iRect){ topLeft_Rect(rect), init_I2(pos * width_Rect(rect), gap_UI / 3)},
113 colors[isCompleted_IndicatorWidget_(d) ? 1 : 0]); 113 colors[isCompleted_IndicatorWidget_(d) ? 1 : 0]);
114 } 114 }
115} 115}
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 32fb5ccb..b108ee17 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -80,6 +80,7 @@ enum iInputWidgetFlag {
80 markWords_InputWidgetFlag = iBit(8), 80 markWords_InputWidgetFlag = iBit(8),
81 needUpdateBuffer_InputWidgetFlag = iBit(9), 81 needUpdateBuffer_InputWidgetFlag = iBit(9),
82 enterKeyEnabled_InputWidgetFlag = iBit(10), 82 enterKeyEnabled_InputWidgetFlag = iBit(10),
83 enterKeyInsertsLineFeed_InputWidgetFlag = iBit(11),
83}; 84};
84 85
85/*----------------------------------------------------------------------------------------------*/ 86/*----------------------------------------------------------------------------------------------*/
@@ -347,8 +348,11 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
347 d->lastCursor = 0; 348 d->lastCursor = 0;
348 d->cursorLine = 0; 349 d->cursorLine = 0;
349 d->lastUpdateWidth = 0; 350 d->lastUpdateWidth = 0;
350 d->verticalMoveX = -1; /* TODO: Use this. */ 351 d->verticalMoveX = -1; /* TODO: Use this. */
351 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; 352 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag;
353 if (deviceType_App() != desktop_AppDeviceType) {
354 d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag;
355 }
352 iZap(d->mark); 356 iZap(d->mark);
353 setMaxLen_InputWidget(d, maxLen); 357 setMaxLen_InputWidget(d, maxLen);
354 d->maxLayoutLines = iInvalidSize; 358 d->maxLayoutLines = iInvalidSize;
@@ -464,6 +468,10 @@ void setValidator_InputWidget(iInputWidget *d, iInputWidgetValidatorFunc validat
464 d->validatorContext = context; 468 d->validatorContext = context;
465} 469}
466 470
471void setEnterInsertsLF_InputWidget(iInputWidget *d, iBool enterInsertsLF) {
472 iChangeFlags(d->inFlags, enterKeyInsertsLineFeed_InputWidgetFlag, enterInsertsLF);
473}
474
467void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) { 475void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) {
468 iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled); 476 iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled);
469} 477}
@@ -714,12 +722,12 @@ iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine
714} 722}
715 723
716static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { 724static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) {
725 size_t index = line->offset;
717 if (x <= 0) { 726 if (x <= 0) {
718 return line->offset; 727 return index;
719 } 728 }
720 const char *endPos; 729 const char *endPos;
721 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); 730 tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos);
722 size_t index = line->offset;
723 if (endPos == constEnd_String(&line->text)) { 731 if (endPos == constEnd_String(&line->text)) {
724 index += line->len; 732 index += line->len;
725 } 733 }
@@ -1166,7 +1174,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1166 case SDLK_KP_ENTER: 1174 case SDLK_KP_ENTER:
1167 if (mods == KMOD_SHIFT || (d->maxLen == 0 && 1175 if (mods == KMOD_SHIFT || (d->maxLen == 0 &&
1168 ~d->inFlags & isUrl_InputWidgetFlag && 1176 ~d->inFlags & isUrl_InputWidgetFlag &&
1169 deviceType_App() != desktop_AppDeviceType)) { 1177 d->inFlags & enterKeyInsertsLineFeed_InputWidgetFlag)) {
1170 pushUndo_InputWidget_(d); 1178 pushUndo_InputWidget_(d);
1171 deleteMarked_InputWidget_(d); 1179 deleteMarked_InputWidget_(d);
1172 insertChar_InputWidget_(d, '\n'); 1180 insertChar_InputWidget_(d, '\n');
@@ -1357,13 +1365,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
1357 } 1365 }
1358 iPaint p; 1366 iPaint p;
1359 init_Paint(&p); 1367 init_Paint(&p);
1360 /* `lines` is already up to date and ready for drawing. */ 1368 /* `lines` is already up to date and ready for drawing. */
1361 /* TODO: If empty, draw the hint. */
1362// iString *text = visText_InputWidget_(d);
1363// if (isWhite_(text) && !isEmpty_String(&d->hint)) {
1364// set_String(text, &d->hint);
1365// isHint = iTrue;
1366// }
1367 fillRect_Paint( 1369 fillRect_Paint(
1368 &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId); 1370 &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId);
1369 drawRectThickness_Paint(&p, 1371 drawRectThickness_Paint(&p,
@@ -1374,7 +1376,6 @@ static void draw_InputWidget_(const iInputWidget *d) {
1374 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), 1376 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
1375 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); 1377 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
1376 const iRect contentBounds = contentBounds_InputWidget_(d); 1378 const iRect contentBounds = contentBounds_InputWidget_(d);
1377// const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text));
1378 iInt2 drawPos = topLeft_Rect(contentBounds); 1379 iInt2 drawPos = topLeft_Rect(contentBounds);
1379 const int fg = isHint ? uiAnnotation_ColorId 1380 const int fg = isHint ? uiAnnotation_ColorId
1380 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId 1381 : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId
@@ -1418,19 +1419,6 @@ static void draw_InputWidget_(const iInputWidget *d) {
1418 drawPos.y += lineHeight_Text(d->font); 1419 drawPos.y += lineHeight_Text(d->font);
1419 } 1420 }
1420 } 1421 }
1421// if (d->buffered && !isFocused && !isHint) {
1422// /* Most input widgets will use this, since only one is focused at a time. */
1423// draw_TextBuf(d->buffered, textOrigin, white_ColorId);
1424// }
1425// else {
1426// draw_Text(d->font,
1427// textOrigin,
1428// isHint ? uiAnnotation_ColorId
1429// : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId
1430// : uiInputText_ColorId,
1431// "%s",
1432// cstr_String(text));
1433// }
1434 unsetClip_Paint(&p); 1422 unsetClip_Paint(&p);
1435 /* Cursor blinking. */ 1423 /* Cursor blinking. */
1436 if (isFocused && d->cursorVis) { 1424 if (isFocused && d->cursorVis) {
@@ -1474,7 +1462,6 @@ static void draw_InputWidget_(const iInputWidget *d) {
1474 deinit_String(&cur); 1462 deinit_String(&cur);
1475 } 1463 }
1476 } 1464 }
1477// delete_String(text);
1478 drawChildren_Widget(w); 1465 drawChildren_Widget(w);
1479} 1466}
1480 1467
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index 5c39aae0..c70d9ad6 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -41,20 +41,21 @@ struct Impl_InputWidgetContentPadding {
41 41
42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); 42typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context);
43 43
44void setHint_InputWidget (iInputWidget *, const char *hintText); 44void setHint_InputWidget (iInputWidget *, const char *hintText);
45void setMode_InputWidget (iInputWidget *, enum iInputMode mode); 45void 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 setFont_InputWidget (iInputWidget *, int fontId); 49void setFont_InputWidget (iInputWidget *, int fontId);
50void setCursor_InputWidget (iInputWidget *, size_t pos); 50void setCursor_InputWidget (iInputWidget *, size_t pos);
51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 51void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); 52void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines);
53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 53void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
54void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF);
54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 55void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
55void begin_InputWidget (iInputWidget *); 56void begin_InputWidget (iInputWidget *);
56void end_InputWidget (iInputWidget *, iBool accept); 57void end_InputWidget (iInputWidget *, iBool accept);
57void selectAll_InputWidget (iInputWidget *); 58void selectAll_InputWidget (iInputWidget *);
58 59
59void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); 60void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
60void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); 61void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive);
@@ -62,15 +63,13 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl);
62void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); 63void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
63void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); 64void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape);
64 65
65const iString * text_InputWidget (const iInputWidget *); 66iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *);
67const iString * text_InputWidget (const iInputWidget *);
66 68
67iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { 69iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) {
68 return cstr_String(text_InputWidget(d)); 70 return cstr_String(text_InputWidget(d));
69} 71}
70 72
71iInputWidgetContentPadding
72 contentPadding_InputWidget (const iInputWidget *);
73
74iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { 73iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) {
75 iInputWidget *d = new_InputWidget(maxLen); 74 iInputWidget *d = new_InputWidget(maxLen);
76 setHint_InputWidget(d, hint); 75 setHint_InputWidget(d, hint);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 95f281be..b68ab793 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -44,6 +44,9 @@ struct Impl_LabelWidget {
44 struct { 44 struct {
45 uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ 45 uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */
46 uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ 46 uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */
47 uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */
48 uint8_t noTopFrame : 1;
49 uint8_t wrap : 1;
47 } flags; 50 } flags;
48}; 51};
49 52
@@ -206,7 +209,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int
206 } 209 }
207 } 210 }
208 int colorEscape = none_ColorId; 211 int colorEscape = none_ColorId;
209 if (startsWith_String(&d->label, "\r")) { 212 if (startsWith_String(&d->label, "\v")) {
210 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ 213 colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */
211 } 214 }
212 if (isHover_LabelWidget_(d)) { 215 if (isHover_LabelWidget_(d)) {
@@ -289,7 +292,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
289 }; 292 };
290 drawLines_Paint(&p, points + 2, 3, frame2); 293 drawLines_Paint(&p, points + 2, 3, frame2);
291 drawLines_Paint( 294 drawLines_Paint(
292 &p, points, !isHover && flags & noTopFrame_WidgetFlag ? 2 : 3, frame); 295 &p, points, !isHover && flags & d->flags.noTopFrame ? 2 : 3, frame);
293 } 296 }
294 } 297 }
295 setClip_Paint(&p, rect); 298 setClip_Paint(&p, rect);
@@ -315,7 +318,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
315 cstr_String(&str)); 318 cstr_String(&str));
316 deinit_String(&str); 319 deinit_String(&str);
317 } 320 }
318 if (flags & wrapText_WidgetFlag) { 321 if (d->flags.wrap) {
319 const iRect inner = adjusted_Rect(innerBounds_Widget(w), init_I2(iconPad, 0), zero_I2()); 322 const iRect inner = adjusted_Rect(innerBounds_Widget(w), init_I2(iconPad, 0), zero_I2());
320 const int wrap = inner.size.x; 323 const int wrap = inner.size.x;
321 drawWrapRange_Text(d->font, topLeft_Rect(inner), wrap, fg, range_String(&d->label)); 324 drawWrapRange_Text(d->font, topLeft_Rect(inner), wrap, fg, range_String(&d->label));
@@ -348,14 +351,14 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
348 cstr_String(&d->label)); 351 cstr_String(&d->label));
349 } 352 }
350 else { 353 else {
351 drawCentered_Text(d->font, 354 drawCenteredOutline_Text(
352 adjusted_Rect(bounds, 355 d->font,
353 add_I2(zero_I2(), init_I2(iconPad, 0)), 356 adjusted_Rect(bounds, add_I2(zero_I2(), init_I2(iconPad, 0)), neg_I2(zero_I2())),
354 neg_I2(zero_I2())), 357 d->flags.alignVisual,
355 d->flags.alignVisual, 358 d->flags.drawAsOutline ? fg : none_ColorId,
356 fg, 359 d->flags.drawAsOutline ? d->widget.bgColor : fg,
357 "%s", 360 "%s",
358 cstr_String(&d->label)); 361 cstr_String(&d->label));
359 } 362 }
360 if (flags & chevron_WidgetFlag) { 363 if (flags & chevron_WidgetFlag) {
361 const iRect chRect = rect; 364 const iRect chRect = rect;
@@ -370,7 +373,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
370 373
371static void sizeChanged_LabelWidget_(iLabelWidget *d) { 374static void sizeChanged_LabelWidget_(iLabelWidget *d) {
372 iWidget *w = as_Widget(d); 375 iWidget *w = as_Widget(d);
373 if (flags_Widget(w) & wrapText_WidgetFlag) { 376 if (d->flags.wrap) {
374 if (flags_Widget(w) & fixedHeight_WidgetFlag) { 377 if (flags_Widget(w) & fixedHeight_WidgetFlag) {
375 /* Calculate a new height based on the wrapping. */ 378 /* Calculate a new height based on the wrapping. */
376 w->rect.size.y = advanceWrapRange_Text( 379 w->rect.size.y = advanceWrapRange_Text(
@@ -408,7 +411,7 @@ void updateSize_LabelWidget(iLabelWidget *d) {
408 w->minSize.y = size.y; /* vertically text must remain visible */ 411 w->minSize.y = size.y; /* vertically text must remain visible */
409 } 412 }
410 /* Wrapped text implies that width must be defined by arrangement. */ 413 /* Wrapped text implies that width must be defined by arrangement. */
411 if (!(flags & (fixedWidth_WidgetFlag | wrapText_WidgetFlag))) { 414 if (~flags & fixedWidth_WidgetFlag && !d->flags.wrap) {
412 w->rect.size.x = size.x; 415 w->rect.size.x = size.x;
413 } 416 }
414 if (~flags & fixedHeight_WidgetFlag) { 417 if (~flags & fixedHeight_WidgetFlag) {
@@ -440,8 +443,11 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) {
440 d->kmods = 0; 443 d->kmods = 0;
441 init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); 444 init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0);
442 setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); 445 setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0);
443 d->flags.alignVisual = iFalse; 446 d->flags.alignVisual = iFalse;
444 d->flags.noAutoMinHeight = iFalse; 447 d->flags.noAutoMinHeight = iFalse;
448 d->flags.drawAsOutline = iFalse;
449 d->flags.noTopFrame = iFalse;
450 d->flags.wrap = iFalse;
445 updateSize_LabelWidget(d); 451 updateSize_LabelWidget(d);
446 updateKey_LabelWidget_(d); /* could be bound to another key */ 452 updateKey_LabelWidget_(d); /* could be bound to another key */
447} 453}
@@ -481,6 +487,20 @@ void setNoAutoMinHeight_LabelWidget(iLabelWidget *d, iBool noAutoMinHeight) {
481 } 487 }
482} 488}
483 489
490void setNoTopFrame_LabelWidget(iLabelWidget *d, iBool noTopFrame) {
491 d->flags.noTopFrame = noTopFrame;
492}
493
494void setWrap_LabelWidget(iLabelWidget *d, iBool wrap) {
495 d->flags.wrap = wrap;
496}
497
498void setOutline_LabelWidget(iLabelWidget *d, iBool drawAsOutline) {
499 if (d) {
500 d->flags.drawAsOutline = drawAsOutline;
501 }
502}
503
484void updateText_LabelWidget(iLabelWidget *d, const iString *text) { 504void updateText_LabelWidget(iLabelWidget *d, const iString *text) {
485 set_String(&d->label, text); 505 set_String(&d->label, text);
486 set_String(&d->srcLabel, text); 506 set_String(&d->srcLabel, text);
@@ -495,6 +515,11 @@ void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) {
495 refresh_Widget(&d->widget); 515 refresh_Widget(&d->widget);
496} 516}
497 517
518void updateTextAndResizeWidthCStr_LabelWidget(iLabelWidget *d, const char *text) {
519 updateTextCStr_LabelWidget(d, text);
520 d->widget.rect.size.x = defaultSize_LabelWidget(d).x;
521}
522
498void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { 523void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) {
499 setCStr_String(&d->label, text); 524 setCStr_String(&d->label, text);
500 set_String(&d->srcLabel, &d->label); 525 set_String(&d->srcLabel, &d->label);
@@ -537,6 +562,10 @@ iChar icon_LabelWidget(const iLabelWidget *d) {
537 return d->icon; 562 return d->icon;
538} 563}
539 564
565iBool isWrapped_LabelWidget(const iLabelWidget *d) {
566 return d->flags.wrap;
567}
568
540const iString *text_LabelWidget(const iLabelWidget *d) { 569const iString *text_LabelWidget(const iLabelWidget *d) {
541 if (!d) return collectNew_String(); 570 if (!d) return collectNew_String();
542 return &d->label; 571 return &d->label;
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h
index e38a1dc8..b8b6fd87 100644
--- a/src/ui/labelwidget.h
+++ b/src/ui/labelwidget.h
@@ -31,6 +31,9 @@ iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *comma
31 31
32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); 32void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual);
33void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight); 33void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight);
34void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame);
35void setWrap_LabelWidget (iLabelWidget *, iBool wrap);
36void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline);
34void setFont_LabelWidget (iLabelWidget *, int fontId); 37void setFont_LabelWidget (iLabelWidget *, int fontId);
35void setTextColor_LabelWidget (iLabelWidget *, int color); 38void setTextColor_LabelWidget (iLabelWidget *, int color);
36void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ 39void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */
@@ -43,12 +46,15 @@ void updateSize_LabelWidget (iLabelWidget *);
43void updateText_LabelWidget (iLabelWidget *, const iString *text); /* not resized */ 46void updateText_LabelWidget (iLabelWidget *, const iString *text); /* not resized */
44void updateTextCStr_LabelWidget (iLabelWidget *, const char *text); /* not resized */ 47void updateTextCStr_LabelWidget (iLabelWidget *, const char *text); /* not resized */
45 48
49void updateTextAndResizeWidthCStr_LabelWidget (iLabelWidget *, const char *text);
50
46iInt2 defaultSize_LabelWidget (const iLabelWidget *); 51iInt2 defaultSize_LabelWidget (const iLabelWidget *);
47int font_LabelWidget (const iLabelWidget *); 52int font_LabelWidget (const iLabelWidget *);
48const iString * text_LabelWidget (const iLabelWidget *); 53const iString * text_LabelWidget (const iLabelWidget *);
49const iString * sourceText_LabelWidget (const iLabelWidget *); /* untranslated */ 54const iString * sourceText_LabelWidget (const iLabelWidget *); /* untranslated */
50const iString * command_LabelWidget (const iLabelWidget *); 55const iString * command_LabelWidget (const iLabelWidget *);
51iChar icon_LabelWidget (const iLabelWidget *); 56iChar icon_LabelWidget (const iLabelWidget *);
57iBool isWrapped_LabelWidget (const iLabelWidget *);
52 58
53iLabelWidget *newKeyMods_LabelWidget(const char *label, int key, int kmods, const char *command); 59iLabelWidget *newKeyMods_LabelWidget(const char *label, int key, int kmods, const char *command);
54iLabelWidget *newColor_LabelWidget (const char *text, int color); 60iLabelWidget *newColor_LabelWidget (const char *text, int color);
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c
index bc417fc3..fa09b214 100644
--- a/src/ui/mediaui.c
+++ b/src/ui/mediaui.c
@@ -118,7 +118,7 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
118 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", 118 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8",
119 uiContent_FontId); 119 uiContent_FontId);
120 drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); 120 drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId);
121 drawPlayerButton_(p, d->menuRect, "\U0001d362", uiContent_FontId); 121 drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId);
122 if (!isAdjusting) { 122 if (!isAdjusting) {
123 drawPlayerButton_( 123 drawPlayerButton_(
124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); 124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId);
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 263fc141..0ff3fe85 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -120,7 +120,7 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd)
120 } 120 }
121 iForEach(ObjectList, i, children_Widget(detailStack)) { 121 iForEach(ObjectList, i, children_Widget(detailStack)) {
122 iWidget *panel = i.object; 122 iWidget *panel = i.object;
123 setFlags_Widget(panel, edgeDraggable_WidgetFlag, !isSideBySide); 123 setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide);
124 if (isSideBySide) { 124 if (isSideBySide) {
125 setVisualOffset_Widget(panel, 0, 0, 0); 125 setVisualOffset_Widget(panel, 0, 0, 0);
126 } 126 }
@@ -150,8 +150,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) {
150 setFlags_Widget(button, selected_WidgetFlag, iTrue); 150 setFlags_Widget(button, selected_WidgetFlag, iTrue);
151 return iTrue; 151 return iTrue;
152 } 152 }
153 if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && 153 if (equal_Command(cmd, "swipe.back")) {
154 argLabel_Command(cmd, "button") == SDL_BUTTON_X1) {
155 postCommand_App("panel.close"); 154 postCommand_App("panel.close");
156 return iTrue; 155 return iTrue;
157 } 156 }
@@ -201,6 +200,8 @@ static iBool isTwoColumnPage_(iWidget *d) {
201 200
202static iBool isOmittedPref_(const iString *id) { 201static iBool isOmittedPref_(const iString *id) {
203 static const char *omittedPrefs[] = { 202 static const char *omittedPrefs[] = {
203 "prefs.userfont",
204 "prefs.animate",
204 "prefs.smoothscroll", 205 "prefs.smoothscroll",
205 "prefs.imageloadscroll", 206 "prefs.imageloadscroll",
206 "prefs.pinsplit", 207 "prefs.pinsplit",
@@ -413,7 +414,7 @@ void finalizeSheet_Mobile(iWidget *sheet) {
413 setFlags_Widget(sheet, 414 setFlags_Widget(sheet,
414 frameless_WidgetFlag | 415 frameless_WidgetFlag |
415 //resizeWidthOfChildren_WidgetFlag | 416 //resizeWidthOfChildren_WidgetFlag |
416 edgeDraggable_WidgetFlag | 417 leftEdgeDraggable_WidgetFlag |
417 commandOnClick_WidgetFlag, 418 commandOnClick_WidgetFlag,
418 iTrue); 419 iTrue);
419 iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ 420 iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */
@@ -446,7 +447,7 @@ void finalizeSheet_Mobile(iWidget *sheet) {
446 } 447 }
447 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); 448 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
448 /* Slide top panel with detail panels. */ { 449 /* Slide top panel with detail panels. */ {
449 setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); 450 setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue);
450 topPanel->offsetRef = detailStack; 451 topPanel->offsetRef = detailStack;
451 } 452 }
452 if (prefsTabs) { 453 if (prefsTabs) {
@@ -472,7 +473,8 @@ void finalizeSheet_Mobile(iWidget *sheet) {
472 0x02699, /* gear */ 473 0x02699, /* gear */
473 0x1f4f1, /* mobile phone */ 474 0x1f4f1, /* mobile phone */
474 0x1f3a8, /* palette */ 475 0x1f3a8, /* palette */
475 0x1f523, 476 0x1f5da, /* aA */
477 0x1f660, /* pointing bud */
476 0x1f5a7, /* computer network */ 478 0x1f5a7, /* computer network */
477 }; 479 };
478 setIcon_LabelWidget(panelButton, icons[i]); 480 setIcon_LabelWidget(panelButton, icons[i]);
@@ -621,6 +623,12 @@ void finalizeSheet_Mobile(iWidget *sheet) {
621 /* Additional elements for preferences. */ 623 /* Additional elements for preferences. */
622 if (isPrefs) { 624 if (isPrefs) {
623 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); 625 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
626 /* Management. */ {
627 iLabelWidget *idManButton = addChildFlags_Widget(topPanel,
628 iClob(makePanelButton_(person_Icon " ${sidebar.identities}", "panel.open")),
629 chevron_WidgetFlag | borderTop_WidgetFlag);
630 }
631 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_()))));
624 iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, 632 iLabelWidget *aboutButton = addChildFlags_Widget(topPanel,
625 iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), 633 iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")),
626 chevron_WidgetFlag | borderTop_WidgetFlag); 634 chevron_WidgetFlag | borderTop_WidgetFlag);
diff --git a/src/ui/root.c b/src/ui/root.c
index 15548e74..5266978b 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -52,10 +52,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
52 52
53#include <SDL_timer.h> 53#include <SDL_timer.h>
54 54
55#if defined (iPlatformAppleDesktop)
56# define iHaveNativeMenus
57#endif
58
59#if defined (iPlatformPcDesktop) 55#if defined (iPlatformPcDesktop)
60/* TODO: Submenus wouldn't hurt here. */ 56/* TODO: Submenus wouldn't hurt here. */
61static const iMenuItem navMenuItems_[] = { 57static const iMenuItem navMenuItems_[] = {
@@ -90,6 +86,7 @@ static const iMenuItem navMenuItems_[] = {
90#if defined (iPlatformAppleMobile) 86#if defined (iPlatformAppleMobile)
91/* Tablet menu. */ 87/* Tablet menu. */
92static const iMenuItem tabletNavMenuItems_[] = { 88static const iMenuItem tabletNavMenuItems_[] = {
89 { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" },
93 { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, 90 { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" },
94 { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, 91 { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" },
95 { "---", 0, 0, NULL }, 92 { "---", 0, 0, NULL },
@@ -110,6 +107,7 @@ static const iMenuItem tabletNavMenuItems_[] = {
110 107
111/* Phone menu. */ 108/* Phone menu. */
112static const iMenuItem phoneNavMenuItems_[] = { 109static const iMenuItem phoneNavMenuItems_[] = {
110 { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" },
113 { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, 111 { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" },
114 { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, 112 { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" },
115 { "---", 0, 0, NULL }, 113 { "---", 0, 0, NULL },
@@ -273,7 +271,8 @@ void destroyPending_Root(iRoot *d) {
273 setCurrent_Root(d); 271 setCurrent_Root(d);
274 iForEach(PtrSet, i, d->pendingDestruction) { 272 iForEach(PtrSet, i, d->pendingDestruction) {
275 iWidget *widget = *i.value; 273 iWidget *widget = *i.value;
276 if (!isFinished_Anim(&widget->visualOffset)) { 274 if (!isFinished_Anim(&widget->visualOffset) ||
275 isBeingVisuallyOffsetByReference_Widget(widget)) {
277 continue; 276 continue;
278 } 277 }
279 if (widget->flags & keepOnTop_WidgetFlag) { 278 if (widget->flags & keepOnTop_WidgetFlag) {
@@ -282,7 +281,7 @@ void destroyPending_Root(iRoot *d) {
282 if (widget->parent) { 281 if (widget->parent) {
283 removeChild_Widget(widget->parent, widget); 282 removeChild_Widget(widget->parent, widget);
284 } 283 }
285 iAssert(widget->parent == NULL); 284 iAssert(widget->parent == NULL);
286 iRelease(widget); 285 iRelease(widget);
287 remove_PtrSetIterator(&i); 286 remove_PtrSetIterator(&i);
288 } 287 }
@@ -433,9 +432,9 @@ static void updateNavBarIdentity_(iWidget *navBar) {
433 const iGmIdentity *ident = 432 const iGmIdentity *ident =
434 identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); 433 identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App()));
435 iWidget *button = findChild_Widget(navBar, "navbar.ident"); 434 iWidget *button = findChild_Widget(navBar, "navbar.ident");
436 iWidget *tool = findWidget_App("toolbar.ident"); 435 iLabelWidget *toolButton = findWidget_App("toolbar.ident");
437 setFlags_Widget(button, selected_WidgetFlag, ident != NULL); 436 setFlags_Widget(button, selected_WidgetFlag, ident != NULL);
438 setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); 437 setOutline_LabelWidget(toolButton, ident == NULL);
439 /* Update menu. */ 438 /* Update menu. */
440 iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); 439 iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0);
441 const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; 440 const iString *subjectName = ident ? name_GmIdentity(ident) : NULL;
@@ -444,6 +443,12 @@ static void updateNavBarIdentity_(iWidget *navBar) {
444 subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) 443 subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName))
445 : "${menu.identity.notactive}"); 444 : "${menu.identity.notactive}");
446 setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); 445 setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident);
446 iLabelWidget *toolName = findWidget_App("toolbar.name");
447 if (toolName) {
448 updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : "");
449 setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId);
450 arrange_Widget(parent_Widget(toolButton));
451 }
447} 452}
448 453
449static void updateNavDirButtons_(iWidget *navBar) { 454static void updateNavDirButtons_(iWidget *navBar) {
@@ -515,6 +520,26 @@ void updatePadding_Root(iRoot *d) {
515#endif 520#endif
516} 521}
517 522
523void updateToolbarColors_Root(iRoot *d) {
524#if defined (iPlatformMobile)
525 iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
526 if (toolBar) {
527 const iBool isSidebarVisible = isVisible_Widget(findChild_Widget(d->widget, "sidebar"));
528 const int bg = isSidebarVisible ? uiBackgroundSidebar_ColorId :
529 tmBannerBackground_ColorId;
530 setBackgroundColor_Widget(toolBar, bg);
531 iForEach(ObjectList, i, children_Widget(toolBar)) {
532 iLabelWidget *btn = i.object;
533 setTextColor_LabelWidget(i.object, isSidebarVisible ? uiTextDim_ColorId :
534 tmBannerIcon_ColorId);
535 setBackgroundColor_Widget(i.object, bg); /* using noBackground, but ident has outline */
536 }
537 }
538#else
539 iUnused(d);
540#endif
541}
542
518void dismissPortraitPhoneSidebars_Root(iRoot *d) { 543void dismissPortraitPhoneSidebars_Root(iRoot *d) {
519 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { 544 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
520 iWidget *sidebar = findChild_Widget(d->widget, "sidebar"); 545 iWidget *sidebar = findChild_Widget(d->widget, "sidebar");
@@ -551,9 +576,16 @@ static void updateUrlInputContentPadding_(iWidget *navBar) {
551} 576}
552 577
553static void showSearchQueryIndicator_(iBool show) { 578static void showSearchQueryIndicator_(iBool show) {
579 iWidget *navBar = findWidget_Root("navbar");
554 iWidget *indicator = findWidget_App("input.indicator.search"); 580 iWidget *indicator = findWidget_App("input.indicator.search");
581 updateTextCStr_LabelWidget((iLabelWidget *) indicator,
582 (deviceType_App() == phone_AppDeviceType ||
583 flags_Widget(navBar) & tight_WidgetFlag)
584 ? "${status.query.tight} " return_Icon
585 : "${status.query} " return_Icon);
586 indicator->rect.size.x = defaultSize_LabelWidget((iLabelWidget *) indicator).x; /* don't touch height */
555 showCollapsed_Widget(indicator, show); 587 showCollapsed_Widget(indicator, show);
556 updateUrlInputContentPadding_(findWidget_Root("navbar")); 588 updateUrlInputContentPadding_(navBar);
557} 589}
558 590
559static int navBarAvailableSpace_(iWidget *navBar) { 591static int navBarAvailableSpace_(iWidget *navBar) {
@@ -692,6 +724,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
692 iInputWidget *url = findWidget_Root("url"); 724 iInputWidget *url = findWidget_Root("url");
693 const iString *urlStr = collect_String(suffix_Command(cmd, "url")); 725 const iString *urlStr = collect_String(suffix_Command(cmd, "url"));
694 trimCache_App(); 726 trimCache_App();
727 trimMemory_App();
695 visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ 728 visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */
696 postCommand_App("visited.changed"); /* sidebar will update */ 729 postCommand_App("visited.changed"); /* sidebar will update */
697 setText_InputWidget(url, urlStr); 730 setText_InputWidget(url, urlStr);
@@ -827,20 +860,20 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) {
827 const int viewHeight = size_Root(get_Root()).y; 860 const int viewHeight = size_Root(get_Root()).y;
828 if (arg_Command(cmd) >= 0) { 861 if (arg_Command(cmd) >= 0) {
829 postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); 862 postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd));
830 if (!isVisible) { 863// if (!isVisible) {
831 setVisualOffset_Widget(sidebar, viewHeight, 0, 0); 864// setVisualOffset_Widget(sidebar, viewHeight, 0, 0);
832 setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); 865// setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
833 } 866// }
834 } 867 }
835 else { 868 else {
836 postCommandf_App("sidebar.toggle"); 869 postCommandf_App("sidebar.toggle");
837 if (isVisible) { 870// if (isVisible) {
838 setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); 871// setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
839 } 872// }
840 else { 873// else {
841 setVisualOffset_Widget(sidebar, viewHeight, 0, 0); 874// setVisualOffset_Widget(sidebar, viewHeight, 0, 0);
842 setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); 875// setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
843 } 876// }
844 } 877 }
845 return iTrue; 878 return iTrue;
846 } 879 }
@@ -848,7 +881,10 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) {
848 /* TODO: Clean this up. */ 881 /* TODO: Clean this up. */
849 iWidget *sidebar = findWidget_App("sidebar"); 882 iWidget *sidebar = findWidget_App("sidebar");
850 iWidget *sidebar2 = findWidget_App("sidebar2"); 883 iWidget *sidebar2 = findWidget_App("sidebar2");
851 dismissSidebar_(sidebar, "toolbar.view"); 884 //dismissSidebar_(sidebar, "toolbar.view");
885 if (isVisible_Widget(sidebar)) {
886 postCommandf_App("sidebar.toggle");
887 }
852 const iBool isVisible = isVisible_Widget(sidebar2); 888 const iBool isVisible = isVisible_Widget(sidebar2);
853 // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, 889 // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag,
854 // isVisible); 890 // isVisible);
@@ -921,6 +957,23 @@ void updateMetrics_Root(iRoot *d) {
921 updatePadding_Root(d); 957 updatePadding_Root(d);
922 arrange_Widget(d->widget); 958 arrange_Widget(d->widget);
923 updateUrlInputContentPadding_(navBar); 959 updateUrlInputContentPadding_(navBar);
960 /* Position the toolbar identity name label manually. */ {
961 iLabelWidget *idName = findChild_Widget(d->widget, "toolbar.name");
962 if (idName) {
963 const iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
964 const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view");
965 const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident");
966 const int font = defaultSmall_FontId;
967 setFont_LabelWidget(idName, font);
968 setPos_Widget(as_Widget(idName),
969 windowToLocal_Widget(as_Widget(idName),
970 init_I2(left_Rect(bounds_Widget(idButton)),
971 bottom_Rect(bounds_Widget(viewButton)) -
972 lineHeight_Text(font) - gap_UI / 2)));
973 setFixedSize_Widget(as_Widget(idName), init_I2(width_Widget(idButton),
974 lineHeight_Text(font)));
975 }
976 }
924 postRefresh_App(); 977 postRefresh_App();
925} 978}
926 979
@@ -1048,9 +1101,9 @@ void createUserInterface_Root(iRoot *d) {
1048 resizeHeightOfChildren_WidgetFlag | 1101 resizeHeightOfChildren_WidgetFlag |
1049 moveToParentRightEdge_WidgetFlag); 1102 moveToParentRightEdge_WidgetFlag);
1050 /* Feeds refresh indicator is inside the input field. */ { 1103 /* Feeds refresh indicator is inside the input field. */ {
1051 iLabelWidget *queryInd = 1104 iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL);
1052 new_LabelWidget(uiTextAction_ColorEscape "${status.query} " return_Icon, NULL);
1053 setId_Widget(as_Widget(queryInd), "input.indicator.search"); 1105 setId_Widget(as_Widget(queryInd), "input.indicator.search");
1106 setTextColor_LabelWidget(queryInd, uiTextAction_ColorId);
1054 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); 1107 setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId);
1055 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); 1108 setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId);
1056 setAlignVisually_LabelWidget(queryInd, iTrue); 1109 setAlignVisually_LabelWidget(queryInd, iTrue);
@@ -1060,9 +1113,9 @@ void createUserInterface_Root(iRoot *d) {
1060 collapse_WidgetFlag | hidden_WidgetFlag); 1113 collapse_WidgetFlag | hidden_WidgetFlag);
1061 } 1114 }
1062 /* Feeds refresh indicator is inside the input field. */ { 1115 /* Feeds refresh indicator is inside the input field. */ {
1063 iLabelWidget *fprog = new_LabelWidget(uiTextCaution_ColorEscape 1116 iLabelWidget *fprog = new_LabelWidget("", NULL);
1064 "\u2605 ${status.feeds}", NULL);
1065 setId_Widget(as_Widget(fprog), "feeds.progress"); 1117 setId_Widget(as_Widget(fprog), "feeds.progress");
1118 setTextColor_LabelWidget(fprog, uiTextCaution_ColorId);
1066 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); 1119 setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId);
1067 setAlignVisually_LabelWidget(fprog, iTrue); 1120 setAlignVisually_LabelWidget(fprog, iTrue);
1068 setNoAutoMinHeight_LabelWidget(fprog, iTrue); 1121 setNoAutoMinHeight_LabelWidget(fprog, iTrue);
@@ -1116,7 +1169,7 @@ void createUserInterface_Root(iRoot *d) {
1116 setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); 1169 setId_Widget(as_Widget(pageMenuButton), "pagemenubutton");
1117 setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); 1170 setFont_LabelWidget(pageMenuButton, uiContentBold_FontId);
1118 setAlignVisually_LabelWidget(pageMenuButton, iTrue); 1171 setAlignVisually_LabelWidget(pageMenuButton, iTrue);
1119 addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags); 1172 addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags | tight_WidgetFlag);
1120 updateSize_LabelWidget(pageMenuButton); 1173 updateSize_LabelWidget(pageMenuButton);
1121 } 1174 }
1122 /* Reload button. */ { 1175 /* Reload button. */ {
@@ -1146,11 +1199,11 @@ void createUserInterface_Root(iRoot *d) {
1146#if !defined (iHaveNativeMenus) 1199#if !defined (iHaveNativeMenus)
1147# if defined (iPlatformAppleMobile) 1200# if defined (iPlatformAppleMobile)
1148 iLabelWidget *navMenu = 1201 iLabelWidget *navMenu =
1149 makeMenuButton_LabelWidget("\U0001d362", isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, 1202 makeMenuButton_LabelWidget(menu_Icon, isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_,
1150 isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); 1203 isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_));
1151# else 1204# else
1152 iLabelWidget *navMenu = 1205 iLabelWidget *navMenu =
1153 makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_)); 1206 makeMenuButton_LabelWidget(menu_Icon, navMenuItems_, iElemCount(navMenuItems_));
1154# endif 1207# endif
1155 setAlignVisually_LabelWidget(navMenu, iTrue); 1208 setAlignVisually_LabelWidget(navMenu, iTrue);
1156 setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); 1209 setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu");
@@ -1161,11 +1214,11 @@ void createUserInterface_Root(iRoot *d) {
1161 setId_Widget(mainStack, "stack"); 1214 setId_Widget(mainStack, "stack");
1162 addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag | 1215 addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag |
1163 unhittable_WidgetFlag); 1216 unhittable_WidgetFlag);
1164 iWidget *tabBar = makeTabs_Widget(mainStack); 1217 iWidget *docTabs = makeTabs_Widget(mainStack);
1165 setId_Widget(tabBar, "doctabs"); 1218 setId_Widget(docTabs, "doctabs");
1166 setBackgroundColor_Widget(tabBar, uiBackground_ColorId); 1219 setBackgroundColor_Widget(docTabs, uiBackground_ColorId);
1167 appendTabPage_Widget(tabBar, iClob(new_DocumentWidget()), "Document", 0, 0); 1220 appendTabPage_Widget(docTabs, iClob(new_DocumentWidget()), "Document", 0, 0);
1168 iWidget *buttons = findChild_Widget(tabBar, "tabs.buttons"); 1221 iWidget *buttons = findChild_Widget(docTabs, "tabs.buttons");
1169 setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag | 1222 setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag |
1170 drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue); 1223 drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue);
1171 if (deviceType_App() == phone_AppDeviceType) { 1224 if (deviceType_App() == phone_AppDeviceType) {
@@ -1177,9 +1230,9 @@ void createUserInterface_Root(iRoot *d) {
1177 } 1230 }
1178 /* Sidebars. */ { 1231 /* Sidebars. */ {
1179 iWidget *content = findChild_Widget(root, "tabs.content"); 1232 iWidget *content = findChild_Widget(root, "tabs.content");
1180 iSidebarWidget *sidebar1 = new_SidebarWidget(left_SideBarSide); 1233 iSidebarWidget *sidebar1 = new_SidebarWidget(left_SidebarSide);
1181 addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); 1234 addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos);
1182 iSidebarWidget *sidebar2 = new_SidebarWidget(right_SideBarSide); 1235 iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide);
1183 if (deviceType_App() != phone_AppDeviceType) { 1236 if (deviceType_App() != phone_AppDeviceType) {
1184 addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); 1237 addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos);
1185 } 1238 }
@@ -1218,6 +1271,7 @@ void createUserInterface_Root(iRoot *d) {
1218 setHint_InputWidget(input, "${hint.findtext}"); 1271 setHint_InputWidget(input, "${hint.findtext}");
1219 setSelectAllOnFocus_InputWidget(input, iTrue); 1272 setSelectAllOnFocus_InputWidget(input, iTrue);
1220 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ 1273 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */
1274 setEnterInsertsLF_InputWidget(input, iFalse);
1221 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), 1275 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag),
1222 "find.input"); 1276 "find.input");
1223 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); 1277 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next")));
@@ -1237,7 +1291,6 @@ void createUserInterface_Root(iRoot *d) {
1237 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag | 1291 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag |
1238 commandOnClick_WidgetFlag | 1292 commandOnClick_WidgetFlag |
1239 drawBackgroundToBottom_WidgetFlag, iTrue); 1293 drawBackgroundToBottom_WidgetFlag, iTrue);
1240 setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId);
1241 setId_Widget(addChildFlags_Widget(toolBar, 1294 setId_Widget(addChildFlags_Widget(toolBar,
1242 iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), 1295 iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")),
1243 frameless_WidgetFlag), 1296 frameless_WidgetFlag),
@@ -1254,7 +1307,16 @@ void createUserInterface_Root(iRoot *d) {
1254 iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), 1307 iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")),
1255 frameless_WidgetFlag | commandOnClick_WidgetFlag), 1308 frameless_WidgetFlag | commandOnClick_WidgetFlag),
1256 "toolbar.view"); 1309 "toolbar.view");
1257 iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, 1310 setId_Widget(addChildFlags_Widget(toolBar,
1311 iClob(new_LabelWidget("", "toolbar.showident")),
1312 frameless_WidgetFlag |
1313 noBackground_WidgetFlag |
1314 fixedPosition_WidgetFlag |
1315 fixedSize_WidgetFlag |
1316 ignoreForParentWidth_WidgetFlag |
1317 ignoreForParentHeight_WidgetFlag),
1318 "toolbar.name");
1319 iLabelWidget *menuButton = makeMenuButton_LabelWidget(menu_Icon, phoneNavMenuItems_,
1258 iElemCount(phoneNavMenuItems_)); 1320 iElemCount(phoneNavMenuItems_));
1259 setFont_LabelWidget(menuButton, uiLabelLarge_FontId); 1321 setFont_LabelWidget(menuButton, uiLabelLarge_FontId);
1260 setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); 1322 setId_Widget(as_Widget(menuButton), "toolbar.navmenu");
@@ -1262,9 +1324,8 @@ void createUserInterface_Root(iRoot *d) {
1262 iForEach(ObjectList, i, children_Widget(toolBar)) { 1324 iForEach(ObjectList, i, children_Widget(toolBar)) {
1263 iLabelWidget *btn = i.object; 1325 iLabelWidget *btn = i.object;
1264 setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); 1326 setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue);
1265 setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId);
1266 // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId);
1267 } 1327 }
1328 updateToolbarColors_Root(d);
1268 const iMenuItem items[] = { 1329 const iMenuItem items[] = {
1269 { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, 1330 { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" },
1270 { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, 1331 { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" },
@@ -1346,7 +1407,7 @@ void createUserInterface_Root(iRoot *d) {
1346 } 1407 }
1347} 1408}
1348 1409
1349void showToolbars_Root(iRoot *d, iBool show) { 1410void showToolbar_Root(iRoot *d, iBool show) {
1350 /* The toolbar is only used on phone portrait layout. */ 1411 /* The toolbar is only used on phone portrait layout. */
1351 if (isLandscape_App()) return; 1412 if (isLandscape_App()) return;
1352 iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); 1413 iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
diff --git a/src/ui/root.h b/src/ui/root.h
index 96864a15..740e97c9 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -34,7 +34,8 @@ void postArrange_Root (iRoot *);
34void updateMetrics_Root (iRoot *); 34void updateMetrics_Root (iRoot *);
35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ 35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */
36void dismissPortraitPhoneSidebars_Root (iRoot *); 36void dismissPortraitPhoneSidebars_Root (iRoot *);
37void showToolbars_Root (iRoot *, iBool show); 37void showToolbar_Root (iRoot *, iBool show);
38void updateToolbarColors_Root (iRoot *);
38 39
39iInt2 size_Root (const iRoot *); 40iInt2 size_Root (const iRoot *);
40iRect rect_Root (const iRoot *); 41iRect rect_Root (const iRoot *);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 27646b22..c0a22e99 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -523,15 +523,17 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
523 addChild_Widget(div, iClob(makePadding_Widget(gap_UI))); 523 addChild_Widget(div, iClob(makePadding_Widget(gap_UI)));
524 addChild_Widget(div, iClob(new_LabelWidget("${menu.identity.import}", "ident.import"))); 524 addChild_Widget(div, iClob(new_LabelWidget("${menu.identity.import}", "ident.import")));
525 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ 525 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */
526 iLabelWidget *linkLabel;
526 setBackgroundColor_Widget( 527 setBackgroundColor_Widget(
527 addChildFlags_Widget( 528 addChildFlags_Widget(
528 div, 529 div,
529 iClob(new_LabelWidget(format_CStr(cstr_Lang("ident.gotohelp"), 530 iClob(linkLabel = new_LabelWidget(format_CStr(cstr_Lang("ident.gotohelp"),
530 uiTextStrong_ColorEscape, 531 uiTextStrong_ColorEscape,
531 restore_ColorEscape), 532 restore_ColorEscape),
532 "!open newtab:1 gotoheading:1.6 url:about:help")), 533 "!open newtab:1 gotoheading:1.6 url:about:help")),
533 frameless_WidgetFlag | fixedHeight_WidgetFlag | wrapText_WidgetFlag), 534 frameless_WidgetFlag | fixedHeight_WidgetFlag),
534 uiBackgroundSidebar_ColorId); 535 uiBackgroundSidebar_ColorId);
536 setWrap_LabelWidget(linkLabel, iTrue);
535 addChild_Widget(d->blank, iClob(div)); 537 addChild_Widget(d->blank, iClob(div));
536 } 538 }
537// arrange_Widget(d->blank); 539// arrange_Widget(d->blank);
@@ -625,13 +627,14 @@ static void updateMetrics_SidebarWidget_(iSidebarWidget *d) {
625void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { 627void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
626 iWidget *w = as_Widget(d); 628 iWidget *w = as_Widget(d);
627 init_Widget(w); 629 init_Widget(w);
628 setId_Widget(w, side == left_SideBarSide ? "sidebar" : "sidebar2"); 630 setId_Widget(w, side == left_SidebarSide ? "sidebar" : "sidebar2");
629 initCopy_String(&d->cmdPrefix, id_Widget(w)); 631 initCopy_String(&d->cmdPrefix, id_Widget(w));
630 appendChar_String(&d->cmdPrefix, '.'); 632 appendChar_String(&d->cmdPrefix, '.');
631 setBackgroundColor_Widget(w, none_ColorId); 633 setBackgroundColor_Widget(w, none_ColorId);
632 setFlags_Widget(w, 634 setFlags_Widget(w,
633 collapse_WidgetFlag | hidden_WidgetFlag | arrangeHorizontal_WidgetFlag | 635 collapse_WidgetFlag | hidden_WidgetFlag | arrangeHorizontal_WidgetFlag |
634 resizeWidthOfChildren_WidgetFlag, 636 resizeWidthOfChildren_WidgetFlag | noFadeBackground_WidgetFlag |
637 noShadowBorder_WidgetFlag,
635 iTrue); 638 iTrue);
636 iZap(d->modeScroll); 639 iZap(d->modeScroll);
637 d->side = side; 640 d->side = side;
@@ -659,7 +662,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
659 d->actions = NULL; 662 d->actions = NULL;
660 /* On a phone, the right sidebar is used exclusively for Identities. */ 663 /* On a phone, the right sidebar is used exclusively for Identities. */
661 const iBool isPhone = deviceType_App() == phone_AppDeviceType; 664 const iBool isPhone = deviceType_App() == phone_AppDeviceType;
662 if (!isPhone || d->side == left_SideBarSide) { 665 if (!isPhone || d->side == left_SidebarSide) {
663 iWidget *buttons = new_Widget(); 666 iWidget *buttons = new_Widget();
664 setId_Widget(buttons, "buttons"); 667 setId_Widget(buttons, "buttons");
665 for (int i = 0; i < max_SidebarMode; i++) { 668 for (int i = 0; i < max_SidebarMode; i++) {
@@ -678,8 +681,8 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
678 iClob(buttons), 681 iClob(buttons),
679 arrangeHorizontal_WidgetFlag | 682 arrangeHorizontal_WidgetFlag |
680 resizeWidthOfChildren_WidgetFlag | 683 resizeWidthOfChildren_WidgetFlag |
681 arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag | 684 arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag); // |
682 drawBackgroundToHorizontalSafeArea_WidgetFlag); 685// drawBackgroundToHorizontalSafeArea_WidgetFlag);
683 setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); 686 setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId);
684 } 687 }
685 else { 688 else {
@@ -700,13 +703,13 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
700 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); 703 setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI);
701 addChildFlags_Widget(listAndActions, 704 addChildFlags_Widget(listAndActions,
702 iClob(d->list), 705 iClob(d->list),
703 expand_WidgetFlag | drawBackgroundToHorizontalSafeArea_WidgetFlag); 706 expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag);
704 setId_Widget(addChildPosFlags_Widget(listAndActions, 707 setId_Widget(addChildPosFlags_Widget(listAndActions,
705 iClob(d->actions = new_Widget()), 708 iClob(d->actions = new_Widget()),
706 isPhone ? front_WidgetAddPos : back_WidgetAddPos, 709 isPhone ? front_WidgetAddPos : back_WidgetAddPos,
707 arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | 710 arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag |
708 resizeWidthOfChildren_WidgetFlag | 711 resizeWidthOfChildren_WidgetFlag), // |
709 drawBackgroundToHorizontalSafeArea_WidgetFlag), 712// drawBackgroundToHorizontalSafeArea_WidgetFlag),
710 "actions"); 713 "actions");
711 setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); 714 setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId);
712 d->contextItem = NULL; 715 d->contextItem = NULL;
@@ -715,24 +718,24 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
715 addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); 718 addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag);
716 addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); 719 addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag);
717 setMode_SidebarWidget(d, 720 setMode_SidebarWidget(d,
718 deviceType_App() == phone_AppDeviceType && d->side == right_SideBarSide ? 721 deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide ?
719 identities_SidebarMode : bookmarks_SidebarMode); 722 identities_SidebarMode : bookmarks_SidebarMode);
720 d->resizer = 723 d->resizer =
721 addChildFlags_Widget(w, 724 addChildFlags_Widget(w,
722 iClob(new_Widget()), 725 iClob(new_Widget()),
723 hover_WidgetFlag | commandOnClick_WidgetFlag | fixedWidth_WidgetFlag | 726 hover_WidgetFlag | commandOnClick_WidgetFlag | fixedWidth_WidgetFlag |
724 resizeToParentHeight_WidgetFlag | 727 resizeToParentHeight_WidgetFlag |
725 (side == left_SideBarSide ? moveToParentRightEdge_WidgetFlag 728 (side == left_SidebarSide ? moveToParentRightEdge_WidgetFlag
726 : moveToParentLeftEdge_WidgetFlag)); 729 : moveToParentLeftEdge_WidgetFlag));
727 if (deviceType_App() == phone_AppDeviceType) { 730 if (deviceType_App() == phone_AppDeviceType) {
728 setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); 731 setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue);
729 } 732 }
730 setId_Widget(d->resizer, side == left_SideBarSide ? "sidebar.grab" : "sidebar2.grab"); 733 setId_Widget(d->resizer, side == left_SidebarSide ? "sidebar.grab" : "sidebar2.grab");
731 setBackgroundColor_Widget(d->resizer, none_ColorId); 734 setBackgroundColor_Widget(d->resizer, none_ColorId);
732 d->menu = NULL; 735 d->menu = NULL;
733 addAction_Widget(w, SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh"); 736 addAction_Widget(w, SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh");
734 updateMetrics_SidebarWidget_(d); 737 updateMetrics_SidebarWidget_(d);
735 if (side == left_SideBarSide) { 738 if (side == left_SidebarSide) {
736 postCommand_App("~sidebar.update"); /* unread count */ 739 postCommand_App("~sidebar.update"); /* unread count */
737 } 740 }
738} 741}
@@ -773,6 +776,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
773 const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); 776 const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id);
774 postCommandf_App("document.goto loc:%p", head->text.start); 777 postCommandf_App("document.goto loc:%p", head->text.start);
775 dismissPortraitPhoneSidebars_Root(as_Widget(d)->root); 778 dismissPortraitPhoneSidebars_Root(as_Widget(d)->root);
779 setOpenedFromSidebar_DocumentWidget(document_App(), iTrue);
776 break; 780 break;
777 } 781 }
778 case feeds_SidebarMode: { 782 case feeds_SidebarMode: {
@@ -783,7 +787,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
783 case bookmarks_SidebarMode: 787 case bookmarks_SidebarMode:
784 case history_SidebarMode: { 788 case history_SidebarMode: {
785 if (!isEmpty_String(&item->url)) { 789 if (!isEmpty_String(&item->url)) {
786 postCommandf_Root(get_Root(), "open newtab:%d url:%s", 790 postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s",
787 openTabMode_Sym(modState_Keys()), 791 openTabMode_Sym(modState_Keys()),
788 cstr_String(&item->url)); 792 cstr_String(&item->url));
789 } 793 }
@@ -799,7 +803,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
799 updateContextMenu_SidebarWidget_(d); 803 updateContextMenu_SidebarWidget_(d);
800 arrange_Widget(d->menu); 804 arrange_Widget(d->menu);
801 openMenu_Widget(d->menu, 805 openMenu_Widget(d->menu,
802 d->side == left_SideBarSide 806 d->side == left_SidebarSide
803 ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex)) 807 ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex))
804 : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)), 808 : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)),
805 -width_Widget(d->menu))); 809 -width_Widget(d->menu)));
@@ -857,7 +861,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) {
857 if (!isFixedWidth) { 861 if (!isFixedWidth) {
858 /* Even less space if the other sidebar is visible, too. */ 862 /* Even less space if the other sidebar is visible, too. */
859 const int otherWidth = 863 const int otherWidth =
860 width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar")); 864 width_Widget(findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar"));
861 width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); 865 width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth);
862 } 866 }
863 d->widthAsGaps = (float) width / (float) gap_UI; 867 d->widthAsGaps = (float) width / (float) gap_UI;
@@ -938,33 +942,39 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
938 } 942 }
939 const iBool isAnimated = prefs_App()->uiAnimations && 943 const iBool isAnimated = prefs_App()->uiAnimations &&
940 argLabel_Command(cmd, "noanim") == 0 && 944 argLabel_Command(cmd, "noanim") == 0 &&
941 (deviceType_App() != phone_AppDeviceType); 945 (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType);
942 int visX = 0; 946 int visX = 0;
943 if (isVisible_Widget(w)) { 947 if (isVisible_Widget(w)) {
944 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); 948 visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect);
945 } 949 }
946 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); 950 setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w));
951 /* Safe area inset for mobile. */
952 const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0);
947 if (isVisible_Widget(w)) { 953 if (isVisible_Widget(w)) {
948 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); 954 setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse);
949 w->rect.size.x = d->widthAsGaps * gap_UI; 955 w->rect.size.x = d->widthAsGaps * gap_UI;
950 invalidate_ListWidget(d->list); 956 invalidate_ListWidget(d->list);
951 if (isAnimated) { 957 if (isAnimated) {
952 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 958 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
953 setVisualOffset_Widget(w, (d->side == left_SideBarSide ? -1 : 1) * w->rect.size.x, 0, 0); 959 setVisualOffset_Widget(
960 w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0);
954 setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); 961 setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag);
955 } 962 }
956 } 963 }
957 else if (isAnimated) { 964 else if (isAnimated) {
958 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); 965 setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue);
959 if (d->side == right_SideBarSide) { 966 if (d->side == right_SidebarSide) {
960 setVisualOffset_Widget(w, visX, 0, 0); 967 setVisualOffset_Widget(w, visX, 0, 0);
961 setVisualOffset_Widget(w, visX + w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); 968 setVisualOffset_Widget(
969 w, visX + w->rect.size.x + safePad, 300, easeOut_AnimFlag | softer_AnimFlag);
962 } 970 }
963 else { 971 else {
964 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); 972 setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue);
965 setVisualOffset_Widget(w, -w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); 973 setVisualOffset_Widget(
974 w, -w->rect.size.x - safePad, 300, easeOut_AnimFlag | softer_AnimFlag);
966 } 975 }
967 } 976 }
977 updateToolbarColors_Root(w->root);
968 arrange_Widget(w->parent); 978 arrange_Widget(w->parent);
969 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ 979 /* BUG: Rearranging because the arrange above didn't fully resolve the height. */
970 arrange_Widget(w); 980 arrange_Widget(w);
@@ -984,11 +994,21 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
984 /* Handle commands. */ 994 /* Handle commands. */
985 if (isResize_UserEvent(ev)) { 995 if (isResize_UserEvent(ev)) {
986 checkModeButtonLayout_SidebarWidget_(d); 996 checkModeButtonLayout_SidebarWidget_(d);
987 if (deviceType_App() == phone_AppDeviceType && d->side == left_SideBarSide) { 997 if (deviceType_App() == phone_AppDeviceType && d->side == left_SidebarSide) {
998 setFlags_Widget(w, rightEdgeDraggable_WidgetFlag, isPortrait_App());
988 /* In landscape, visibility of the toolbar is controlled separately. */ 999 /* In landscape, visibility of the toolbar is controlled separately. */
989 if (isVisible_Widget(w)) { 1000 if (isVisible_Widget(w)) {
990 postCommand_Widget(w, "sidebar.toggle"); 1001 postCommand_Widget(w, "sidebar.toggle");
991 } 1002 }
1003 setFlags_Widget(findChild_Widget(w, "buttons"),
1004 drawBackgroundToHorizontalSafeArea_WidgetFlag,
1005 isLandscape_App());
1006 setFlags_Widget(findChild_Widget(w, "actions"),
1007 drawBackgroundToHorizontalSafeArea_WidgetFlag,
1008 isLandscape_App());
1009 setFlags_Widget(as_Widget(d->list),
1010 drawBackgroundToHorizontalSafeArea_WidgetFlag,
1011 isLandscape_App());
992 return iFalse; 1012 return iFalse;
993 } 1013 }
994 } 1014 }
@@ -1029,6 +1049,11 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1029 postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode); 1049 postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode);
1030 return iTrue; 1050 return iTrue;
1031 } 1051 }
1052 else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide &&
1053 equal_Command(cmd, "swipe.forward")) {
1054 postCommand_App("sidebar.toggle");
1055 return iTrue;
1056 }
1032 else if (startsWith_CStr(cmd, cstr_String(&d->cmdPrefix))) { 1057 else if (startsWith_CStr(cmd, cstr_String(&d->cmdPrefix))) {
1033 if (handleSidebarCommand_SidebarWidget_(d, cmd + size_String(&d->cmdPrefix))) { 1058 if (handleSidebarCommand_SidebarWidget_(d, cmd + size_String(&d->cmdPrefix))) {
1034 return iTrue; 1059 return iTrue;
@@ -1059,7 +1084,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1059 const int resMid = d->resizer->rect.size.x / 2; 1084 const int resMid = d->resizer->rect.size.x / 2;
1060 setWidth_SidebarWidget( 1085 setWidth_SidebarWidget(
1061 d, 1086 d,
1062 ((d->side == left_SideBarSide 1087 ((d->side == left_SidebarSide
1063 ? inner.x 1088 ? inner.x
1064 : (right_Rect(rect_Root(w->root)) - coord_Command(cmd).x)) + 1089 : (right_Rect(rect_Root(w->root)) - coord_Command(cmd).x)) +
1065 resMid) / (float) gap_UI); 1090 resMid) / (float) gap_UI);
@@ -1528,15 +1553,17 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) {
1528 const iRect bounds = bounds_Widget(w); 1553 const iRect bounds = bounds_Widget(w);
1529 iPaint p; 1554 iPaint p;
1530 init_Paint(&p); 1555 init_Paint(&p);
1531 if (flags_Widget(w) & visualOffset_WidgetFlag && 1556 if (deviceType_App() != phone_AppDeviceType) {
1532 flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { 1557 if (flags_Widget(w) & visualOffset_WidgetFlag &&
1533 fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); 1558 flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) {
1559 fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId);
1560 }
1534 } 1561 }
1535 draw_Widget(w); 1562 draw_Widget(w);
1536 if (isVisible_Widget(w)) { 1563 if (isVisible_Widget(w)) {
1537 drawVLine_Paint( 1564 drawVLine_Paint(
1538 &p, 1565 &p,
1539 addX_I2(d->side == left_SideBarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1), 1566 addX_I2(d->side == left_SidebarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1),
1540 height_Rect(bounds), 1567 height_Rect(bounds),
1541 uiSeparator_ColorId); 1568 uiSeparator_ColorId);
1542 } 1569 }
@@ -1782,13 +1809,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1782 : uiTextFramelessHover_ColorId) 1809 : uiTextFramelessHover_ColorId)
1783 : uiTextDim_ColorId; 1810 : uiTextDim_ColorId;
1784 if (!d->listItem.isSelected && !isUsedOnDomain) { 1811 if (!d->listItem.isSelected && !isUsedOnDomain) {
1785 /* Draw an outline of the icon. */ 1812 drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon));
1786 for (int off = 0; off < 4; ++off) {
1787 drawRange_Text(font,
1788 add_I2(cPos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)),
1789 metaFg,
1790 range_String(&icon));
1791 }
1792 } 1813 }
1793 drawRange_Text(font, 1814 drawRange_Text(font,
1794 cPos, 1815 cPos,
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h
index 2e418aa4..130242ab 100644
--- a/src/ui/sidebarwidget.h
+++ b/src/ui/sidebarwidget.h
@@ -36,8 +36,8 @@ enum iSidebarMode {
36const char * icon_SidebarMode (enum iSidebarMode mode); 36const char * icon_SidebarMode (enum iSidebarMode mode);
37 37
38enum iSidebarSide { 38enum iSidebarSide {
39 left_SideBarSide, 39 left_SidebarSide,
40 right_SideBarSide, 40 right_SidebarSide,
41}; 41};
42 42
43enum iFeedsMode { 43enum iFeedsMode {
diff --git a/src/ui/text.c b/src/ui/text.c
index 55fd4254..edbc6583 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -116,6 +116,7 @@ iDefineTypeConstructionArgs(Glyph, (iChar ch), ch)
116 116
117struct Impl_Font { 117struct Impl_Font {
118 iBlock * data; 118 iBlock * data;
119 enum iTextFont family;
119 stbtt_fontinfo font; 120 stbtt_fontinfo font;
120 float xScale, yScale; 121 float xScale, yScale;
121 int vertOffset; /* offset due to scaling */ 122 int vertOffset; /* offset due to scaling */
@@ -134,6 +135,15 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale,
134 enum iFontSize sizeId, iBool isMonospaced) { 135 enum iFontSize sizeId, iBool isMonospaced) {
135 init_Hash(&d->glyphs); 136 init_Hash(&d->glyphs);
136 d->data = NULL; 137 d->data = NULL;
138 d->family = undefined_TextFont;
139 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */
140 if (data == &fontNunitoRegular_Embedded ||
141 data == &fontNunitoBold_Embedded ||
142 data == &fontNunitoExtraBold_Embedded ||
143 data == &fontNunitoLightItalic_Embedded ||
144 data == &fontNunitoExtraLight_Embedded) {
145 d->family = nunito_TextFont;
146 }
137 d->isMonospaced = isMonospaced; 147 d->isMonospaced = isMonospaced;
138 d->height = height; 148 d->height = height;
139 iZap(d->font); 149 iZap(d->font);
@@ -306,6 +316,7 @@ static void initFonts_Text_(iText *d) {
306 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 316 { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
307 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize }, 317 { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize },
308 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, 318 { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize },
319 { &fontSourceSans3Semibold_Embedded, uiSize * 0.8f, 1.0f, uiNormal_FontSize },
309 /* UI fonts: bold weight */ 320 /* UI fonts: bold weight */
310 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize }, 321 { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize },
311 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, 322 { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize },
@@ -389,7 +400,9 @@ static void initCache_Text_(iText *d) {
389 d->cacheRowAllocStep = iMax(2, textSize / 6); 400 d->cacheRowAllocStep = iMax(2, textSize / 6);
390 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache 401 /* Allocate initial (empty) rows. These will be assigned actual locations in the cache
391 once at least one glyph is stored. */ 402 once at least one glyph is stored. */
392 for (int h = d->cacheRowAllocStep; h <= 2 * textSize + d->cacheRowAllocStep; h += d->cacheRowAllocStep) { 403 for (int h = d->cacheRowAllocStep;
404 h <= 2.5 * textSize + d->cacheRowAllocStep;
405 h += d->cacheRowAllocStep) {
393 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 }); 406 pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 });
394 } 407 }
395 d->cacheBottom = 0; 408 d->cacheBottom = 0;
@@ -1010,10 +1023,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1010 prevCh = 0; 1023 prevCh = 0;
1011 continue; 1024 continue;
1012 } 1025 }
1013 if (ch == '\r') { /* color change */ 1026 if (ch == '\v') { /* color change */
1014 iChar esc = nextChar_(&chPos, args->text.end); 1027 iChar esc = nextChar_(&chPos, args->text.end);
1015 int colorNum = args->color; 1028 int colorNum = args->color;
1016 if (esc == '\r') { /* Extended range. */ 1029 if (esc == '\v') { /* Extended range. */
1017 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; 1030 esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape;
1018 colorNum = esc - asciiBase_ColorEscape; 1031 colorNum = esc - asciiBase_ColorEscape;
1019 } 1032 }
@@ -1128,14 +1141,26 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1128 const iChar next = nextChar_(&peek, args->text.end); 1141 const iChar next = nextChar_(&peek, args->text.end);
1129 if (enableKerning_Text && !d->manualKernOnly && next) { 1142 if (enableKerning_Text && !d->manualKernOnly && next) {
1130 const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next); 1143 const uint32_t nextGlyphIndex = glyphIndex_Font_(glyph->font, next);
1131 const int kern = stbtt_GetGlyphKernAdvance( 1144 int kern = stbtt_GetGlyphKernAdvance(
1132 &glyph->font->font, glyph->glyphIndex, nextGlyphIndex); 1145 &glyph->font->font, glyph->glyphIndex, nextGlyphIndex);
1146 /* Nunito needs some kerning fixes. */
1147 if (glyph->font->family == nunito_TextFont) {
1148 if (ch == 'W' && (next == 'i' || next == 'h')) {
1149 kern = -30;
1150 }
1151 else if (ch == 'T' && next == 'h') {
1152 kern = -15;
1153 }
1154 else if (ch == 'V' && next == 'i') {
1155 kern = -15;
1156 }
1157 }
1133 if (kern) { 1158 if (kern) {
1134// printf("%lc(%u) -> %lc(%u): kern %d (%f)\n", ch, glyph->glyphIndex, next, 1159// printf("%lc(%u) -> %lc(%u): kern %d (%f)\n", ch, glyph->glyphIndex, next,
1135// nextGlyphIndex, 1160// nextGlyphIndex,
1136// kern, d->xScale * kern); 1161// kern, d->xScale * kern);
1137 xpos += d->xScale * kern; 1162 xpos += glyph->font->xScale * kern;
1138 xposExtend += d->xScale * kern; 1163 xposExtend += glyph->font->xScale * kern;
1139 } 1164 }
1140 } 1165 }
1141 } 1166 }
@@ -1313,6 +1338,18 @@ void drawRangeN_Text(int fontId, iInt2 pos, int color, iRangecc text, size_t max
1313 drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars); 1338 drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars);
1314} 1339}
1315 1340
1341void drawOutline_Text(int fontId, iInt2 pos, int outlineColor, int fillColor, iRangecc text) {
1342 for (int off = 0; off < 4; ++off) {
1343 drawRange_Text(fontId,
1344 add_I2(pos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)),
1345 outlineColor,
1346 text);
1347 }
1348 if (fillColor != none_ColorId) {
1349 drawRange_Text(fontId, pos, fillColor, text);
1350 }
1351}
1352
1316iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { 1353iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
1317 iInt2 size = zero_I2(); 1354 iInt2 size = zero_I2();
1318 const char *endp; 1355 const char *endp;
@@ -1354,6 +1391,31 @@ void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, con
1354 deinit_Block(&chars); 1391 deinit_Block(&chars);
1355} 1392}
1356 1393
1394void drawCenteredOutline_Text(int fontId, iRect rect, iBool alignVisual, int outlineColor,
1395 int fillColor, const char *format, ...) {
1396 iBlock chars;
1397 init_Block(&chars, 0); {
1398 va_list args;
1399 va_start(args, format);
1400 vprintf_Block(&chars, format, args);
1401 va_end(args);
1402 }
1403 if (outlineColor != none_ColorId) {
1404 for (int off = 0; off < 4; ++off) {
1405 drawCenteredRange_Text(
1406 fontId,
1407 moved_Rect(rect, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)),
1408 alignVisual,
1409 outlineColor,
1410 range_Block(&chars));
1411 }
1412 }
1413 if (fillColor != none_ColorId) {
1414 drawCenteredRange_Text(fontId, rect, alignVisual, fillColor, range_Block(&chars));
1415 }
1416 deinit_Block(&chars);
1417}
1418
1357void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { 1419void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) {
1358 iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) 1420 iRect textBounds = alignVisual ? visualBounds_Text(fontId, text)
1359 : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; 1421 : (iRect){ zero_I2(), advanceRange_Text(fontId, text) };
@@ -1504,9 +1566,10 @@ static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iB
1504 if (d->texture) { 1566 if (d->texture) {
1505 SDL_Texture *oldTarget = SDL_GetRenderTarget(render); 1567 SDL_Texture *oldTarget = SDL_GetRenderTarget(render);
1506 SDL_SetRenderTarget(render, d->texture); 1568 SDL_SetRenderTarget(render, d->texture);
1569 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
1570 SDL_SetRenderDrawColor(render, 255, 255, 255, 0);
1571 SDL_RenderClear(render);
1507 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ 1572 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */
1508 SDL_SetRenderDrawColor(text_.render, 0, 0, 0, 0);
1509 SDL_RenderClear(text_.render);
1510 const int fg = color | fillBackground_ColorId; 1573 const int fg = color | fillBackground_ColorId;
1511 iRangecc range = range_CStr(text); 1574 iRangecc range = range_CStr(text);
1512 if (maxWidth == 0) { 1575 if (maxWidth == 0) {
diff --git a/src/ui/text.h b/src/ui/text.h
index 2f2bcf3a..5a099142 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -50,6 +50,7 @@ enum iFontId {
50 defaultMedium_FontId, 50 defaultMedium_FontId,
51 defaultBig_FontId, 51 defaultBig_FontId,
52 defaultLarge_FontId, 52 defaultLarge_FontId,
53 defaultSmall_FontId,
53 /* UI fonts: bold weight */ 54 /* UI fonts: bold weight */
54 defaultBold_FontId, 55 defaultBold_FontId,
55 defaultMediumBold_FontId, 56 defaultMediumBold_FontId,
@@ -116,7 +117,8 @@ iLocalDef iBool isJapanese_FontId(enum iFontId id) {
116#define emojiVariationSelector_Char ((iChar) 0xfe0f) 117#define emojiVariationSelector_Char ((iChar) 0xfe0f)
117 118
118enum iTextFont { 119enum iTextFont {
119 nunito_TextFont, 120 undefined_TextFont = -1,
121 nunito_TextFont = 0,
120 firaSans_TextFont, 122 firaSans_TextFont,
121 literata_TextFont, 123 literata_TextFont,
122 tinos_TextFont, 124 tinos_TextFont,
@@ -162,9 +164,11 @@ void draw_Text (int fontId, iInt2 pos, int color, const char *t
162void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); 164void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...);
163void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); 165void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...);
164void drawCenteredRange_Text (int fontId, iRect rect, iBool alignVisual, int color, iRangecc text); 166void drawCenteredRange_Text (int fontId, iRect rect, iBool alignVisual, int color, iRangecc text);
167void drawCenteredOutline_Text(int fontId, iRect rect, iBool alignVisual, int outlineColor, int fillColor, const char *text, ...);
165void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); 168void drawString_Text (int fontId, iInt2 pos, int color, const iString *text);
166void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); 169void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text);
167void drawRangeN_Text (int fontId, iInt2 pos, int color, iRangecc text, size_t maxLen); 170void drawRangeN_Text (int fontId, iInt2 pos, int color, iRangecc text, size_t maxLen);
171void drawOutline_Text (int fontId, iInt2 pos, int outlineColor, int fillColor, iRangecc text);
168void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */ 172void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */
169int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ 173int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */
170 174
diff --git a/src/ui/touch.c b/src/ui/touch.c
index 74a22baf..dac1152e 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -40,6 +40,10 @@ iDeclareType(TouchState)
40#define numHistory_Touch_ 5 40#define numHistory_Touch_ 5
41#define lastIndex_Touch_ (numHistory_Touch_ - 1) 41#define lastIndex_Touch_ (numHistory_Touch_ - 1)
42 42
43static const uint32_t longPressSpanMs_ = 500;
44static const uint32_t shortPressSpanMs_ = 250;
45static const int tapRadiusPt_ = 10;
46
43enum iTouchEdge { 47enum iTouchEdge {
44 none_TouchEdge, 48 none_TouchEdge,
45 left_TouchEdge, 49 left_TouchEdge,
@@ -55,12 +59,13 @@ enum iTouchAxis {
55struct Impl_Touch { 59struct Impl_Touch {
56 SDL_FingerID id; 60 SDL_FingerID id;
57 iWidget *affinity; /* widget on which the touch started */ 61 iWidget *affinity; /* widget on which the touch started */
58 iWidget *edgeDragging; 62// iWidget *edgeDragging;
59 iBool hasMoved; 63 iBool hasMoved;
60 iBool isTapBegun; 64 iBool isTapBegun;
61 iBool isLeftDown; 65 iBool isLeftDown;
62 iBool isTouchDrag; 66 iBool isTouchDrag;
63 iBool isTapAndHold; 67 iBool isTapAndHold;
68 iBool didPostEdgeMove;
64 iBool didBeginOnTouchDrag; 69 iBool didBeginOnTouchDrag;
65 int pinchId; 70 int pinchId;
66 enum iTouchEdge edge; 71 enum iTouchEdge edge;
@@ -132,9 +137,6 @@ static iTouch *find_TouchState_(iTouchState *d, SDL_FingerID id) {
132 return NULL; 137 return NULL;
133} 138}
134 139
135static const uint32_t longPressSpanMs_ = 500;
136static const int tapRadiusPt_ = 10;
137
138iLocalDef float distance_Touch_(const iTouch *d) { 140iLocalDef float distance_Touch_(const iTouch *d) {
139 return length_F3(sub_F3(d->pos[0], d->startPos)); 141 return length_F3(sub_F3(d->pos[0], d->startPos));
140} 142}
@@ -246,6 +248,11 @@ iLocalDef double accurateTicks_(void) {
246 return 1000.0 * (double) count / (double) freq; 248 return 1000.0 * (double) count / (double) freq;
247} 249}
248 250
251static iFloat3 gestureVector_Touch_(const iTouch *d) {
252 const size_t lastIndex = iMin(d->posCount - 1, lastIndex_Touch_);
253 return sub_F3(d->pos[0], d->pos[lastIndex]);
254}
255
249static void update_TouchState_(void *ptr) { 256static void update_TouchState_(void *ptr) {
250 iTouchState *d = ptr; 257 iTouchState *d = ptr;
251 /* Check for long presses to simulate right clicks. */ 258 /* Check for long presses to simulate right clicks. */
@@ -255,6 +262,24 @@ static void update_TouchState_(void *ptr) {
255 if (touch->pinchId || touch->isTouchDrag) { 262 if (touch->pinchId || touch->isTouchDrag) {
256 continue; 263 continue;
257 } 264 }
265 if (touch->edge) {
266 const iFloat3 pos = touch->pos[0];
267 /* Cancel the swipe if the finger doesn't move or moves mostly vertically. */
268 const iFloat3 gestureVector = gestureVector_Touch_(touch);
269 if (fabsf(2 * x_F3(gestureVector)) < fabsf(y_F3(gestureVector)) ||
270 (isStationary_Touch_(touch) && nowTime - touch->startTime > shortPressSpanMs_)) {
271 //const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1;
272 //dispatchClick_Touch_(touch,
273// touch->edge == left_TouchEdge && swipeDir > 0 ? SDL_BUTTON_X1 :
274// touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0);
275// setHover_Widget(NULL);
276 postCommandf_App("edgeswipe.ended abort:1 side:%d id:%llu", touch->edge, touch->id);
277 touch->edge = none_TouchEdge;
278 /* May be a regular drag along the edge so don't remove. */
279 //remove_ArrayIterator(&i);
280 }
281 continue;
282 }
258 /* Holding a touch will reset previous momentum for this widget. */ 283 /* Holding a touch will reset previous momentum for this widget. */
259 if (isStationary_Touch_(touch)) { 284 if (isStationary_Touch_(touch)) {
260 const int elapsed = nowTime - touch->startTime; 285 const int elapsed = nowTime - touch->startTime;
@@ -340,6 +365,7 @@ static void update_TouchState_(void *ptr) {
340 } 365 }
341} 366}
342 367
368#if 0
343static iWidget *findSlidePanel_Widget_(iWidget *d) { 369static iWidget *findSlidePanel_Widget_(iWidget *d) {
344 for (iWidget *w = d; w; w = parent_Widget(w)) { 370 for (iWidget *w = d; w; w = parent_Widget(w)) {
345 if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) { 371 if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) {
@@ -348,6 +374,7 @@ static iWidget *findSlidePanel_Widget_(iWidget *d) {
348 } 374 }
349 return NULL; 375 return NULL;
350} 376}
377#endif
351 378
352static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) { 379static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) {
353 iWidget *affinity = newTouch->affinity; 380 iWidget *affinity = newTouch->affinity;
@@ -365,6 +392,12 @@ static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) {
365 pinch.touchIds[1] = other->id; 392 pinch.touchIds[1] = other->id;
366 newTouch->pinchId = other->pinchId = pinch.id; 393 newTouch->pinchId = other->pinchId = pinch.id;
367 clearWidgetMomentum_TouchState_(d, affinity); 394 clearWidgetMomentum_TouchState_(d, affinity);
395 if (other->edge && other->didPostEdgeMove) {
396 postCommandf_App("edgeswipe.ended abort:1 side:%d id:%llu", other->edge, other->id);
397 other->didPostEdgeMove = iFalse;
398 }
399 other->edge = none_TouchEdge;
400 newTouch->edge = none_TouchEdge;
368 /* Remember current positions to determine pinch amount. */ 401 /* Remember current positions to determine pinch amount. */
369 newTouch->startPos = newTouch->pos[0]; 402 newTouch->startPos = newTouch->pos[0];
370 other->startPos = other->pos[0]; 403 other->startPos = other->pos[0];
@@ -452,6 +485,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
452 edge = right_TouchEdge; 485 edge = right_TouchEdge;
453 } 486 }
454 iWidget *aff = hitChild_Window(window, init_I2(iRound(x), iRound(y_F3(pos)))); 487 iWidget *aff = hitChild_Window(window, init_I2(iRound(x), iRound(y_F3(pos))));
488#if 0
455 if (edge == left_TouchEdge) { 489 if (edge == left_TouchEdge) {
456 dragging = findSlidePanel_Widget_(aff); 490 dragging = findSlidePanel_Widget_(aff);
457 if (dragging) { 491 if (dragging) {
@@ -460,6 +494,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
460 setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); 494 setFlags_Widget(dragging, dragged_WidgetFlag, iTrue);
461 } 495 }
462 } 496 }
497#endif
463 /* TODO: We must retain a reference to the affinity widget, or otherwise it might 498 /* TODO: We must retain a reference to the affinity widget, or otherwise it might
464 be destroyed during the gesture. */ 499 be destroyed during the gesture. */
465// printf("aff:[%p] %s:'%s'\n", aff, aff ? class_Widget(aff)->name : "-", 500// printf("aff:[%p] %s:'%s'\n", aff, aff ? class_Widget(aff)->name : "-",
@@ -469,7 +504,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
469 iTouch newTouch = { 504 iTouch newTouch = {
470 .id = fing->fingerId, 505 .id = fing->fingerId,
471 .affinity = aff, 506 .affinity = aff,
472 .edgeDragging = dragging, 507// .edgeDragging = dragging,
473 .didBeginOnTouchDrag = (flags_Widget(aff) & touchDrag_WidgetFlag) != 0, 508 .didBeginOnTouchDrag = (flags_Widget(aff) & touchDrag_WidgetFlag) != 0,
474 .edge = edge, 509 .edge = edge,
475 .startTime = nowTime, 510 .startTime = nowTime,
@@ -487,6 +522,16 @@ iBool processEvent_Touch(const SDL_Event *ev) {
487 } 522 }
488 else if (ev->type == SDL_FINGERMOTION) { 523 else if (ev->type == SDL_FINGERMOTION) {
489 iTouch *touch = find_TouchState_(d, fing->fingerId); 524 iTouch *touch = find_TouchState_(d, fing->fingerId);
525 if (touch && touch->edge) {
526 clear_Array(d->moms);
527 pushPos_Touch_(touch, pos, nowTime);
528 postCommandf_App("edgeswipe.moved arg:%d side:%d id:%llu",
529 (int) (x_F3(pos) - x_F3(touch->startPos)),
530 touch->edge,
531 touch->id);
532 touch->didPostEdgeMove = iTrue;
533 return iTrue;
534 }
490 if (touch && touch->affinity) { 535 if (touch && touch->affinity) {
491 if (touch->isTouchDrag) { 536 if (touch->isTouchDrag) {
492 dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK); 537 dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK);
@@ -556,36 +601,18 @@ iBool processEvent_Touch(const SDL_Event *ev) {
556 touch->axis = y_TouchAxis; 601 touch->axis = y_TouchAxis;
557 } 602 }
558 } 603 }
559 /* Edge swipe aborted? */ 604 iAssert(touch->edge == none_TouchEdge);
560 if (touch->edge == left_TouchEdge) {
561 if (fing->dx < 0 && x_F3(touch->pos[0]) < tapRadiusPt_ * window->pixelRatio) {
562 touch->edge = none_TouchEdge;
563 if (touch->edgeDragging) {
564 setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse);
565 setVisualOffset_Widget(touch->edgeDragging, 0, 200, easeOut_AnimFlag);
566 touch->edgeDragging = NULL;
567 }
568 }
569 else if (touch->edgeDragging) {
570 setVisualOffset_Widget(touch->edgeDragging, x_F3(pos) - x_F3(touch->startPos), 10, 0);
571 }
572 }
573 if (touch->edge == right_TouchEdge && fing->dx > 0) {
574 touch->edge = none_TouchEdge;
575 }
576 if (touch->edge) {
577 pixels.y = 0;
578 }
579 if (touch->axis == x_TouchAxis) { 605 if (touch->axis == x_TouchAxis) {
580 pixels.y = 0; 606 pixels.y = 0;
581 } 607 }
582 if (touch->axis == y_TouchAxis) { 608 if (touch->axis == y_TouchAxis) {
583 pixels.x = 0; 609 pixels.x = 0;
584 } 610 }
585// printf("%p (%s) py: %i wy: %f acc: %f\n", 611// printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n",
586// touch->affinity, 612// touch->affinity,
587// class_Widget(touch->affinity)->name, 613// class_Widget(touch->affinity)->name,
588// pixels.y, y_F3(amount), y_F3(touch->accum)); 614// pixels.y, y_F3(amount), y_F3(touch->accum),
615// touch->edge);
589 if (pixels.x || pixels.y) { 616 if (pixels.x || pixels.y) {
590 setFocus_Widget(NULL); 617 setFocus_Widget(NULL);
591 dispatchMotion_Touch_(touch->pos[0], 0); 618 dispatchMotion_Touch_(touch->pos[0], 0);
@@ -612,9 +639,21 @@ iBool processEvent_Touch(const SDL_Event *ev) {
612 endPinch_TouchState_(d, touch->pinchId); 639 endPinch_TouchState_(d, touch->pinchId);
613 break; 640 break;
614 } 641 }
642#if 0
615 if (touch->edgeDragging) { 643 if (touch->edgeDragging) {
616 setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); 644 setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse);
617 } 645 }
646#endif
647 if (touch->edge && !isStationary_Touch_(touch)) {
648 const iFloat3 gesture = gestureVector_Touch_(touch);
649 const float pixel = window->pixelRatio;
650 const int moveDir = x_F3(gesture) < -pixel ? -1 : x_F3(gesture) > pixel ? +1 : 0;
651 const int didAbort = (touch->edge == left_TouchEdge && moveDir < 0) ||
652 (touch->edge == right_TouchEdge && moveDir > 0);
653 postCommandf_App("edgeswipe.ended abort:%d side:%d id:%llu", didAbort, touch->edge, touch->id);
654 remove_ArrayIterator(&i);
655 continue;
656 }
618 if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { 657 if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) {
619 if (!touch->isLeftDown && !touch->isTapAndHold) { 658 if (!touch->isLeftDown && !touch->isTapAndHold) {
620 /* This will be a click on a touchDrag widget. */ 659 /* This will be a click on a touchDrag widget. */
@@ -638,6 +677,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
638 const uint32_t duration = nowTime - touch->startTime; 677 const uint32_t duration = nowTime - touch->startTime;
639 const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); 678 const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]);
640 iFloat3 velocity = zero_F3(); 679 iFloat3 velocity = zero_F3();
680#if 0
641 if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && 681 if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) &&
642 !isStationary_Touch_(touch)) { 682 !isStationary_Touch_(touch)) {
643 const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; 683 const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1;
@@ -646,7 +686,9 @@ iBool processEvent_Touch(const SDL_Event *ev) {
646 touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); 686 touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0);
647 setHover_Widget(NULL); 687 setHover_Widget(NULL);
648 } 688 }
649 else { 689 else
690#endif
691 {
650 const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; 692 const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex];
651 const float minVelocity = 400.0f; 693 const float minVelocity = 400.0f;
652 if (elapsed < 150) { 694 if (elapsed < 150) {
diff --git a/src/ui/util.c b/src/ui/util.c
index c4fb8886..e0b05a44 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -710,7 +710,8 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
710 menu, 710 menu,
711 iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), 711 iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)),
712 noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | 712 noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag |
713 drawKey_WidgetFlag | (isInfo ? wrapText_WidgetFlag : 0) | itemFlags); 713 drawKey_WidgetFlag | itemFlags);
714 setWrap_LabelWidget(label, isInfo);
714 haveIcons |= checkIcon_LabelWidget(label); 715 haveIcons |= checkIcon_LabelWidget(label);
715 updateSize_LabelWidget(label); /* drawKey was set */ 716 updateSize_LabelWidget(label); /* drawKey was set */
716 if (isInfo) { 717 if (isInfo) {
@@ -776,7 +777,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
776 if (isInstance_Object(i.object, &Class_LabelWidget)) { 777 if (isInstance_Object(i.object, &Class_LabelWidget)) {
777 iLabelWidget *label = i.object; 778 iLabelWidget *label = i.object;
778 const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); 779 const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape);
779 if (flags_Widget(as_Widget(label)) & wrapText_WidgetFlag) { 780 if (isWrapped_LabelWidget(label)) {
780 continue; 781 continue;
781 } 782 }
782 if (deviceType_App() == desktop_AppDeviceType) { 783 if (deviceType_App() == desktop_AppDeviceType) {
@@ -979,8 +980,8 @@ static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget
979 iClob(newKeyMods_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))), 980 iClob(newKeyMods_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))),
980 addPos); 981 addPos);
981 setFlags_Widget(button, selected_WidgetFlag, isSel); 982 setFlags_Widget(button, selected_WidgetFlag, isSel);
982 setFlags_Widget( 983 setFlags_Widget(button, commandOnClick_WidgetFlag | expand_WidgetFlag, iTrue);
983 button, noTopFrame_WidgetFlag | commandOnClick_WidgetFlag | expand_WidgetFlag, iTrue); 984 setNoTopFrame_LabelWidget((iLabelWidget *) button, iTrue);
984 addChildPos_Widget(pages, page, addPos); 985 addChildPos_Widget(pages, page, addPos);
985 if (tabCount_Widget(tabs) > 1) { 986 if (tabCount_Widget(tabs) > 1) {
986 setFlags_Widget(buttons, hidden_WidgetFlag, iFalse); 987 setFlags_Widget(buttons, hidden_WidgetFlag, iFalse);
@@ -1317,7 +1318,7 @@ void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt)
1317 1318
1318static iBool messageHandler_(iWidget *msg, const char *cmd) { 1319static iBool messageHandler_(iWidget *msg, const char *cmd) {
1319 /* Almost any command dismisses the sheet. */ 1320 /* Almost any command dismisses the sheet. */
1320 /* TODO: Use a "notification" prefix (like `) to ignore all types of commands line this? */ 1321 /* TODO: Add a "notification" type of user events to separate them from user actions. */
1321 if (!(equal_Command(cmd, "media.updated") || 1322 if (!(equal_Command(cmd, "media.updated") ||
1322 equal_Command(cmd, "media.player.update") || 1323 equal_Command(cmd, "media.player.update") ||
1323 equal_Command(cmd, "bookmarks.request.finished") || 1324 equal_Command(cmd, "bookmarks.request.finished") ||
@@ -1326,6 +1327,7 @@ static iBool messageHandler_(iWidget *msg, const char *cmd) {
1326 equal_Command(cmd, "document.request.updated") || 1327 equal_Command(cmd, "document.request.updated") ||
1327 equal_Command(cmd, "scrollbar.fade") || 1328 equal_Command(cmd, "scrollbar.fade") ||
1328 equal_Command(cmd, "widget.overflow") || 1329 equal_Command(cmd, "widget.overflow") ||
1330 equal_Command(cmd, "edgeswipe.ended") ||
1329 startsWith_CStr(cmd, "window."))) { 1331 startsWith_CStr(cmd, "window."))) {
1330 setupSheetTransition_Mobile(msg, iFalse); 1332 setupSheetTransition_Mobile(msg, iFalse);
1331 destroy_Widget(msg); 1333 destroy_Widget(msg);
@@ -1599,8 +1601,6 @@ iWidget *makePreferences_Widget(void) {
1599 /* General preferences. */ { 1601 /* General preferences. */ {
1600 appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values); 1602 appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values);
1601#if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) 1603#if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT)
1602 //addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.downloads}")));
1603 //setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads");
1604 addPrefsInputWithHeading_(headings, values, "prefs.downloads", iClob(new_InputWidget(0))); 1604 addPrefsInputWithHeading_(headings, values, "prefs.downloads", iClob(new_InputWidget(0)));
1605#endif 1605#endif
1606 iInputWidget *searchUrl; 1606 iInputWidget *searchUrl;
@@ -1608,12 +1608,6 @@ iWidget *makePreferences_Widget(void) {
1608 setUrlContent_InputWidget(searchUrl, iTrue); 1608 setUrlContent_InputWidget(searchUrl, iTrue);
1609 addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); 1609 addChild_Widget(headings, iClob(makePadding_Widget(bigGap)));
1610 addChild_Widget(values, iClob(makePadding_Widget(bigGap))); 1610 addChild_Widget(values, iClob(makePadding_Widget(bigGap)));
1611 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}")));
1612 addChild_Widget(values, iClob(makeToggle_Widget("prefs.collapsepreonload")));
1613 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.plaintext.wrap}")));
1614 addChild_Widget(values, iClob(makeToggle_Widget("prefs.plaintext.wrap")));
1615 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.centershort}")));
1616 addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort")));
1617 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hoverlink}"))); 1611 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hoverlink}")));
1618 addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); 1612 addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink")));
1619 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); 1613 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}")));
@@ -1674,26 +1668,6 @@ iWidget *makePreferences_Widget(void) {
1674 } 1668 }
1675 /* User Interface. */ { 1669 /* User Interface. */ {
1676 appendTwoColumnPage_(tabs, "${heading.prefs.interface}", '2', &headings, &values); 1670 appendTwoColumnPage_(tabs, "${heading.prefs.interface}", '2', &headings, &values);
1677#if defined (iPlatformApple) || defined (iPlatformMSys)
1678 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}")));
1679 addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme")));
1680#endif
1681 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}")));
1682 iWidget *themes = new_Widget();
1683 /* Themes. */ {
1684 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.black}", "theme.set arg:0"))), "prefs.theme.0");
1685 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.dark}", "theme.set arg:1"))), "prefs.theme.1");
1686 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.light}", "theme.set arg:2"))), "prefs.theme.2");
1687 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.white}", "theme.set arg:3"))), "prefs.theme.3");
1688 }
1689 addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1690 /* Accents. */
1691 iWidget *accent = new_Widget(); {
1692 setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.teal}", "accent.set arg:0"))), "prefs.accent.0");
1693 setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.orange}", "accent.set arg:1"))), "prefs.accent.1");
1694 }
1695 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.accent}")));
1696 addChildFlags_Widget(values, iClob(accent), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1697#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 1671#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
1698 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); 1672 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}")));
1699 addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); 1673 addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe")));
@@ -1718,6 +1692,27 @@ iWidget *makePreferences_Widget(void) {
1718 } 1692 }
1719 /* Colors. */ { 1693 /* Colors. */ {
1720 appendTwoColumnPage_(tabs, "${heading.prefs.colors}", '3', &headings, &values); 1694 appendTwoColumnPage_(tabs, "${heading.prefs.colors}", '3', &headings, &values);
1695 makeTwoColumnHeading_("${heading.prefs.uitheme}", headings, values);
1696#if defined (iPlatformApple) || defined (iPlatformMSys)
1697 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}")));
1698 addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme")));
1699#endif
1700 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}")));
1701 iWidget *themes = new_Widget();
1702 /* Themes. */ {
1703 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.black}", "theme.set arg:0"))), "prefs.theme.0");
1704 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.dark}", "theme.set arg:1"))), "prefs.theme.1");
1705 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.light}", "theme.set arg:2"))), "prefs.theme.2");
1706 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.white}", "theme.set arg:3"))), "prefs.theme.3");
1707 }
1708 addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1709 /* Accents. */
1710 iWidget *accent = new_Widget(); {
1711 setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.teal}", "accent.set arg:0"))), "prefs.accent.0");
1712 setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.orange}", "accent.set arg:1"))), "prefs.accent.1");
1713 }
1714 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.accent}")));
1715 addChildFlags_Widget(values, iClob(accent), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1721 makeTwoColumnHeading_("${heading.prefs.pagecontent}", headings, values); 1716 makeTwoColumnHeading_("${heading.prefs.pagecontent}", headings, values);
1722 for (int i = 0; i < 2; ++i) { 1717 for (int i = 0; i < 2; ++i) {
1723 const iBool isDark = (i == 0); 1718 const iBool isDark = (i == 0);
@@ -1750,14 +1745,15 @@ iWidget *makePreferences_Widget(void) {
1750 } 1745 }
1751 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1746 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1752 } 1747 }
1753 /* Layout. */ { 1748 /* Fonts. */ {
1754 setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.style}", '4', &headings, &values), "prefs.page.style"); 1749 setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts");
1755 makeTwoColumnHeading_("${heading.prefs.fonts}", headings, values);
1756 /* Fonts. */ { 1750 /* Fonts. */ {
1757 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}"))); 1751 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}")));
1758 addFontButtons_(values, "headingfont"); 1752 addFontButtons_(values, "headingfont");
1759 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); 1753 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}")));
1760 addFontButtons_(values, "font"); 1754 addFontButtons_(values, "font");
1755 addChild_Widget(headings, iClob(makePadding_Widget(bigGap)));
1756 addChild_Widget(values, iClob(makePadding_Widget(bigGap)));
1761 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); 1757 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}")));
1762 iWidget *mono = new_Widget(); { 1758 iWidget *mono = new_Widget(); {
1763 iWidget *tog; 1759 iWidget *tog;
@@ -1789,9 +1785,18 @@ iWidget *makePreferences_Widget(void) {
1789 updateSize_LabelWidget((iLabelWidget *) tog); 1785 updateSize_LabelWidget((iLabelWidget *) tog);
1790 } 1786 }
1791 addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1787 addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1792 addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(new_InputWidget(0))); 1788 addChild_Widget(headings, iClob(makePadding_Widget(bigGap)));
1793 } 1789 addChild_Widget(values, iClob(makePadding_Widget(bigGap)));
1794 makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); 1790 /* Custom font. */ {
1791 iInputWidget *customFont = new_InputWidget(0);
1792 setHint_InputWidget(customFont, "${hint.prefs.userfont}");
1793 addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont));
1794 }
1795 }
1796 }
1797 /* Style. */ {
1798 setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.style}", '5', &headings, &values), "prefs.page.style");
1799// makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values);
1795 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); 1800 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}")));
1796 iWidget *widths = new_Widget(); 1801 iWidget *widths = new_Widget();
1797 /* Line widths. */ { 1802 /* Line widths. */ {
@@ -1811,14 +1816,20 @@ iWidget *makePreferences_Widget(void) {
1811 addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 1816 addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
1812 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.biglede}"))); 1817 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.biglede}")));
1813 addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede"))); 1818 addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede")));
1819 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.plaintext.wrap}")));
1820 addChild_Widget(values, iClob(makeToggle_Widget("prefs.plaintext.wrap")));
1821 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}")));
1822 addChild_Widget(values, iClob(makeToggle_Widget("prefs.collapsepreonload")));
1814// makeTwoColumnHeading_("${heading.prefs.widelayout}", headings, values); 1823// makeTwoColumnHeading_("${heading.prefs.widelayout}", headings, values);
1815 addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); 1824 addChild_Widget(headings, iClob(makePadding_Widget(bigGap)));
1816 addChild_Widget(values, iClob(makePadding_Widget(bigGap))); 1825 addChild_Widget(values, iClob(makePadding_Widget(bigGap)));
1817 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.sideicon}"))); 1826 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.sideicon}")));
1818 addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon"))); 1827 addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon")));
1828 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.centershort}")));
1829 addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort")));
1819 } 1830 }
1820 /* Network. */ { 1831 /* Network. */ {
1821 appendTwoColumnPage_(tabs, "${heading.prefs.network}", '5', &headings, &values); 1832 appendTwoColumnPage_(tabs, "${heading.prefs.network}", '6', &headings, &values);
1822 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}"))); 1833 addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}")));
1823 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); 1834 addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls")));
1824 /* Cache size. */ { 1835 /* Cache size. */ {
@@ -1832,6 +1843,17 @@ iWidget *makePreferences_Widget(void) {
1832 resizeToParentHeight_WidgetFlag); 1843 resizeToParentHeight_WidgetFlag);
1833 setContentPadding_InputWidget(cache, 0, width_Widget(unit) - 4 * gap_UI); 1844 setContentPadding_InputWidget(cache, 0, width_Widget(unit) - 4 * gap_UI);
1834 } 1845 }
1846 /* Memory size. */ {
1847 iInputWidget *mem = new_InputWidget(4);
1848 setSelectAllOnFocus_InputWidget(mem, iTrue);
1849 addPrefsInputWithHeading_(headings, values, "prefs.memorysize", iClob(mem));
1850 iWidget *unit =
1851 addChildFlags_Widget(as_Widget(mem),
1852 iClob(new_LabelWidget("${mb}", NULL)),
1853 frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag |
1854 resizeToParentHeight_WidgetFlag);
1855 setContentPadding_InputWidget(mem, 0, width_Widget(unit) - 4 * gap_UI);
1856 }
1835 makeTwoColumnHeading_("${heading.prefs.certs}", headings, values); 1857 makeTwoColumnHeading_("${heading.prefs.certs}", headings, values);
1836 addPrefsInputWithHeading_(headings, values, "prefs.ca.file", iClob(new_InputWidget(0))); 1858 addPrefsInputWithHeading_(headings, values, "prefs.ca.file", iClob(new_InputWidget(0)));
1837 addPrefsInputWithHeading_(headings, values, "prefs.ca.path", iClob(new_InputWidget(0))); 1859 addPrefsInputWithHeading_(headings, values, "prefs.ca.path", iClob(new_InputWidget(0)));
@@ -1843,7 +1865,7 @@ iWidget *makePreferences_Widget(void) {
1843 /* Keybindings. */ 1865 /* Keybindings. */
1844 if (deviceType_App() == desktop_AppDeviceType) { 1866 if (deviceType_App() == desktop_AppDeviceType) {
1845 iBindingsWidget *bind = new_BindingsWidget(); 1867 iBindingsWidget *bind = new_BindingsWidget();
1846 appendFramelessTabPage_(tabs, iClob(bind), "${heading.prefs.keys}", '6', KMOD_PRIMARY); 1868 appendFramelessTabPage_(tabs, iClob(bind), "${heading.prefs.keys}", '7', KMOD_PRIMARY);
1847 } 1869 }
1848 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); 1870 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1849 updatePreferencesLayout_Widget(dlg); 1871 updatePreferencesLayout_Widget(dlg);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 543b8bc9..992f115d 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -82,10 +82,10 @@ static void visualOffsetAnimation_Widget_(void *ptr) {
82 82
83void deinit_Widget(iWidget *d) { 83void deinit_Widget(iWidget *d) {
84 releaseChildren_Widget(d); 84 releaseChildren_Widget(d);
85//#if !defined (NDEBUG) 85#if 0 && !defined (NDEBUG)
86// printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), 86 printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id),
87// d->flags & keepOnTop_WidgetFlag ? 1 : 0); 87 d->flags & keepOnTop_WidgetFlag ? 1 : 0);
88//#endif 88#endif
89 deinit_String(&d->id); 89 deinit_String(&d->id);
90 if (d->flags & keepOnTop_WidgetFlag) { 90 if (d->flags & keepOnTop_WidgetFlag) {
91 removeAll_PtrArray(onTop_Root(d->root), d); 91 removeAll_PtrArray(onTop_Root(d->root), d);
@@ -321,7 +321,6 @@ static iBool setWidth_Widget_(iWidget *d, int width) {
321 d->rect.size.x = width; 321 d->rect.size.x = width;
322 TRACE(d, "width has changed to %d", width); 322 TRACE(d, "width has changed to %d", width);
323 if (class_Widget(d)->sizeChanged) { 323 if (class_Widget(d)->sizeChanged) {
324 const int oldHeight = d->rect.size.y;
325 class_Widget(d)->sizeChanged(d); 324 class_Widget(d)->sizeChanged(d);
326 } 325 }
327 return iTrue; 326 return iTrue;
@@ -407,6 +406,11 @@ static void boundsOfChildren_Widget_(const iWidget *d, iRect *bounds_out) {
407 iRect childRect = child->rect; 406 iRect childRect = child->rect;
408 if (child->flags & ignoreForParentWidth_WidgetFlag) { 407 if (child->flags & ignoreForParentWidth_WidgetFlag) {
409 childRect.size.x = 0; 408 childRect.size.x = 0;
409 childRect.pos.x = bounds_out->pos.x;
410 }
411 if (child->flags & ignoreForParentHeight_WidgetFlag) {
412 childRect.size.y = 0;
413 childRect.pos.y = bounds_out->pos.y;
410 } 414 }
411 if (isEmpty_Rect(*bounds_out)) { 415 if (isEmpty_Rect(*bounds_out)) {
412 *bounds_out = childRect; 416 *bounds_out = childRect;
@@ -760,6 +764,27 @@ void arrange_Widget(iWidget *d) {
760 } 764 }
761} 765}
762 766
767iBool isBeingVisuallyOffsetByReference_Widget(const iWidget *d) {
768 return visualOffsetByReference_Widget(d) != 0;
769}
770
771int visualOffsetByReference_Widget(const iWidget *d) {
772 if (d->offsetRef && d->flags & refChildrenOffset_WidgetFlag) {
773 int offX = 0;
774 iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) {
775 const iWidget *child = i.object;
776 if (child == d) continue;
777 if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) {
778// const float factor = width_Widget(d) / (float) size_Root(d->root).x;
779 const int invOff = width_Widget(d) - iRound(value_Anim(&child->visualOffset));
780 offX -= invOff / 4;
781 }
782 }
783 return offX;
784 }
785 return 0;
786}
787
763static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { 788static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
764 if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { 789 if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) {
765 const int off = iRound(value_Anim(&d->visualOffset)); 790 const int off = iRound(value_Anim(&d->visualOffset));
@@ -774,14 +799,7 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
774 pos->y -= value_Anim(d->animOffsetRef); 799 pos->y -= value_Anim(d->animOffsetRef);
775 } 800 }
776 if (d->flags & refChildrenOffset_WidgetFlag) { 801 if (d->flags & refChildrenOffset_WidgetFlag) {
777 iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { 802 pos->x += visualOffsetByReference_Widget(d);
778 const iWidget *child = i.object;
779 if (child == d) continue;
780 if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) {
781 const int invOff = size_Root(d->root).x - iRound(value_Anim(&child->visualOffset));
782 pos->x -= invOff / 4;
783 }
784 }
785 } 803 }
786} 804}
787 805
@@ -1057,10 +1075,38 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1057 isCommand_UserEvent(ev, "widget.overflow")) { 1075 isCommand_UserEvent(ev, "widget.overflow")) {
1058 scrollOverflow_Widget(d, 0); /* check bounds */ 1076 scrollOverflow_Widget(d, 0); /* check bounds */
1059 } 1077 }
1060 if (ev->user.code == command_UserEventCode && d->commandHandler && 1078 if (ev->user.code == command_UserEventCode) {
1061 d->commandHandler(d, ev->user.data1)) { 1079 const char *cmd = command_UserEvent(ev);
1062 iAssert(get_Root() == d->root); 1080 if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) &&
1063 return iTrue; 1081 isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag &&
1082 equal_Command(cmd, "edgeswipe.moved")) {
1083 /* Check the side. */
1084 const int side = argLabel_Command(cmd, "side");
1085 if ((side == 1 && d->flags & leftEdgeDraggable_WidgetFlag) ||
1086 (side == 2 && d->flags & rightEdgeDraggable_WidgetFlag)) {
1087 if (~d->flags & dragged_WidgetFlag) {
1088 setFlags_Widget(d, dragged_WidgetFlag, iTrue);
1089 }
1090 setVisualOffset_Widget(d, arg_Command(command_UserEvent(ev)) *
1091 width_Widget(d) / size_Root(d->root).x,
1092 10, 0);
1093 return iTrue;
1094 }
1095 }
1096 if (d->flags & dragged_WidgetFlag && equal_Command(cmd, "edgeswipe.ended")) {
1097 if (argLabel_Command(cmd, "abort")) {
1098 setVisualOffset_Widget(d, 0, 200, easeOut_AnimFlag);
1099 }
1100 else {
1101 postCommand_Widget(
1102 d, argLabel_Command(cmd, "side") == 1 ? "swipe.back" : "swipe.forward");
1103 }
1104 setFlags_Widget(d, dragged_WidgetFlag, iFalse);
1105 }
1106 if (d->commandHandler && d->commandHandler(d, ev->user.data1)) {
1107 iAssert(get_Root() == d->root);
1108 return iTrue;
1109 }
1064 } 1110 }
1065 break; 1111 break;
1066 } 1112 }
@@ -1091,6 +1137,17 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1091 return iFalse; 1137 return iFalse;
1092} 1138}
1093 1139
1140int backgroundFadeColor_Widget(void) {
1141 switch (colorTheme_App()) {
1142 case light_ColorTheme:
1143 return gray25_ColorId;
1144 case pureWhite_ColorTheme:
1145 return gray50_ColorId;
1146 default:
1147 return black_ColorId;
1148 }
1149}
1150
1094void drawBackground_Widget(const iWidget *d) { 1151void drawBackground_Widget(const iWidget *d) {
1095 if (d->flags & noBackground_WidgetFlag) { 1152 if (d->flags & noBackground_WidgetFlag) {
1096 return; 1153 return;
@@ -1107,14 +1164,13 @@ void drawBackground_Widget(const iWidget *d) {
1107 shadowBorder = iFalse; 1164 shadowBorder = iFalse;
1108 } 1165 }
1109 } 1166 }
1110 if (shadowBorder) { 1167 if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) {
1111 iPaint p; 1168 iPaint p;
1112 init_Paint(&p); 1169 init_Paint(&p);
1113 drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); 1170 drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30);
1114 } 1171 }
1115 const iBool isFaded = fadeBackground && 1172 const iBool isFaded = fadeBackground &&
1116 ~d->flags & noFadeBackground_WidgetFlag;/* && 1173 ~d->flags & noFadeBackground_WidgetFlag;
1117 ~d->flags & destroyPending_WidgetFlag;*/
1118 if (isFaded) { 1174 if (isFaded) {
1119 iPaint p; 1175 iPaint p;
1120 init_Paint(&p); 1176 init_Paint(&p);
@@ -1125,19 +1181,7 @@ void drawBackground_Widget(const iWidget *d) {
1125 p.alpha *= (area > 0 ? visibleArea / area : 0.0f); 1181 p.alpha *= (area > 0 ? visibleArea / area : 0.0f);
1126 } 1182 }
1127 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 1183 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
1128 int fadeColor; 1184 fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget());
1129 switch (colorTheme_App()) {
1130 default:
1131 fadeColor = black_ColorId;
1132 break;
1133 case light_ColorTheme:
1134 fadeColor = gray25_ColorId;
1135 break;
1136 case pureWhite_ColorTheme:
1137 fadeColor = gray50_ColorId;
1138 break;
1139 }
1140 fillRect_Paint(&p, rect_Root(d->root), fadeColor);
1141 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 1185 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
1142 } 1186 }
1143 if (d->bgColor >= 0 || d->frameColor >= 0) { 1187 if (d->bgColor >= 0 || d->frameColor >= 0) {
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 8de62b7a..41784b99 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -87,12 +87,12 @@ enum iWidgetFlag {
87}; 87};
88 88
89/* 64-bit extended flags */ 89/* 64-bit extended flags */
90//#define wasCollapsed_WidgetFlag iBit64(32) 90#define rightEdgeDraggable_WidgetFlag iBit64(31)
91#define disabledWhenHidden_WidgetFlag iBit64(32) 91#define disabledWhenHidden_WidgetFlag iBit64(32)
92#define centerHorizontal_WidgetFlag iBit64(33) 92#define centerHorizontal_WidgetFlag iBit64(33)
93#define moveToParentLeftEdge_WidgetFlag iBit64(34) 93#define moveToParentLeftEdge_WidgetFlag iBit64(34)
94#define moveToParentRightEdge_WidgetFlag iBit64(35) 94#define moveToParentRightEdge_WidgetFlag iBit64(35)
95#define wrapText_WidgetFlag iBit64(36) 95#define noShadowBorder_WidgetFlag iBit64(36)
96#define borderTop_WidgetFlag iBit64(37) 96#define borderTop_WidgetFlag iBit64(37)
97#define overflowScrollable_WidgetFlag iBit64(38) 97#define overflowScrollable_WidgetFlag iBit64(38)
98#define focusRoot_WidgetFlag iBit64(39) 98#define focusRoot_WidgetFlag iBit64(39)
@@ -103,7 +103,7 @@ enum iWidgetFlag {
103#define drawBackgroundToVerticalSafeArea_WidgetFlag iBit64(44) 103#define drawBackgroundToVerticalSafeArea_WidgetFlag iBit64(44)
104#define visualOffset_WidgetFlag iBit64(45) 104#define visualOffset_WidgetFlag iBit64(45)
105#define parentCannotResize_WidgetFlag iBit64(46) 105#define parentCannotResize_WidgetFlag iBit64(46)
106#define noTopFrame_WidgetFlag iBit64(47) 106#define ignoreForParentHeight_WidgetFlag iBit64(47)
107#define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */ 107#define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */
108#define extraPadding_WidgetFlag iBit64(49) 108#define extraPadding_WidgetFlag iBit64(49)
109#define borderBottom_WidgetFlag iBit64(50) 109#define borderBottom_WidgetFlag iBit64(50)
@@ -117,8 +117,8 @@ enum iWidgetFlag {
117#define parentCannotResizeHeight_WidgetFlag iBit64(58) 117#define parentCannotResizeHeight_WidgetFlag iBit64(58)
118#define ignoreForParentWidth_WidgetFlag iBit64(59) 118#define ignoreForParentWidth_WidgetFlag iBit64(59)
119#define noFadeBackground_WidgetFlag iBit64(60) 119#define noFadeBackground_WidgetFlag iBit64(60)
120#define destroyPending_WidgetFlag iBit64(61) /* TODO: needed? */ 120#define destroyPending_WidgetFlag iBit64(61)
121#define edgeDraggable_WidgetFlag iBit64(62) 121#define leftEdgeDraggable_WidgetFlag iBit64(62)
122#define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ 122#define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */
123 123
124enum iWidgetAddPos { 124enum iWidgetAddPos {
@@ -242,8 +242,9 @@ iBool isSelected_Widget (const iAnyObject *);
242iBool isUnderKeyRoot_Widget (const iAnyObject *); 242iBool isUnderKeyRoot_Widget (const iAnyObject *);
243iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); 243iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd);
244iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); 244iBool hasParent_Widget (const iWidget *d, const iWidget *someParent);
245iBool isAffectedByVisualOffset_Widget 245iBool isAffectedByVisualOffset_Widget (const iWidget *);
246 (const iWidget *); 246iBool isBeingVisuallyOffsetByReference_Widget (const iWidget *);
247int visualOffsetByReference_Widget (const iWidget *);
247void setId_Widget (iWidget *, const char *id); 248void setId_Widget (iWidget *, const char *id);
248void setFlags_Widget (iWidget *, int64_t flags, iBool set); 249void setFlags_Widget (iWidget *, int64_t flags, iBool set);
249void setPos_Widget (iWidget *, iInt2 pos); 250void setPos_Widget (iWidget *, iInt2 pos);
@@ -276,6 +277,8 @@ void refresh_Widget (const iAnyObject *);
276 277
277iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand); 278iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand);
278 279
280int backgroundFadeColor_Widget (void);
281
279void setFocus_Widget (iWidget *); 282void setFocus_Widget (iWidget *);
280iWidget * focus_Widget (void); 283iWidget * focus_Widget (void);
281void setHover_Widget (iWidget *); 284void setHover_Widget (iWidget *);
diff --git a/src/ui/window.c b/src/ui/window.c
index 96a22fee..f71d8102 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -69,10 +69,6 @@ iDefineTypeConstructionArgs(Window, (iRect rect), rect)
69 69
70/* TODO: Define menus per platform. */ 70/* TODO: Define menus per platform. */
71 71
72#if defined (iPlatformAppleDesktop)
73# define iHaveNativeMenus
74#endif
75
76#if defined (iHaveNativeMenus) 72#if defined (iHaveNativeMenus)
77/* Using native menus. */ 73/* Using native menus. */
78static const iMenuItem fileMenuItems_[] = { 74static const iMenuItem fileMenuItems_[] = {
@@ -202,7 +198,7 @@ static void windowSizeChanged_Window_(iWindow *d) {
202} 198}
203 199
204static void setupUserInterface_Window(iWindow *d) { 200static void setupUserInterface_Window(iWindow *d) {
205#if defined (iPlatformAppleDesktop) 201#if defined (iHaveNativeMenus)
206 insertMacMenus_(); 202 insertMacMenus_();
207#endif 203#endif
208 /* One root is created by default. */ 204 /* One root is created by default. */
@@ -913,7 +909,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
913 } 909 }
914 } 910 }
915 if (isCommand_UserEvent(&event, "lang.changed")) { 911 if (isCommand_UserEvent(&event, "lang.changed")) {
916#if defined (iPlatformAppleDesktop) 912#if defined (iHaveNativeMenus)
917 /* Retranslate the menus. */ 913 /* Retranslate the menus. */
918 removeMacMenus_(); 914 removeMacMenus_();
919 insertMacMenus_(); 915 insertMacMenus_();