summaryrefslogtreecommitdiff
path: root/src/ui/util.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-05-16 13:21:36 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-05-16 13:21:36 +0300
commit97f9a1c9bf49ca67fe1f99c57aa70e0bdf64a466 (patch)
treeb680da6408f78c4555edf3f6b9e86c6ce8925a53 /src/ui/util.c
parent19f29bc4382b3dfe9d3c21ddabd5501919c76566 (diff)
Cleanup: Moved mobile UI code to its own file
The mobile UI related code has grown large enough to warrant a separate file. Also, work-in-progress redo of the Preferences layout so it can be used with landscape as well.
Diffstat (limited to 'src/ui/util.c')
-rw-r--r--src/ui/util.c719
1 files changed, 10 insertions, 709 deletions
diff --git a/src/ui/util.c b/src/ui/util.c
index 82047479..b4c43951 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -837,10 +837,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
837 if (postCommands) { 837 if (postCommands) {
838 postCommand_Widget(d, "menu.opened"); 838 postCommand_Widget(d, "menu.opened");
839 } 839 }
840 if (deviceType_App() == phone_AppDeviceType) { 840 setupMenuTransition_Mobile(d, iTrue);
841 setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0);
842 setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag);
843 }
844} 841}
845 842
846void closeMenu_Widget(iWidget *d) { 843void closeMenu_Widget(iWidget *d) {
@@ -851,14 +848,7 @@ void closeMenu_Widget(iWidget *d) {
851 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); 848 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue);
852 postRefresh_App(); 849 postRefresh_App();
853 postCommand_Widget(d, "menu.closed"); 850 postCommand_Widget(d, "menu.closed");
854 if (deviceType_App() == phone_AppDeviceType) { 851 setupMenuTransition_Mobile(d, iFalse);
855 const iBool wasDragged = iAbs(value_Anim(&d->visualOffset) - 0) > 1;
856 setVisualOffset_Widget(d,
857 flags_Widget(d) & horizontalOffset_WidgetFlag ?
858 width_Widget(d) : height_Widget(d),
859 wasDragged ? 100 : 200,
860 wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag);
861 }
862} 852}
863 853
864iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { 854iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) {
@@ -1127,46 +1117,6 @@ size_t tabCount_Widget(const iWidget *tabs) {
1127 1117
1128/*-----------------------------------------------------------------------------------------------*/ 1118/*-----------------------------------------------------------------------------------------------*/
1129 1119
1130static void acceptFilePath_(iWidget *dlg) {
1131 iInputWidget *input = findChild_Widget(dlg, "input");
1132 iString *path = makeAbsolute_Path(text_InputWidget(input));
1133 postCommandf_App("%s path:%s", cstr_String(id_Widget(dlg)), cstr_String(path));
1134 destroy_Widget(dlg);
1135 delete_String(path);
1136}
1137
1138iBool filePathHandler_(iWidget *dlg, const char *cmd) {
1139 iWidget *ptr = as_Widget(pointer_Command(cmd));
1140 if (equal_Command(cmd, "input.ended")) {
1141 if (hasParent_Widget(ptr, dlg)) {
1142 if (arg_Command(cmd)) {
1143 acceptFilePath_(dlg);
1144 }
1145 else {
1146 destroy_Widget(dlg);
1147 }
1148 return iTrue;
1149 }
1150 return iFalse;
1151 }
1152 else if (ptr && !hasParent_Widget(ptr, dlg)) {
1153 /* Command from outside the dialog, so dismiss the dialog. */
1154 if (!equal_Command(cmd, "focus.lost")) {
1155 destroy_Widget(dlg);
1156 }
1157 return iFalse;
1158 }
1159 else if (equal_Command(cmd, "filepath.cancel")) {
1160 end_InputWidget(findChild_Widget(dlg, "input"), iFalse);
1161 destroy_Widget(dlg);
1162 return iTrue;
1163 }
1164 else if (equal_Command(cmd, "filepath.accept")) {
1165 acceptFilePath_(dlg);
1166 return iTrue;
1167 }
1168 return iFalse;
1169}
1170 1120
1171iWidget *makeSheet_Widget(const char *id) { 1121iWidget *makeSheet_Widget(const char *id) {
1172 iWidget *sheet = new_Widget(); 1122 iWidget *sheet = new_Widget();
@@ -1183,638 +1133,6 @@ iWidget *makeSheet_Widget(const char *id) {
1183 return sheet; 1133 return sheet;
1184} 1134}
1185 1135
1186static void updateSheetPanelMetrics_(iWidget *sheet) {
1187 iWidget *navi = findChild_Widget(sheet, "panel.navi");
1188 iWidget *naviPad = child_Widget(navi, 0);
1189 int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI;
1190#if defined (iPlatformAppleMobile)
1191 float left, right, top, bottom;
1192 safeAreaInsets_iOS(&left, &top, &right, &bottom);
1193 setPadding_Widget(sheet, left, 0, right, 0);
1194 navi->rect.pos = init_I2(left, top);
1195 iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) {
1196 iWidget *pad = *i.value;
1197 setFixedSize_Widget(pad, init1_I2(naviHeight));
1198 }
1199#endif
1200 setFixedSize_Widget(navi, init_I2(-1, naviHeight));
1201}
1202
1203static iBool slidePanelHandler_(iWidget *d, const char *cmd) {
1204 if (equal_Command(cmd, "panel.open")) {
1205 iWidget *button = pointer_Command(cmd);
1206 iWidget *panel = userData_Object(button);
1207 openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2()));
1208 setFlags_Widget(panel, disabled_WidgetFlag, iFalse);
1209 /*
1210 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
1211 setFlags_Widget(d, visualOffset_WidgetFlag | horizontalOffset_WidgetFlag, iTrue);
1212 d->visualOffset = panel->visualOffset;
1213 d->visualOffset.to = -d->visualOffset.from / 3;
1214 d->visualOffset.from = 0;
1215 }*/
1216 return iTrue;
1217 }
1218 if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) &&
1219 argLabel_Command(cmd, "button") == SDL_BUTTON_X1) {
1220 postCommand_App("panel.close");
1221 return iTrue;
1222 }
1223 if (equal_Command(cmd, "panel.close")) {
1224 iBool wasClosed = iFalse;
1225 iForEach(ObjectList, i, children_Widget(parent_Widget(d))) {
1226 iWidget *child = i.object;
1227 if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) {
1228 closeMenu_Widget(child);
1229 /*
1230 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
1231 setFlags_Widget(d, visualOffset_WidgetFlag | horizontalOffset_WidgetFlag, iTrue);
1232 d->visualOffset = child->visualOffset;
1233 d->visualOffset.from = -d->visualOffset.to / 3;
1234 d->visualOffset.to = 0;
1235 }*/
1236 setFlags_Widget(child, disabled_WidgetFlag, iTrue);
1237 setFocus_Widget(NULL);
1238 updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back");
1239 wasClosed = iTrue;
1240 }
1241 }
1242 if (!wasClosed) {
1243 postCommand_App("prefs.dismiss");
1244 }
1245 return iTrue;
1246 }
1247 if (equal_Command(cmd, "document.changed")) {
1248 postCommand_App("prefs.dismiss");
1249 return iFalse;
1250 }
1251 if (equal_Command(cmd, "window.resized")) {
1252 updateSheetPanelMetrics_(parent_Widget(d));
1253 }
1254 return iFalse;
1255}
1256
1257static iBool isTwoColumnPage_(iWidget *d) {
1258 if (cmp_String(id_Widget(d), "dialogbuttons") == 0 ||
1259 cmp_String(id_Widget(d), "prefs.tabs") == 0) {
1260 return iFalse;
1261 }
1262 if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) {
1263 return class_Widget(child_Widget(d, 0)) == &Class_Widget &&
1264 class_Widget(child_Widget(d, 1)) == &Class_Widget;
1265 }
1266 return iFalse;
1267}
1268
1269static iBool isOmittedPref_(const iString *id) {
1270 static const char *omittedPrefs[] = {
1271 "prefs.smoothscroll",
1272 "prefs.imageloadscroll",
1273 "prefs.pinsplit",
1274 "prefs.retainwindow",
1275 "prefs.ca.file",
1276 "prefs.ca.path",
1277 };
1278 iForIndices(i, omittedPrefs) {
1279 if (cmp_String(id, omittedPrefs[i]) == 0) {
1280 return iTrue;
1281 }
1282 }
1283 return iFalse;
1284}
1285
1286enum iPrefsElement {
1287 panelTitle_PrefsElement,
1288 heading_PrefsElement,
1289 toggle_PrefsElement,
1290 dropdown_PrefsElement,
1291 radioButton_PrefsElement,
1292 textInput_PrefsElement,
1293};
1294
1295static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags,
1296 enum iPrefsElement elementType,
1297 enum iPrefsElement precedingElementType) {
1298 /* Erase redundant/unused headings. */
1299 if (precedingElementType == heading_PrefsElement &&
1300 (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) {
1301 iRelease(removeChild_Widget(panel, lastChild_Widget(panel)));
1302 if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) {
1303 iRelease(removeChild_Widget(panel, lastChild_Widget(panel)));
1304 }
1305 }
1306 if (child) {
1307 /* Insert padding between different element types. */
1308 if (precedingElementType != panelTitle_PrefsElement) {
1309 if (elementType == heading_PrefsElement ||
1310 (elementType == toggle_PrefsElement &&
1311 precedingElementType != toggle_PrefsElement &&
1312 precedingElementType != heading_PrefsElement) ||
1313 (elementType == dropdown_PrefsElement &&
1314 precedingElementType != dropdown_PrefsElement &&
1315 precedingElementType != heading_PrefsElement) ||
1316 (elementType == textInput_PrefsElement &&
1317 precedingElementType != textInput_PrefsElement &&
1318 precedingElementType != heading_PrefsElement)) {
1319 addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
1320 }
1321 }
1322 if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) ||
1323 (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) {
1324 flags |= borderTop_WidgetFlag;
1325 }
1326 return addChildFlags_Widget(panel, child, flags);
1327 }
1328 return NULL;
1329}
1330
1331static void stripTrailingColon_(iLabelWidget *label) {
1332 const iString *text = text_LabelWidget(label);
1333 if (endsWith_String(text, ":")) {
1334 iString *mod = copy_String(text);
1335 removeEnd_String(mod, 1);
1336 updateText_LabelWidget(label, mod);
1337 delete_String(mod);
1338 }
1339}
1340
1341static iLabelWidget *makePanelButton_(const char *text, const char *command) {
1342 iLabelWidget *btn = new_LabelWidget(text, command);
1343 setFlags_Widget(as_Widget(btn),
1344 borderBottom_WidgetFlag | alignLeft_WidgetFlag |
1345 frameless_WidgetFlag | extraPadding_WidgetFlag,
1346 iTrue);
1347 checkIcon_LabelWidget(btn);
1348 setFont_LabelWidget(btn, defaultBig_FontId);
1349 setTextColor_LabelWidget(btn, uiTextStrong_ColorId);
1350 setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId);
1351 return btn;
1352}
1353
1354static iWidget *makeValuePadding_(iWidget *value) {
1355 iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL;
1356 if (input) {
1357 setFont_InputWidget(input, defaultBig_FontId);
1358 setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI);
1359 }
1360 iWidget *pad = new_Widget();
1361 setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId);
1362 setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI);
1363 addChild_Widget(pad, iClob(value));
1364 setFlags_Widget(pad,
1365 borderBottom_WidgetFlag |
1366 arrangeVertical_WidgetFlag |
1367 resizeToParentWidth_WidgetFlag |
1368 resizeWidthOfChildren_WidgetFlag |
1369 arrangeHeight_WidgetFlag,
1370 iTrue);
1371 return pad;
1372}
1373
1374static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) {
1375 iWidget *div = new_Widget();
1376 setFlags_Widget(div,
1377 borderBottom_WidgetFlag | arrangeHeight_WidgetFlag |
1378 resizeWidthOfChildren_WidgetFlag |
1379 arrangeHorizontal_WidgetFlag, iTrue);
1380 setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId);
1381 setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI);
1382 addChildFlags_Widget(div, iClob(heading), 0);
1383 //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value)));
1384 setFont_LabelWidget(heading, defaultBig_FontId);
1385 setTextColor_LabelWidget(heading, uiTextStrong_ColorId);
1386 if (isInstance_Object(value, &Class_InputWidget)) {
1387 addChildFlags_Widget(div, iClob(value), expand_WidgetFlag);
1388 }
1389 else if (isInstance_Object(value, &Class_LabelWidget) &&
1390 cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) {
1391 addChildFlags_Widget(div, iClob(value), expand_WidgetFlag);
1392 /* TODO: This doesn't work? */
1393// setCommand_LabelWidget(heading,
1394// collectNewFormat_String("!%s ptr:%p",
1395// cstr_String(command_LabelWidget((iLabelWidget *) value)),
1396// value));
1397 }
1398 else {
1399 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
1400 addChild_Widget(div, iClob(value));
1401 }
1402 return div;
1403}
1404
1405static iWidget *addChildPanel_(iWidget *sheet, iLabelWidget *panelButton,
1406 const iString *titleText) {
1407 iWidget *owner = new_Widget();
1408 setId_Widget(owner, "panel");
1409 setUserData_Object(panelButton, owner);
1410 setBackgroundColor_Widget(owner, uiBackground_ColorId);
1411 setId_Widget(addChild_Widget(owner, iClob(makePadding_Widget(0))), "panel.toppad");
1412 if (titleText) {
1413 iLabelWidget *title =
1414 addChildFlags_Widget(owner,
1415 iClob(new_LabelWidget(cstr_String(titleText), NULL)),
1416 alignLeft_WidgetFlag | frameless_WidgetFlag);
1417 setFont_LabelWidget(title, uiLabelLargeBold_FontId);
1418 setTextColor_LabelWidget(title, uiHeading_ColorId);
1419 }
1420 addChildFlags_Widget(sheet,
1421 iClob(owner),
1422 focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag |
1423 arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
1424 arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag |
1425 horizontalOffset_WidgetFlag | edgeDraggable_WidgetFlag |
1426 commandOnClick_WidgetFlag);
1427 return owner;
1428}
1429
1430void finalizeSheet_Widget(iWidget *sheet) {
1431 /* The sheet contents are completely rearranged and restyled on a phone.
1432 We'll set up a linear fullscreen arrangement of the widgets. Sheets are already
1433 scrollable so they can be taller than the display. In hindsight, it may have been
1434 easier to create phone versions of each dialog, but at least this works with any
1435 future changes to the UI (..."works"). At least this way it is possible to enforce
1436 a consistent styling. */
1437 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) {
1438 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) {
1439 /* Already finalized. */
1440 arrange_Widget(sheet);
1441 postRefresh_App();
1442 return;
1443 }
1444 /* Modify the top sheet to act as a fullscreen background. */
1445 setPadding1_Widget(sheet, 0);
1446 setBackgroundColor_Widget(sheet, uiBackground_ColorId);
1447 setFlags_Widget(sheet,
1448 keepOnTop_WidgetFlag |
1449 parentCannotResize_WidgetFlag |
1450 arrangeSize_WidgetFlag |
1451 centerHorizontal_WidgetFlag |
1452 arrangeVertical_WidgetFlag |
1453 arrangeHorizontal_WidgetFlag |
1454 overflowScrollable_WidgetFlag,
1455 iFalse);
1456 setFlags_Widget(sheet,
1457 commandOnClick_WidgetFlag |
1458 frameless_WidgetFlag |
1459 resizeWidthOfChildren_WidgetFlag |
1460 edgeDraggable_WidgetFlag,
1461 iTrue);
1462 iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */
1463 iPtrArray * panelButtons = collect_PtrArray(new_PtrArray());
1464 iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs");
1465 iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0));
1466 const iBool isPrefs = (prefsTabs != NULL);
1467 const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag |
1468 frameless_WidgetFlag | extraPadding_WidgetFlag;
1469 iWidget *topPanel = new_Widget();
1470 setFlags_Widget(topPanel, topPanelOffset_WidgetFlag, iTrue); /* slide with children */
1471 setId_Widget(topPanel, "panel.top");
1472 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
1473 if (prefsTabs) {
1474 iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */
1475 iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons")));
1476 /* Pull out the pages and make them panels. */
1477 iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages");
1478 size_t pageCount = tabCount_Widget(prefsTabs);
1479 for (size_t i = 0; i < pageCount; i++) {
1480 iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0))));
1481 iWidget *page = removeTabPage_Widget(prefsTabs, 0);
1482 iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */
1483 pushBack_PtrArray(contents, ref_Object(pageContent));
1484 iLabelWidget *panelButton;
1485 pushBack_PtrArray(panelButtons,
1486 addChildFlags_Widget(topPanel,
1487 iClob(panelButton = makePanelButton_(
1488 i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text),
1489 "panel.open")),
1490 (i == 0 ? borderTop_WidgetFlag : 0) |
1491 chevron_WidgetFlag));
1492 const iChar icons[] = {
1493 0x02699, /* gear */
1494 0x1f4f1, /* mobile phone */
1495 0x1f3a8, /* palette */
1496 0x1f523,
1497 0x1f5a7, /* computer network */
1498 };
1499 setIcon_LabelWidget(panelButton, icons[i]);
1500// setFont_LabelWidget(panelButton, defaultBig_FontId);
1501// setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId);
1502 iRelease(page);
1503 delete_String(text);
1504 }
1505 destroy_Widget(prefsTabs);
1506 }
1507 iForEach(ObjectList, i, children_Widget(sheet)) {
1508 iWidget *child = i.object;
1509 if (isTwoColumnPage_(child)) {
1510 pushBack_PtrArray(contents, removeChild_Widget(sheet, child));
1511 }
1512 else {
1513 removeChild_Widget(sheet, child);
1514 addChild_Widget(topPanel, child);
1515 iRelease(child);
1516 }
1517 }
1518 const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons));
1519 addChildFlags_Widget(sheet, iClob(topPanel),
1520 arrangeVertical_WidgetFlag |
1521 resizeWidthOfChildren_WidgetFlag |
1522 arrangeHeight_WidgetFlag |
1523 overflowScrollable_WidgetFlag |
1524 commandOnClick_WidgetFlag);
1525 setCommandHandler_Widget(topPanel, slidePanelHandler_);
1526 iForEach(PtrArray, j, contents) {
1527 iWidget *owner = topPanel;
1528 if (useSlidePanels) {
1529 /* Create a new child panel. */
1530 iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j));
1531 owner = addChildPanel_(sheet, button,
1532 collect_String(upper_String(text_LabelWidget(button))));
1533 }
1534 iWidget *pageContent = j.ptr;
1535 iWidget *headings = child_Widget(pageContent, 0);
1536 iWidget *values = child_Widget(pageContent, 1);
1537 enum iPrefsElement prevElement = panelTitle_PrefsElement;
1538 /* Identify the types of controls in the dialog and restyle/organize them. */
1539 while (!isEmpty_ObjectList(children_Widget(headings))) {
1540 iWidget *heading = child_Widget(headings, 0);
1541 iWidget *value = child_Widget(values, 0);
1542 removeChild_Widget(headings, heading);
1543 removeChild_Widget(values, value);
1544 /* Can we ignore these widgets? */
1545 if (isOmittedPref_(id_Widget(value)) ||
1546 (class_Widget(heading) == &Class_Widget &&
1547 class_Widget(value) == &Class_Widget) /* just padding */) {
1548 iRelease(heading);
1549 iRelease(value);
1550 continue;
1551 }
1552 enum iPrefsElement element = toggle_PrefsElement;
1553 iLabelWidget *headingLabel = NULL;
1554 iLabelWidget *valueLabel = NULL;
1555 iInputWidget *valueInput = NULL;
1556 const iBool isMenuButton = findChild_Widget(value, "menu") != NULL;
1557 if (isInstance_Object(heading, &Class_LabelWidget)) {
1558 headingLabel = (iLabelWidget *) heading;
1559 stripTrailingColon_(headingLabel);
1560 }
1561 if (isInstance_Object(value, &Class_LabelWidget)) {
1562 valueLabel = (iLabelWidget *) value;
1563 setFont_LabelWidget(valueLabel, defaultBig_FontId);
1564 }
1565 if (isInstance_Object(value, &Class_InputWidget)) {
1566 valueInput = (iInputWidget *) value;
1567 setFlags_Widget(value, borderBottom_WidgetFlag, iFalse);
1568 element = textInput_PrefsElement;
1569 }
1570 if (childCount_Widget(value) >= 2) {
1571 if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) {
1572 element = textInput_PrefsElement;
1573 setPadding_Widget(value, 0, 0, gap_UI, 0);
1574 valueInput = child_Widget(value, 0);
1575 }
1576 }
1577 if (valueInput) {
1578 setFont_InputWidget(valueInput, defaultBig_FontId);
1579 setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0);
1580 }
1581 /* Toggles have the button on the right. */
1582 if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) {
1583 element = toggle_PrefsElement;
1584 addPanelChild_(owner,
1585 iClob(makeValuePaddingWithHeading_(headingLabel, value)),
1586 0,
1587 element,
1588 prevElement);
1589 }
1590 else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) {
1591 element = heading_PrefsElement;
1592 iRelease(value);
1593 addPanelChild_(owner, iClob(heading), 0, element, prevElement);
1594 setFont_LabelWidget(headingLabel, uiLabel_FontId);
1595 }
1596 else if (isMenuButton) {
1597 element = dropdown_PrefsElement;
1598 setFlags_Widget(value,
1599 alignRight_WidgetFlag | noBackground_WidgetFlag |
1600 frameless_WidgetFlag, iTrue);
1601 setFlags_Widget(value, alignLeft_WidgetFlag, iFalse);
1602 iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0,
1603 element, prevElement);
1604 pad->padding[2] = gap_UI;
1605 }
1606 else if (valueInput) {
1607 addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0,
1608 element, prevElement);
1609 }
1610 else {
1611 if (childCount_Widget(value) >= 2) {
1612 element = radioButton_PrefsElement;
1613 /* Always padding before radio buttons. */
1614 addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
1615 }
1616 addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag);
1617 if (headingLabel) {
1618 setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId);
1619 setText_LabelWidget(headingLabel,
1620 collect_String(upper_String(text_LabelWidget(headingLabel))));
1621 }
1622 addPanelChild_(owner, iClob(value), 0, element, prevElement);
1623 /* Radio buttons expand to fill the space. */
1624 if (element == radioButton_PrefsElement) {
1625 setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId);
1626 setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI);
1627 setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse);
1628 setFlags_Widget(value,
1629 borderBottom_WidgetFlag |
1630 resizeToParentWidth_WidgetFlag |
1631 resizeWidthOfChildren_WidgetFlag,
1632 iTrue);
1633 iForEach(ObjectList, sub, children_Widget(value)) {
1634 if (isInstance_Object(sub.object, &Class_LabelWidget)) {
1635 iLabelWidget *opt = sub.object;
1636 setFont_LabelWidget(opt, defaultMedium_FontId);
1637 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue);
1638 }
1639 }
1640 }
1641 }
1642 prevElement = element;
1643 }
1644 addPanelChild_(owner, NULL, 0, 0, prevElement);
1645 destroy_Widget(pageContent);
1646 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue);
1647 }
1648 destroyPending_Root(sheet->root);
1649 /* Additional elements for preferences. */
1650 if (isPrefs) {
1651 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
1652 addChildFlags_Widget(topPanel,
1653 iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")),
1654 borderTop_WidgetFlag);
1655 iLabelWidget *aboutButton = addChildFlags_Widget(topPanel,
1656 iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")),
1657 chevron_WidgetFlag);
1658 /* The About panel. */ {
1659 iWidget *panel = addChildPanel_(sheet, aboutButton, NULL);
1660 iString *msg = collectNew_String();
1661 setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION);
1662#if defined (iPlatformAppleMobile)
1663 appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")");
1664#endif
1665 addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)),
1666 frameless_WidgetFlag);
1667 addChildFlags_Widget(panel,
1668 iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi",
1669 "!open url:https://skyjake.fi/@jk")),
1670 borderTop_WidgetFlag);
1671 addChildFlags_Widget(panel,
1672 iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}",
1673 "!open url:about:version")),
1674 0);
1675 addChildFlags_Widget(panel,
1676 iClob(makePanelButton_(info_Icon " ${menu.aboutpages}",
1677 "!open url:about:about")),
1678 0);
1679 addChildFlags_Widget(panel,
1680 iClob(makePanelButton_(bug_Icon " ${menu.debug}",
1681 "!open url:about:debug")),
1682 0);
1683 }
1684 }
1685 else {
1686 setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue);
1687 /* Update heading style. */
1688 setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId);
1689 setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue);
1690 }
1691 if (findChild_Widget(sheet, "valueinput.prompt")) {
1692 iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt");
1693 setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue);
1694 iInputWidget *input = findChild_Widget(sheet, "input");
1695 removeChild_Widget(parent_Widget(input), input);
1696 addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input))));
1697 }
1698 /* Top padding for each panel, to account for the overlaid navbar. */ {
1699 setId_Widget(addChildPos_Widget(topPanel,
1700 iClob(makePadding_Widget(0)), front_WidgetAddPos),
1701 "panel.toppad");
1702 }
1703 /* Navbar. */ {
1704 iWidget *navi = new_Widget();
1705 setId_Widget(navi, "panel.navi");
1706 setBackgroundColor_Widget(navi, uiBackground_ColorId);
1707 addChild_Widget(navi, iClob(makePadding_Widget(0)));
1708 iLabelWidget *back = addChildFlags_Widget(navi,
1709 iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")),
1710 noBackground_WidgetFlag | frameless_WidgetFlag |
1711 alignLeft_WidgetFlag | extraPadding_WidgetFlag);
1712 checkIcon_LabelWidget(back);
1713 setId_Widget(as_Widget(back), "panel.back");
1714 setFont_LabelWidget(back, defaultBig_FontId);
1715 if (!isPrefs) {
1716 /* Pick up the dialog buttons for the navbar. */
1717 iWidget *buttons = findChild_Widget(sheet, "dialogbuttons");
1718 iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel");
1719// if (!cancel) {
1720// cancel = findMenuItem_Widget(buttons, "translation.cancel");
1721// }
1722 if (cancel) {
1723 updateText_LabelWidget(back, text_LabelWidget(cancel));
1724 setCommand_LabelWidget(back, command_LabelWidget(cancel));
1725 }
1726 iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons);
1727 if (def && !cancel) {
1728 updateText_LabelWidget(back, text_LabelWidget(def));
1729 setCommand_LabelWidget(back, command_LabelWidget(def));
1730 setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse);
1731 setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue);
1732 setIcon_LabelWidget(back, 0);
1733 setFont_LabelWidget(back, defaultBigBold_FontId);
1734 }
1735 else if (def != cancel) {
1736 removeChild_Widget(buttons, def);
1737 setFont_LabelWidget(def, defaultBigBold_FontId);
1738 setFlags_Widget(as_Widget(def),
1739 frameless_WidgetFlag | extraPadding_WidgetFlag |
1740 noBackground_WidgetFlag, iTrue);
1741 addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag);
1742 updateSize_LabelWidget(def);
1743 }
1744 /* Action buttons are added in the bottom as extra buttons. */ {
1745 iBool isFirstAction = iTrue;
1746 iForEach(ObjectList, i, children_Widget(buttons)) {
1747 if (isInstance_Object(i.object, &Class_LabelWidget) &&
1748 i.object != cancel && i.object != def) {
1749 iLabelWidget *item = i.object;
1750 setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId);
1751 setFont_LabelWidget(item, defaultBig_FontId);
1752 removeChild_Widget(buttons, item);
1753 addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags |
1754 (isFirstAction ? borderTop_WidgetFlag : 0));
1755 updateSize_LabelWidget(item);
1756 isFirstAction = iFalse;
1757 }
1758 }
1759 }
1760 iRelease(removeChild_Widget(parent_Widget(buttons), buttons));
1761 /* Styling for remaining elements. */
1762 iForEach(ObjectList, i, children_Widget(topPanel)) {
1763 if (isInstance_Object(i.object, &Class_LabelWidget) &&
1764 isEmpty_String(command_LabelWidget(i.object)) &&
1765 isEmpty_String(id_Widget(i.object))) {
1766 setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue);
1767 if (font_LabelWidget(i.object) == uiLabel_FontId) {
1768 setFont_LabelWidget(i.object, uiContent_FontId);
1769 }
1770 }
1771 }
1772 }
1773 addChildFlags_Widget(sheet, iClob(navi),
1774 drawBackgroundToVerticalSafeArea_WidgetFlag |
1775 arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
1776 resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag);
1777 }
1778 updateSheetPanelMetrics_(sheet);
1779 iAssert(sheet->parent);
1780 arrange_Widget(sheet->parent);
1781 postCommand_App("widget.overflow"); /* with the correct dimensions */
1782// printTree_Widget(sheet);
1783 }
1784 else {
1785 arrange_Widget(sheet);
1786 }
1787 postRefresh_App();
1788}
1789
1790void makeFilePath_Widget(iWidget * parent,
1791 const iString *initialPath,
1792 const char * title,
1793 const char * acceptLabel,
1794 const char * command) {
1795 setFocus_Widget(NULL);
1796// processEvents_App(postedEventsOnly_AppEventMode);
1797 iWidget *dlg = makeSheet_Widget(command);
1798 setCommandHandler_Widget(dlg, filePathHandler_);
1799 addChild_Widget(parent, iClob(dlg));
1800 addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag);
1801 iInputWidget *input = addChild_Widget(dlg, iClob(new_InputWidget(0)));
1802 if (initialPath) {
1803 setText_InputWidget(input, collect_String(makeRelative_Path(initialPath)));
1804 }
1805 setId_Widget(as_Widget(input), "input");
1806 as_Widget(input)->rect.size.x = dlg->rect.size.x;
1807 addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI)));
1808 iWidget *div = new_Widget(); {
1809 setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
1810 addChild_Widget(div, iClob(newKeyMods_LabelWidget("${cancel}", SDLK_ESCAPE, 0, "filepath.cancel")));
1811 addChild_Widget(div, iClob(newKeyMods_LabelWidget(acceptLabel, SDLK_RETURN, 0, "filepath.accept")));
1812 }
1813 addChild_Widget(dlg, iClob(div));
1814 finalizeSheet_Widget(dlg);
1815 setFocus_Widget(as_Widget(input));
1816}
1817
1818static void acceptValueInput_(iWidget *dlg) { 1136static void acceptValueInput_(iWidget *dlg) {
1819 const iInputWidget *input = findChild_Widget(dlg, "input"); 1137 const iInputWidget *input = findChild_Widget(dlg, "input");
1820 if (!isEmpty_String(id_Widget(dlg))) { 1138 if (!isEmpty_String(id_Widget(dlg))) {
@@ -1981,7 +1299,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con
1981 iClob(makeDialogButtons_Widget( 1299 iClob(makeDialogButtons_Widget(
1982 (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, 1300 (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } },
1983 2))); 1301 2)));
1984 finalizeSheet_Widget(dlg); 1302 finalizeSheet_Mobile(dlg);
1985 if (parent) { 1303 if (parent) {
1986 setFocus_Widget(as_Widget(input)); 1304 setFocus_Widget(as_Widget(input));
1987 } 1305 }
@@ -2056,7 +1374,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg,
2056 addChild_Widget(dlg->root->widget, iClob(dlg)); 1374 addChild_Widget(dlg->root->widget, iClob(dlg));
2057 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't 1375 arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't
2058 be arranged correctly unless it's here. */ 1376 be arranged correctly unless it's here. */
2059 finalizeSheet_Widget(dlg); 1377 finalizeSheet_Mobile(dlg);
2060 return dlg; 1378 return dlg;
2061} 1379}
2062 1380
@@ -2256,23 +1574,6 @@ static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values,
2256 addDialogInputWithHeading_(headings, values, format_CStr("${%s}", id), id, input); 1574 addDialogInputWithHeading_(headings, values, format_CStr("${%s}", id), id, input);
2257} 1575}
2258 1576
2259void setupSheetTransition_Widget(iWidget *sheet, iBool isIncoming) {
2260 if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
2261 identify_Widget(sheet);
2262 /* View transition. */
2263 if (isIncoming) {
2264 setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue);
2265 setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0);
2266 setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag);
2267 }
2268 else {
2269 const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0;
2270 setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200,
2271 wasDragged ? 0 : easeIn_AnimFlag);
2272 }
2273 }
2274}
2275
2276iWidget *makePreferences_Widget(void) { 1577iWidget *makePreferences_Widget(void) {
2277 iWidget *dlg = makeSheet_Widget("prefs"); 1578 iWidget *dlg = makeSheet_Widget("prefs");
2278 addChildFlags_Widget(dlg, 1579 addChildFlags_Widget(dlg,
@@ -2535,8 +1836,8 @@ iWidget *makePreferences_Widget(void) {
2535 iClob(makeDialogButtons_Widget( 1836 iClob(makeDialogButtons_Widget(
2536 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); 1837 (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1)));
2537 addChild_Widget(dlg->root->widget, iClob(dlg)); 1838 addChild_Widget(dlg->root->widget, iClob(dlg));
2538 finalizeSheet_Widget(dlg); 1839 finalizeSheet_Mobile(dlg);
2539 setupSheetTransition_Widget(dlg, iTrue); 1840 setupSheetTransition_Mobile(dlg, iTrue);
2540// printTree_Widget(dlg); 1841// printTree_Widget(dlg);
2541 return dlg; 1842 return dlg;
2542} 1843}
@@ -2580,7 +1881,7 @@ iWidget *makeBookmarkEditor_Widget(void) {
2580 "bmed.accept" } }, 1881 "bmed.accept" } },
2581 2))); 1882 2)));
2582 addChild_Widget(get_Root()->widget, iClob(dlg)); 1883 addChild_Widget(get_Root()->widget, iClob(dlg));
2583 finalizeSheet_Widget(dlg); 1884 finalizeSheet_Mobile(dlg);
2584 return dlg; 1885 return dlg;
2585} 1886}
2586 1887
@@ -2709,7 +2010,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
2709 arrange_Widget(dlg); 2010 arrange_Widget(dlg);
2710 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; 2011 as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x;
2711 addChild_Widget(get_Root()->widget, iClob(dlg)); 2012 addChild_Widget(get_Root()->widget, iClob(dlg));
2712 finalizeSheet_Widget(dlg); 2013 finalizeSheet_Mobile(dlg);
2713 /* Initialize. */ { 2014 /* Initialize. */ {
2714 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; 2015 const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL;
2715 setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), 2016 setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"),
@@ -2785,7 +2086,7 @@ iWidget *makeIdentityCreation_Widget(void) {
2785 "ident.accept" } }, 2086 "ident.accept" } },
2786 2))); 2087 2)));
2787 addChild_Widget(get_Root()->widget, iClob(dlg)); 2088 addChild_Widget(get_Root()->widget, iClob(dlg));
2788 finalizeSheet_Widget(dlg); 2089 finalizeSheet_Mobile(dlg);
2789 return dlg; 2090 return dlg;
2790} 2091}
2791 2092
@@ -2882,6 +2183,6 @@ iWidget *makeTranslation_Widget(iWidget *parent) {
2882 2))); 2183 2)));
2883 addChild_Widget(parent, iClob(dlg)); 2184 addChild_Widget(parent, iClob(dlg));
2884 arrange_Widget(dlg); 2185 arrange_Widget(dlg);
2885 finalizeSheet_Widget(dlg); 2186 finalizeSheet_Mobile(dlg);
2886 return dlg; 2187 return dlg;
2887} 2188}