summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-14 15:09:54 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-14 15:09:54 +0300
commit21b16be87fec3ec163fb52b94619ba5f3b5df6d2 (patch)
tree73df8c1469f9d7f4186b40ea7b8d34c5b6dfd7bf
parent7f737f1b88448a8a2ccca716a09e3b37fb0c08f9 (diff)
Mobile and iOS: Various fixes and cleanup
Several regressions occurred when the split view mode was implemented.
-rw-r--r--CMakeLists.txt5
-rw-r--r--src/app.c29
-rw-r--r--src/app.h1
-rw-r--r--src/ios.m2
-rw-r--r--src/ui/documentwidget.c25
-rw-r--r--src/ui/inputwidget.c16
-rw-r--r--src/ui/lookupwidget.c2
-rw-r--r--src/ui/root.c21
-rw-r--r--src/ui/scrollwidget.c2
-rw-r--r--src/ui/sidebarwidget.c7
-rw-r--r--src/ui/touch.c6
-rw-r--r--src/ui/util.c2
-rw-r--r--src/ui/widget.c31
-rw-r--r--src/ui/window.c34
14 files changed, 129 insertions, 54 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ffb34d9c..98fd29fa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,8 +22,11 @@ project (Lagrange
22 DESCRIPTION "A Beautiful Gemini Client" 22 DESCRIPTION "A Beautiful Gemini Client"
23 LANGUAGES C 23 LANGUAGES C
24) 24)
25set (IOS_BUNDLE_VERSION 6)
26set (COPYRIGHT_YEAR 2021) 25set (COPYRIGHT_YEAR 2021)
26if (IOS)
27 set (PROJECT_VERSION 1.4) # pinned for TestFlight
28 set (IOS_BUNDLE_VERSION 7) # just increment this
29endif ()
27 30
28# Build configuration. 31# Build configuration.
29option (ENABLE_IPC "Use IPC to communicate between running instances" ON) 32option (ENABLE_IPC "Use IPC to communicate between running instances" ON)
diff --git a/src/app.c b/src/app.c
index 6d172c02..b80953a1 100644
--- a/src/app.c
+++ b/src/app.c
@@ -146,6 +146,7 @@ iDeclareType(Ticker)
146 146
147struct Impl_Ticker { 147struct Impl_Ticker {
148 iAny *context; 148 iAny *context;
149 iRoot *root;
149 void (*callback)(iAny *); 150 void (*callback)(iAny *);
150}; 151};
151 152
@@ -411,15 +412,17 @@ static iBool loadState_App_(iApp *d) {
411 const uint8_t rootIndex = bits & 0xff; 412 const uint8_t rootIndex = bits & 0xff;
412 const uint8_t flags = bits >> 8; 413 const uint8_t flags = bits >> 8;
413 iRoot *root = d->window->roots[rootIndex]; 414 iRoot *root = d->window->roots[rootIndex];
414 if (root && deviceType_App() != phone_AppDeviceType) { 415 if (root) {
415 iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); 416 iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar");
416 iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); 417 iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2");
417 setWidth_SidebarWidget(sidebar, widths[0]);
418 setWidth_SidebarWidget(sidebar2, widths[1]);
419 postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); 418 postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf);
420 postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); 419 postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4);
421 if (flags & 1) postCommand_Root(root, "sidebar.toggle"); 420 if (deviceType_App() != phone_AppDeviceType) {
422 if (flags & 2) postCommand_Root(root, "sidebar2.toggle"); 421 setWidth_SidebarWidget(sidebar, widths[0]);
422 setWidth_SidebarWidget(sidebar2, widths[1]);
423 if (flags & 1) postCommand_Root(root, "sidebar.toggle");
424 if (flags & 2) postCommand_Root(root, "sidebar2.toggle");
425 }
423 } 426 }
424 } 427 }
425 else if (!memcmp(magic, magicTabDocument_App_, 4)) { 428 else if (!memcmp(magic, magicTabDocument_App_, 4)) {
@@ -776,6 +779,10 @@ static void init_App_(iApp *d, int argc, char **argv) {
776 iRelease(openCmds); 779 iRelease(openCmds);
777 } 780 }
778 fetchRemote_Bookmarks(d->bookmarks); 781 fetchRemote_Bookmarks(d->bookmarks);
782 if (deviceType_App() != desktop_AppDeviceType) {
783 /* HACK: Force a resize so widgets update their state. */
784 resize_Window(d->window, -1, -1);
785 }
779} 786}
780 787
781static void deinit_App(iApp *d) { 788static void deinit_App(iApp *d) {
@@ -1175,7 +1182,7 @@ static void runTickers_App_(iApp *d) {
1175 iConstForEach(Array, i, &pending->values) { 1182 iConstForEach(Array, i, &pending->values) {
1176 const iTicker *ticker = i.value; 1183 const iTicker *ticker = i.value;
1177 if (ticker->callback) { 1184 if (ticker->callback) {
1178 setCurrent_Root(findRoot_Window(d->window, ticker->context)); /* root might be NULL */ 1185 setCurrent_Root(ticker->root); /* root might be NULL */
1179 ticker->callback(ticker->context); 1186 ticker->callback(ticker->context);
1180 } 1187 }
1181 } 1188 }
@@ -1406,13 +1413,19 @@ iAny *findWidget_App(const char *id) {
1406 1413
1407void addTicker_App(iTickerFunc ticker, iAny *context) { 1414void addTicker_App(iTickerFunc ticker, iAny *context) {
1408 iApp *d = &app_; 1415 iApp *d = &app_;
1409 insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); 1416 insert_SortedArray(&d->tickers, &(iTicker){ context, get_Root(), ticker });
1417 postRefresh_App();
1418}
1419
1420void addTickerRoot_App(iTickerFunc ticker, iRoot *root, iAny *context) {
1421 iApp *d = &app_;
1422 insert_SortedArray(&d->tickers, &(iTicker){ context, root, ticker });
1410 postRefresh_App(); 1423 postRefresh_App();
1411} 1424}
1412 1425
1413void removeTicker_App(iTickerFunc ticker, iAny *context) { 1426void removeTicker_App(iTickerFunc ticker, iAny *context) {
1414 iApp *d = &app_; 1427 iApp *d = &app_;
1415 remove_SortedArray(&d->tickers, &(iTicker){ context, ticker }); 1428 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker });
1416} 1429}
1417 1430
1418iMimeHooks *mimeHooks_App(void) { 1431iMimeHooks *mimeHooks_App(void) {
diff --git a/src/app.h b/src/app.h
index aa647bfb..0fb6be29 100644
--- a/src/app.h
+++ b/src/app.h
@@ -110,6 +110,7 @@ typedef void (*iTickerFunc)(iAny *);
110 110
111iAny * findWidget_App (const char *id); 111iAny * findWidget_App (const char *id);
112void addTicker_App (iTickerFunc ticker, iAny *context); 112void addTicker_App (iTickerFunc ticker, iAny *context);
113void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context);
113void removeTicker_App (iTickerFunc ticker, iAny *context); 114void removeTicker_App (iTickerFunc ticker, iAny *context);
114void postRefresh_App (void); 115void postRefresh_App (void);
115void postImmediateRefresh_App(void); 116void postImmediateRefresh_App(void);
diff --git a/src/ios.m b/src/ios.m
index 6e2fa64b..7f2751e0 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -196,7 +196,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
196 CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; 196 CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil];
197// NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); 197// NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame));
198 iWindow *window = get_Window(); 198 iWindow *window = get_Window();
199 const iInt2 rootSize = rootSize_Window(window); 199 const iInt2 rootSize = size_Root(window->roots[0]);
200 const int keyTop = keyboardFrame.origin.y * window->pixelRatio; 200 const int keyTop = keyboardFrame.origin.y * window->pixelRatio;
201 setKeyboardHeight_Window(window, rootSize.y - keyTop); 201 setKeyboardHeight_Window(window, rootSize.y - keyTop);
202} 202}
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index d2a97371..23731105 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1444,7 +1444,8 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du
1444 } 1444 }
1445 /* Show and hide toolbar on scroll. */ 1445 /* Show and hide toolbar on scroll. */
1446 if (deviceType_App() == phone_AppDeviceType) { 1446 if (deviceType_App() == phone_AppDeviceType) {
1447 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { 1447 if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 &&
1448 normScrollPos_DocumentWidget_(d) > 0) {
1448 showToolbars_Root(as_Widget(d)->root, offset < 0); 1449 showToolbars_Root(as_Widget(d)->root, offset < 0);
1449 } 1450 }
1450 } 1451 }
@@ -1584,6 +1585,7 @@ static void inputQueryValidator_(iInputWidget *input, void *context) {
1584 avail < 128 ? uiTextStrong_ColorId 1585 avail < 128 ? uiTextStrong_ColorId
1585 : uiTextDim_ColorId); 1586 : uiTextDim_ColorId);
1586 delete_String(url); 1587 delete_String(url);
1588 arrange_Widget(findChild_Widget(dlg, "dialogbuttons"));
1587} 1589}
1588 1590
1589static void checkResponse_DocumentWidget_(iDocumentWidget *d) { 1591static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
@@ -1616,16 +1618,27 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1616 uiTextCaution_ColorEscape "${dlg.input.send}", 1618 uiTextCaution_ColorEscape "${dlg.input.send}",
1617 format_CStr("!document.input.submit doc:%p", d)); 1619 format_CStr("!document.input.submit doc:%p", d));
1618 iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); 1620 iWidget *buttons = findChild_Widget(dlg, "dialogbuttons");
1619 iLabelWidget *lineBreak = 1621 iLabelWidget *lineBreak;
1620 insertChildAfter_Widget(buttons, iClob(new_LabelWidget("${dlg.input.linebreak}" 1622 /* The line break and URL length counters are positioned differently on mobile. */
1621 uiTextAction_ColorEscape 1623 if (deviceType_App() == desktop_AppDeviceType) {
1622 " " shiftReturn_Icon, 1624 lineBreak = new_LabelWidget("${dlg.input.linebreak}"
1623 NULL)), 0); 1625 uiTextAction_ColorEscape
1626 " " shiftReturn_Icon,
1627 NULL);
1628 insertChildAfter_Widget(buttons, iClob(lineBreak), 0);
1629 }
1630 else {
1631 lineBreak = new_LabelWidget("${dlg.input.linebreak}", "text.insert arg:10");
1632 }
1633 setFlags_Widget(as_Widget(lineBreak), frameless_WidgetFlag, iTrue);
1624 setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId); 1634 setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId);
1625 setId_Widget(addChildPosFlags_Widget(buttons, 1635 setId_Widget(addChildPosFlags_Widget(buttons,
1626 iClob(new_LabelWidget("", NULL)), 1636 iClob(new_LabelWidget("", NULL)),
1627 front_WidgetAddPos, frameless_WidgetFlag), 1637 front_WidgetAddPos, frameless_WidgetFlag),
1628 "valueinput.counter"); 1638 "valueinput.counter");
1639 if (deviceType_App() != desktop_AppDeviceType) {
1640 addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos);
1641 }
1629 setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); 1642 setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d);
1630 setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), 1643 setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"),
1631 statusCode == sensitiveInput_GmStatusCode); 1644 statusCode == sensitiveInput_GmStatusCode);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index d9985a8b..59bb8331 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -156,6 +156,9 @@ static iRect contentBounds_InputWidget_(const iInputWidget *d) {
156 neg_I2(addX_I2(padding_(), d->rightPadding))); 156 neg_I2(addX_I2(padding_(), d->rightPadding)));
157 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); 157 shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0));
158 bounds.pos.y += padding_().y / 2; 158 bounds.pos.y += padding_().y / 2;
159 if (flags_Widget(w) & extraPadding_WidgetFlag) {
160 bounds.pos.y += gap_UI;
161 }
159 return bounds; 162 return bounds;
160} 163}
161 164
@@ -955,6 +958,9 @@ static iRect bounds_InputWidget_(const iInputWidget *d) {
955 return bounds; 958 return bounds;
956 } 959 }
957 bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; 960 bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y;
961 if (w->flags & extraPadding_WidgetFlag) {
962 bounds.size.y += 2 * gap_UI;
963 }
958 return bounds; 964 return bounds;
959} 965}
960 966
@@ -1006,6 +1012,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1006 } 1012 }
1007 return iFalse; 1013 return iFalse;
1008 } 1014 }
1015 else if (isCommand_UserEvent(ev, "text.insert")) {
1016 pushUndo_InputWidget_(d);
1017 deleteMarked_InputWidget_(d);
1018 insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev)));
1019 contentsWereChanged_InputWidget_(d);
1020 return iTrue;
1021 }
1009 else if (isMetricsChange_UserEvent(ev)) { 1022 else if (isMetricsChange_UserEvent(ev)) {
1010 updateMetrics_InputWidget_(d); 1023 updateMetrics_InputWidget_(d);
1011 updateLinesAndResize_InputWidget_(d); 1024 updateLinesAndResize_InputWidget_(d);
@@ -1351,7 +1364,8 @@ static void draw_InputWidget_(const iInputWidget *d) {
1351 isFocused ? gap_UI / 4 : 1, 1364 isFocused ? gap_UI / 4 : 1,
1352 isFocused ? uiInputFrameFocused_ColorId 1365 isFocused ? uiInputFrameFocused_ColorId
1353 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); 1366 : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
1354 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); 1367 setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0),
1368 init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0)));
1355 const iRect contentBounds = contentBounds_InputWidget_(d); 1369 const iRect contentBounds = contentBounds_InputWidget_(d);
1356// const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); 1370// const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text));
1357 iInt2 drawPos = topLeft_Rect(contentBounds); 1371 iInt2 drawPos = topLeft_Rect(contentBounds);
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index b06523f9..3eafd4bd 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -662,7 +662,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
662 setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); 662 setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url))));
663#if defined (iPlatformAppleMobile) 663#if defined (iPlatformAppleMobile)
664 /* Adjust height based on keyboard size. */ { 664 /* Adjust height based on keyboard size. */ {
665 w->rect.size.y = visibleRootSize_Window(window).y - top_Rect(bounds_Widget(w)); 665 w->rect.size.y = visibleSize_Root(root).y - top_Rect(bounds_Widget(w));
666 if (deviceType_App() == phone_AppDeviceType) { 666 if (deviceType_App() == phone_AppDeviceType) {
667 float l, r; 667 float l, r;
668 safeAreaInsets_iOS(&l, NULL, &r, NULL); 668 safeAreaInsets_iOS(&l, NULL, &r, NULL);
diff --git a/src/ui/root.c b/src/ui/root.c
index 76ef05c4..233de51c 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -269,6 +269,7 @@ iAnyObject *findWidget_Root(const char *id) {
269} 269}
270 270
271void destroyPending_Root(iRoot *d) { 271void destroyPending_Root(iRoot *d) {
272 iRoot *oldRoot = current_Root();
272 setCurrent_Root(d); 273 setCurrent_Root(d);
273 iForEach(PtrSet, i, d->pendingDestruction) { 274 iForEach(PtrSet, i, d->pendingDestruction) {
274 iWidget *widget = *i.value; 275 iWidget *widget = *i.value;
@@ -282,7 +283,7 @@ void destroyPending_Root(iRoot *d) {
282 iRelease(widget); 283 iRelease(widget);
283 remove_PtrSetIterator(&i); 284 remove_PtrSetIterator(&i);
284 } 285 }
285 setCurrent_Root(NULL); 286 setCurrent_Root(oldRoot);
286} 287}
287 288
288void postArrange_Root(iRoot *d) { 289void postArrange_Root(iRoot *d) {
@@ -414,6 +415,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
414 else { 415 else {
415 addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); 416 addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos);
416 setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); 417 setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI);
418 setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI);
417 } 419 }
418 return iFalse; 420 return iFalse;
419 } 421 }
@@ -595,7 +597,7 @@ static void updateNavBarSize_(iWidget *navBar) {
595 if (isPhone) { 597 if (isPhone) {
596 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", 598 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar",
597 "navbar.ident", "navbar.home", "navbar.menu" }; 599 "navbar.ident", "navbar.home", "navbar.menu" };
598 iWidget *toolBar = findWidget_App("toolbar"); 600 iWidget *toolBar = findWidget_Root("toolbar");
599 setVisualOffset_Widget(toolBar, 0, 0, 0); 601 setVisualOffset_Widget(toolBar, 0, 0, 0);
600 setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); 602 setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App());
601 iForIndices(i, buttons) { 603 iForIndices(i, buttons) {
@@ -1216,7 +1218,9 @@ void createUserInterface_Root(iRoot *d) {
1216 setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | 1218 setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag |
1217 parentCannotResizeHeight_WidgetFlag | 1219 parentCannotResizeHeight_WidgetFlag |
1218 resizeWidthOfChildren_WidgetFlag | 1220 resizeWidthOfChildren_WidgetFlag |
1219 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); 1221 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag |
1222 commandOnClick_WidgetFlag |
1223 drawBackgroundToBottom_WidgetFlag, iTrue);
1220 setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); 1224 setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId);
1221 setId_Widget(addChildFlags_Widget(toolBar, 1225 setId_Widget(addChildFlags_Widget(toolBar,
1222 iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), 1226 iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")),
@@ -1244,17 +1248,17 @@ void createUserInterface_Root(iRoot *d) {
1244 setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); 1248 setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue);
1245 setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); 1249 setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId);
1246 // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); 1250 // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId);
1247 } 1251 }
1248 const iMenuItem items[] = { 1252 const iMenuItem items[] = {
1249 { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, 1253 { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" },
1250 { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, 1254 { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" },
1251 { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, 1255 { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" },
1252 { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, 1256 { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" },
1253 }; 1257 };
1254 iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), 1258 iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"),
1255 items, iElemCount(items)); 1259 items, iElemCount(items));
1256 setId_Widget(menu, "toolbar.menu"); /* view menu */ 1260 setId_Widget(menu, "toolbar.menu"); /* view menu */
1257 } 1261 }
1258#endif 1262#endif
1259 updatePadding_Root(d); 1263 updatePadding_Root(d);
1260 /* Global context menus. */ { 1264 /* Global context menus. */ {
@@ -1319,6 +1323,11 @@ void createUserInterface_Root(iRoot *d) {
1319 } 1323 }
1320 updateMetrics_Root(d); 1324 updateMetrics_Root(d);
1321 updateNavBarSize_(navBar); 1325 updateNavBarSize_(navBar);
1326 if (deviceType_App() == phone_AppDeviceType) {
1327 const float sidebarWidth = width_Widget(root) / (float) gap_UI;
1328 setWidth_SidebarWidget(findChild_Widget(root, "sidebar"), sidebarWidth);
1329 setWidth_SidebarWidget(findChild_Widget(root, "sidebar2"), sidebarWidth);
1330 }
1322} 1331}
1323 1332
1324void showToolbars_Root(iRoot *d, iBool show) { 1333void showToolbars_Root(iRoot *d, iBool show) {
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c
index 32b57c69..ff5144b2 100644
--- a/src/ui/scrollwidget.c
+++ b/src/ui/scrollwidget.c
@@ -108,7 +108,7 @@ static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) {
108 d->fadeStart = SDL_GetTicks() + 1000; 108 d->fadeStart = SDL_GetTicks() + 1000;
109 if (targetValue_Anim(&d->opacity) < opacity) { 109 if (targetValue_Anim(&d->opacity) < opacity) {
110 setValue_Anim(&d->opacity, opacity, 66); 110 setValue_Anim(&d->opacity, opacity, 66);
111 addTicker_App(animateOpacity_ScrollWidget_, d); 111 addTickerRoot_App(animateOpacity_ScrollWidget_, as_Widget(d)->root, d);
112 } 112 }
113 if (!d->willCheckFade && d->fadeEnabled) { 113 if (!d->willCheckFade && d->fadeEnabled) {
114 d->willCheckFade = iTrue; 114 d->willCheckFade = iTrue;
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 1eddd6b7..2c7ee75f 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -555,6 +555,7 @@ static void updateMetrics_SidebarWidget_(iSidebarWidget *d) {
555 .x); 555 .x);
556 } 556 }
557 } 557 }
558 printf("maxButtonLabelWidth: %d\n", d->maxButtonLabelWidth);
558 updateItemHeight_SidebarWidget_(d); 559 updateItemHeight_SidebarWidget_(d);
559} 560}
560 561
@@ -578,11 +579,11 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
578 d->itemFonts[0] = uiContent_FontId; 579 d->itemFonts[0] = uiContent_FontId;
579 d->itemFonts[1] = uiContentBold_FontId; 580 d->itemFonts[1] = uiContentBold_FontId;
580#if defined (iPlatformAppleMobile) 581#if defined (iPlatformAppleMobile)
581 d->widthAsGaps = 73;
582 if (deviceType_App() == phone_AppDeviceType) { 582 if (deviceType_App() == phone_AppDeviceType) {
583 d->itemFonts[0] = defaultBig_FontId; 583 d->itemFonts[0] = defaultBig_FontId;
584 d->itemFonts[1] = defaultBigBold_FontId; 584 d->itemFonts[1] = defaultBigBold_FontId;
585 } 585 }
586 d->widthAsGaps = 73;
586#else 587#else
587 d->widthAsGaps = 60; 588 d->widthAsGaps = 60;
588#endif 589#endif
@@ -806,7 +807,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) {
806 if (isVisible_Widget(w)) { 807 if (isVisible_Widget(w)) {
807 w->rect.size.x = width; 808 w->rect.size.x = width;
808 } 809 }
809 arrange_Widget(findWidget_App("stack")); 810 arrange_Widget(findWidget_Root("stack"));
810 checkModeButtonLayout_SidebarWidget_(d); 811 checkModeButtonLayout_SidebarWidget_(d);
811 updateItemHeight_SidebarWidget_(d); 812 updateItemHeight_SidebarWidget_(d);
812 if (!isFixedWidth && !isRefreshPending_App()) { 813 if (!isFixedWidth && !isRefreshPending_App()) {
@@ -1661,7 +1662,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1661 drawRange_Text( 1662 drawRange_Text(
1662 font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); 1663 font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon));
1663 deinit_String(&icon); 1664 deinit_String(&icon);
1664 drawRange_Text(d->listItem.isSelected ? uiContentBold_FontId : font, 1665 drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font,
1665 add_I2(cPos, init_I2(indent, 0)), 1666 add_I2(cPos, init_I2(indent, 0)),
1666 fg, 1667 fg,
1667 range_String(&d->label)); 1668 range_String(&d->label));
diff --git a/src/ui/touch.c b/src/ui/touch.c
index c5634788..f1aa8f06 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -227,7 +227,7 @@ static void dispatchButtonUp_Touch_(iFloat3 pos) {
227 227
228static void dispatchNotification_Touch_(const iTouch *d, int code) { 228static void dispatchNotification_Touch_(const iTouch *d, int code) {
229 if (d->affinity) { 229 if (d->affinity) {
230 iRoot *oldRoot = get_Root(); 230 iRoot *oldRoot = current_Root();
231 setCurrent_Root(d->affinity->root); 231 setCurrent_Root(d->affinity->root);
232 dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ 232 dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){
233 .type = SDL_USEREVENT, 233 .type = SDL_USEREVENT,
@@ -334,7 +334,7 @@ static void update_TouchState_(void *ptr) {
334 } 334 }
335 /* Keep updating if interaction is still ongoing. */ 335 /* Keep updating if interaction is still ongoing. */
336 if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { 336 if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) {
337 addTicker_App(update_TouchState_, ptr); 337 addTickerRoot_App(update_TouchState_, NULL, ptr);
338 } 338 }
339} 339}
340 340
@@ -479,7 +479,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
479 } 479 }
480 /* This may begin a pinch. */ 480 /* This may begin a pinch. */
481 checkNewPinch_TouchState_(d, back_Array(d->touches)); 481 checkNewPinch_TouchState_(d, back_Array(d->touches));
482 addTicker_App(update_TouchState_, d); 482 addTickerRoot_App(update_TouchState_, NULL, d);
483 } 483 }
484 else if (ev->type == SDL_FINGERMOTION) { 484 else if (ev->type == SDL_FINGERMOTION) {
485 iTouch *touch = find_TouchState_(d, fing->fingerId); 485 iTouch *touch = find_TouchState_(d, fing->fingerId);
diff --git a/src/ui/util.c b/src/ui/util.c
index 2352df7d..fe35031b 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1257,6 +1257,7 @@ static iBool isOmittedPref_(const iString *id) {
1257 static const char *omittedPrefs[] = { 1257 static const char *omittedPrefs[] = {
1258 "prefs.smoothscroll", 1258 "prefs.smoothscroll",
1259 "prefs.imageloadscroll", 1259 "prefs.imageloadscroll",
1260 "prefs.pinsplit",
1260 "prefs.retainwindow", 1261 "prefs.retainwindow",
1261 "prefs.ca.file", 1262 "prefs.ca.file",
1262 "prefs.ca.path", 1263 "prefs.ca.path",
@@ -1597,6 +1598,7 @@ void finalizeSheet_Widget(iWidget *sheet) {
1597 if (element == radioButton_PrefsElement) { 1598 if (element == radioButton_PrefsElement) {
1598 setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); 1599 setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId);
1599 setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); 1600 setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI);
1601 setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse);
1600 setFlags_Widget(value, 1602 setFlags_Widget(value,
1601 borderBottom_WidgetFlag | 1603 borderBottom_WidgetFlag |
1602 resizeToParentWidth_WidgetFlag | 1604 resizeToParentWidth_WidgetFlag |
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 1a0b2ce7..dd0c1b05 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -151,6 +151,11 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
151 removeOne_PtrArray(onTop, d); 151 removeOne_PtrArray(onTop, d);
152 } 152 }
153 } 153 }
154 if (d->flags & arrangeWidth_WidgetFlag &&
155 d->flags & resizeToParentWidth_WidgetFlag) {
156 printf("[Widget] Conflicting flags for ");
157 identify_Widget(d);
158 }
154 } 159 }
155} 160}
156 161
@@ -210,7 +215,7 @@ void setVisualOffset_Widget(iWidget *d, int value, uint32_t span, int animFlags)
210 else { 215 else {
211 setValue_Anim(&d->visualOffset, value, span); 216 setValue_Anim(&d->visualOffset, value, span);
212 d->visualOffset.flags = animFlags; 217 d->visualOffset.flags = animFlags;
213 addTicker_App(visualOffsetAnimation_Widget_, d); 218 addTickerRoot_App(visualOffsetAnimation_Widget_, d->root, d);
214 } 219 }
215} 220}
216 221
@@ -471,12 +476,19 @@ static void arrange_Widget_(iWidget *d) {
471 const int expCount = numExpandingChildren_Widget_(d); 476 const int expCount = numExpandingChildren_Widget_(d);
472 TRACE(d, "%d expanding children", expCount); 477 TRACE(d, "%d expanding children", expCount);
473 /* Resize children to fill the parent widget. */ 478 /* Resize children to fill the parent widget. */
479 iAssert((d->flags & (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag)) !=
480 (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag));
474 if (d->flags & resizeChildren_WidgetFlag) { 481 if (d->flags & resizeChildren_WidgetFlag) {
475 const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0, 482 const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0,
476 (d->flags & resizeHeightOfChildren_WidgetFlag) != 0); 483 (d->flags & resizeHeightOfChildren_WidgetFlag) != 0);
477#if !defined (NDEBUG) 484#if !defined (NDEBUG)
478 /* Check for conflicting flags. */ 485 /* Check for conflicting flags. */
479 if (dirs.x) iAssert(~d->flags & arrangeWidth_WidgetFlag); 486 if (dirs.x) {
487 if (d->flags & arrangeWidth_WidgetFlag) {
488 identify_Widget(d);
489 }
490 iAssert(~d->flags & arrangeWidth_WidgetFlag);
491 }
480 if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag); 492 if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag);
481#endif 493#endif
482 TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y, 494 TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y,
@@ -715,8 +727,10 @@ static void resetArrangement_Widget_(iWidget *d) {
715} 727}
716 728
717void arrange_Widget(iWidget *d) { 729void arrange_Widget(iWidget *d) {
718 resetArrangement_Widget_(d); /* back to initial default sizes */ 730 if (d) {
719 arrange_Widget_(d); 731 resetArrangement_Widget_(d); /* back to initial default sizes */
732 arrange_Widget_(d);
733 }
720} 734}
721 735
722static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { 736static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
@@ -919,9 +933,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
919 } 933 }
920 } 934 }
921 if (class_Widget(d)->processEvent(d, ev)) { 935 if (class_Widget(d)->processEvent(d, ev)) {
936 iAssert(get_Root() == d->root);
922 return iTrue; 937 return iTrue;
923 } 938 }
924 } 939 }
940 iAssert(get_Root() == d->root);
925 return iFalse; 941 return iFalse;
926} 942}
927 943
@@ -994,6 +1010,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
994 } 1010 }
995 if (ev->user.code == command_UserEventCode && d->commandHandler && 1011 if (ev->user.code == command_UserEventCode && d->commandHandler &&
996 d->commandHandler(d, ev->user.data1)) { 1012 d->commandHandler(d, ev->user.data1)) {
1013 iAssert(get_Root() == d->root);
997 return iTrue; 1014 return iTrue;
998 } 1015 }
999 break; 1016 break;
@@ -1586,7 +1603,7 @@ static void printInfo_Widget_(const iWidget *d) {
1586 cstr_String(text_LabelWidget((const iLabelWidget *) d)), 1603 cstr_String(text_LabelWidget((const iLabelWidget *) d)),
1587 cstr_String(command_LabelWidget((const iLabelWidget *) d))); 1604 cstr_String(command_LabelWidget((const iLabelWidget *) d)));
1588 } 1605 }
1589 printf("size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s\n", 1606 printf("size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s%s%s\n",
1590 d->rect.size.x, d->rect.size.y, 1607 d->rect.size.x, d->rect.size.y,
1591 d->minSize.x, d->minSize.y, 1608 d->minSize.x, d->minSize.y,
1592 d->padding[0], d->padding[2], 1609 d->padding[0], d->padding[2],
@@ -1596,7 +1613,9 @@ static void printInfo_Widget_(const iWidget *d) {
1596 d->flags & tight_WidgetFlag ? " tight" : "", 1613 d->flags & tight_WidgetFlag ? " tight" : "",
1597 d->flags & fixedWidth_WidgetFlag ? " fixW" : "", 1614 d->flags & fixedWidth_WidgetFlag ? " fixW" : "",
1598 d->flags & fixedHeight_WidgetFlag ? " fixH" : "", 1615 d->flags & fixedHeight_WidgetFlag ? " fixH" : "",
1599 d->flags & resizeToParentWidth_WidgetFlag ? " rsPrnW" : ""); 1616 d->flags & resizeToParentWidth_WidgetFlag ? " prnW" : "",
1617 d->flags & arrangeWidth_WidgetFlag ? " aW" : "",
1618 d->flags & resizeWidthOfChildren_WidgetFlag ? " rsWChild" : "");
1600} 1619}
1601 1620
1602static void printTree_Widget_(const iWidget *d, int indent) { 1621static void printTree_Widget_(const iWidget *d, int indent) {
diff --git a/src/ui/window.c b/src/ui/window.c
index 232d15e8..5699da4c 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -171,19 +171,6 @@ int numRoots_Window(const iWindow *d) {
171 return num; 171 return num;
172} 172}
173 173
174static void setupUserInterface_Window(iWindow *d) {
175#if defined (iPlatformAppleDesktop)
176 insertMacMenus_();
177#endif
178 /* One root is created by default. */
179 d->roots[0] = new_Root();
180 setCurrent_Root(d->roots[0]);
181 createUserInterface_Root(d->roots[0]);
182 setCurrent_Root(NULL);
183 /* One of the roots always has keyboard input focus. */
184 d->keyRoot = d->roots[0];
185}
186
187static void windowSizeChanged_Window_(iWindow *d) { 174static void windowSizeChanged_Window_(iWindow *d) {
188 const int numRoots = numRoots_Window(d); 175 const int numRoots = numRoots_Window(d);
189 const iInt2 rootSize = d->size; 176 const iInt2 rootSize = d->size;
@@ -214,6 +201,19 @@ static void windowSizeChanged_Window_(iWindow *d) {
214 } 201 }
215} 202}
216 203
204static void setupUserInterface_Window(iWindow *d) {
205#if defined (iPlatformAppleDesktop)
206 insertMacMenus_();
207#endif
208 /* One root is created by default. */
209 d->roots[0] = new_Root();
210 setCurrent_Root(d->roots[0]);
211 createUserInterface_Root(d->roots[0]);
212 setCurrent_Root(NULL);
213 /* One of the roots always has keyboard input focus. */
214 d->keyRoot = d->roots[0];
215}
216
217static void updateSize_Window_(iWindow *d, iBool notifyAlways) { 217static void updateSize_Window_(iWindow *d, iBool notifyAlways) {
218 iInt2 *size = &d->size; 218 iInt2 *size = &d->size;
219 const iInt2 oldSize = *size; 219 const iInt2 oldSize = *size;
@@ -761,7 +761,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
761#if defined (iPlatformMobile) 761#if defined (iPlatformMobile)
762 case SDL_WINDOWEVENT_RESIZED: 762 case SDL_WINDOWEVENT_RESIZED:
763 /* On mobile, this occurs when the display is rotated. */ 763 /* On mobile, this occurs when the display is rotated. */
764 invalidate_Window_(d); 764 invalidate_Window(d);
765 postRefresh_App(); 765 postRefresh_App();
766 return iTrue; 766 return iTrue;
767#endif 767#endif
@@ -772,7 +772,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
772 d->isExposed = iTrue; 772 d->isExposed = iTrue;
773#if defined (iPlatformMobile) 773#if defined (iPlatformMobile)
774 /* Returned to foreground, may have lost buffered content. */ 774 /* Returned to foreground, may have lost buffered content. */
775 invalidate_Window_(d); 775 invalidate_Window(d);
776 postCommand_App("window.unfreeze"); 776 postCommand_App("window.unfreeze");
777#endif 777#endif
778 return iFalse; 778 return iFalse;
@@ -995,7 +995,7 @@ void draw_Window(iWindow *d) {
995 /* Check if root needs resizing. */ { 995 /* Check if root needs resizing. */ {
996 iInt2 renderSize; 996 iInt2 renderSize;
997 SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); 997 SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y);
998 if (!isEqual_I2(renderSize, d->root->rect.size)) { 998 if (!isEqual_I2(renderSize, d->size)) {
999 updateSize_Window_(d, iTrue); 999 updateSize_Window_(d, iTrue);
1000 processEvents_App(postedEventsOnly_AppEventMode); 1000 processEvents_App(postedEventsOnly_AppEventMode);
1001 } 1001 }
@@ -1008,7 +1008,7 @@ void draw_Window(iWindow *d) {
1008 /* Clear the window. The clear color is visible as a border around the window 1008 /* Clear the window. The clear color is visible as a border around the window
1009 when the custom frame is being used. */ { 1009 when the custom frame is being used. */ {
1010#if defined (iPlatformAppleMobile) 1010#if defined (iPlatformAppleMobile)
1011 const iColor back = get_Color(tmBackground_ColorId); 1011 const iColor back = get_Color(uiBackground_ColorId);
1012#else 1012#else
1013 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && 1013 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap &&
1014 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP 1014 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP