diff options
Diffstat (limited to 'src/ui/mobile.c')
-rw-r--r-- | src/ui/mobile.c | 547 |
1 files changed, 516 insertions, 31 deletions
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 0ff3fe85..3cb6e631 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | # include "ios.h" | 36 | # include "ios.h" |
37 | #endif | 37 | #endif |
38 | 38 | ||
39 | static iBool useMobileSheetLayout_(void) { | 39 | iBool isUsingPanelLayout_Mobile(void) { |
40 | return deviceType_App() != desktop_AppDeviceType; | 40 | return deviceType_App() != desktop_AppDeviceType; |
41 | } | 41 | } |
42 | 42 | ||
@@ -57,11 +57,12 @@ static enum iFontId labelBoldFont_(void) { | |||
57 | 57 | ||
58 | static void updatePanelSheetMetrics_(iWidget *sheet) { | 58 | static void updatePanelSheetMetrics_(iWidget *sheet) { |
59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | 59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); |
60 | iWidget *naviPad = child_Widget(navi, 0); | ||
61 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; | 60 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; |
61 | #if defined (iPlatformMobile) | ||
62 | float left = 0.0f, right = 0.0f, top = 0.0f, bottom = 0.0f; | ||
62 | #if defined (iPlatformAppleMobile) | 63 | #if defined (iPlatformAppleMobile) |
63 | float left, right, top, bottom; | ||
64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
65 | #endif | ||
65 | setPadding_Widget(sheet, left, 0, right, 0); | 66 | setPadding_Widget(sheet, left, 0, right, 0); |
66 | navi->rect.pos = init_I2(left, top); | 67 | navi->rect.pos = init_I2(left, top); |
67 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | 68 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { |
@@ -87,17 +88,28 @@ static void unselectAllPanelButtons_(iWidget *topPanel) { | |||
87 | } | 88 | } |
88 | } | 89 | } |
89 | 90 | ||
91 | static iWidget *findTitleLabel_(iWidget *panel) { | ||
92 | iForEach(ObjectList, i, children_Widget(panel)) { | ||
93 | iWidget *child = i.object; | ||
94 | if (flags_Widget(child) & collapse_WidgetFlag && | ||
95 | isInstance_Object(child, &Class_LabelWidget)) { | ||
96 | return child; | ||
97 | } | ||
98 | } | ||
99 | return NULL; | ||
100 | } | ||
101 | |||
90 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { | 102 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { |
91 | if (equal_Command(cmd, "window.resized")) { | 103 | if (equal_Command(cmd, "window.resized")) { |
92 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 104 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
93 | const iRect safeRoot = safeRect_Root(mainDetailSplit->root); | 105 | const iRect safeRoot = safeRect_Root(mainDetailSplit->root); |
94 | setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); | ||
95 | setFixedSize_Widget(mainDetailSplit, safeRoot.size); | ||
96 | iWidget * sheet = parent_Widget(mainDetailSplit); | 106 | iWidget * sheet = parent_Widget(mainDetailSplit); |
97 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); | 107 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); |
98 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); | 108 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); |
99 | const size_t numPanels = childCount_Widget(detailStack); | 109 | const size_t numPanels = childCount_Widget(detailStack); |
100 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; | 110 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; |
111 | setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); | ||
112 | setFixedSize_Widget(mainDetailSplit, safeRoot.size); | ||
101 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); | 113 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); |
102 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); | 114 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); |
103 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); | 115 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); |
@@ -107,7 +119,7 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
107 | iAssert(topPanel); | 119 | iAssert(topPanel); |
108 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? | 120 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? |
109 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); | 121 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); |
110 | } | 122 | } |
111 | if (deviceType_App() == tablet_AppDeviceType) { | 123 | if (deviceType_App() == tablet_AppDeviceType) { |
112 | setPadding_Widget(topPanel, pad, 0, pad, pad); | 124 | setPadding_Widget(topPanel, pad, 0, pad, pad); |
113 | if (numPanels == 0) { | 125 | if (numPanels == 0) { |
@@ -118,8 +130,15 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
118 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); | 130 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); |
119 | } | 131 | } |
120 | } | 132 | } |
133 | iWidget *detailTitle = findChild_Widget(navi, "detailtitle"); { | ||
134 | setPos_Widget(detailTitle, init_I2(width_Widget(topPanel), 0)); | ||
135 | setFixedSize_Widget(detailTitle, | ||
136 | init_I2(width_Widget(detailStack), height_Widget(navi))); | ||
137 | setFlags_Widget(detailTitle, hidden_WidgetFlag, !isSideBySide); | ||
138 | } | ||
121 | iForEach(ObjectList, i, children_Widget(detailStack)) { | 139 | iForEach(ObjectList, i, children_Widget(detailStack)) { |
122 | iWidget *panel = i.object; | 140 | iWidget *panel = i.object; |
141 | setFlags_Widget(findTitleLabel_(panel), hidden_WidgetFlag, isSideBySide); | ||
123 | setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide); | 142 | setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide); |
124 | if (isSideBySide) { | 143 | if (isSideBySide) { |
125 | setVisualOffset_Widget(panel, 0, 0, 0); | 144 | setVisualOffset_Widget(panel, 0, 0, 0); |
@@ -128,9 +147,27 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
128 | } | 147 | } |
129 | arrange_Widget(mainDetailSplit); | 148 | arrange_Widget(mainDetailSplit); |
130 | } | 149 | } |
150 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | ||
151 | if (focus_Widget() && class_Widget(focus_Widget()) == &Class_InputWidget) { | ||
152 | setFocus_Widget(NULL); | ||
153 | return iTrue; | ||
154 | } | ||
155 | } | ||
131 | return iFalse; | 156 | return iFalse; |
132 | } | 157 | } |
133 | 158 | ||
159 | size_t currentPanelIndex_Mobile(const iWidget *panels) { | ||
160 | size_t index = 0; | ||
161 | iConstForEach(ObjectList, i, children_Widget(findChild_Widget(panels, "detailstack"))) { | ||
162 | const iWidget *child = i.object; | ||
163 | if (isVisible_Widget(child)) { | ||
164 | return index; | ||
165 | } | ||
166 | index++; | ||
167 | } | ||
168 | return iInvalidPos; | ||
169 | } | ||
170 | |||
134 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | 171 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { |
135 | const iBool isPortrait = !isSideBySideLayout_(); | 172 | const iBool isPortrait = !isSideBySideLayout_(); |
136 | if (equal_Command(cmd, "panel.open")) { | 173 | if (equal_Command(cmd, "panel.open")) { |
@@ -147,6 +184,12 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
147 | setupSheetTransition_Mobile(panel, iTrue); | 184 | setupSheetTransition_Mobile(panel, iTrue); |
148 | } | 185 | } |
149 | } | 186 | } |
187 | iLabelWidget *detailTitle = | ||
188 | findChild_Widget(parent_Widget(parent_Widget(topPanel)), "detailtitle"); | ||
189 | // setFlags_Widget(as_Widget(detailTitle), hidden_WidgetFlag, !isSideBySideLayout_()); | ||
190 | setFont_LabelWidget(detailTitle, uiLabelLargeBold_FontId); | ||
191 | setTextColor_LabelWidget(detailTitle, uiHeading_ColorId); | ||
192 | setText_LabelWidget(detailTitle, text_LabelWidget((iLabelWidget *) findTitleLabel_(panel))); | ||
150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | 193 | setFlags_Widget(button, selected_WidgetFlag, iTrue); |
151 | return iTrue; | 194 | return iTrue; |
152 | } | 195 | } |
@@ -171,7 +214,22 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
171 | } | 214 | } |
172 | unselectAllPanelButtons_(topPanel); | 215 | unselectAllPanelButtons_(topPanel); |
173 | if (!wasClosed) { | 216 | if (!wasClosed) { |
174 | postCommand_App("prefs.dismiss"); | 217 | /* TODO: Should come up with a more general-purpose approach here. */ |
218 | if (findWidget_App("prefs")) { | ||
219 | postCommand_App("prefs.dismiss"); | ||
220 | } | ||
221 | else if (findWidget_App("upload")) { | ||
222 | postCommand_App("upload.cancel"); | ||
223 | } | ||
224 | else if (findWidget_App("ident")) { | ||
225 | postCommand_Widget(topPanel, "ident.cancel"); | ||
226 | } | ||
227 | else if (findWidget_App("xlt")) { | ||
228 | postCommand_Widget(topPanel, "translation.cancel"); | ||
229 | } | ||
230 | else { | ||
231 | postCommand_Widget(topPanel, "cancel"); | ||
232 | } | ||
175 | } | 233 | } |
176 | return iTrue; | 234 | return iTrue; |
177 | } | 235 | } |
@@ -186,6 +244,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
186 | return iFalse; | 244 | return iFalse; |
187 | } | 245 | } |
188 | 246 | ||
247 | #if 0 | ||
189 | static iBool isTwoColumnPage_(iWidget *d) { | 248 | static iBool isTwoColumnPage_(iWidget *d) { |
190 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | 249 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || |
191 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | 250 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { |
@@ -273,12 +332,13 @@ static void stripTrailingColon_(iLabelWidget *label) { | |||
273 | delete_String(mod); | 332 | delete_String(mod); |
274 | } | 333 | } |
275 | } | 334 | } |
335 | #endif | ||
276 | 336 | ||
277 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | 337 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { |
278 | iLabelWidget *btn = new_LabelWidget(text, command); | 338 | iLabelWidget *btn = new_LabelWidget(text, command); |
279 | setFlags_Widget(as_Widget(btn), | 339 | setFlags_Widget(as_Widget(btn), |
280 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | 340 | borderTop_WidgetFlag | borderBottom_WidgetFlag | alignLeft_WidgetFlag | |
281 | frameless_WidgetFlag | extraPadding_WidgetFlag, | 341 | frameless_WidgetFlag | extraPadding_WidgetFlag, |
282 | iTrue); | 342 | iTrue); |
283 | checkIcon_LabelWidget(btn); | 343 | checkIcon_LabelWidget(btn); |
284 | setFont_LabelWidget(btn, labelFont_()); | 344 | setFont_LabelWidget(btn, labelFont_()); |
@@ -298,11 +358,9 @@ static iWidget *makeValuePadding_(iWidget *value) { | |||
298 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | 358 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); |
299 | addChild_Widget(pad, iClob(value)); | 359 | addChild_Widget(pad, iClob(value)); |
300 | setFlags_Widget(pad, | 360 | setFlags_Widget(pad, |
301 | borderBottom_WidgetFlag | | 361 | borderTop_WidgetFlag | borderBottom_WidgetFlag | arrangeVertical_WidgetFlag | |
302 | arrangeVertical_WidgetFlag | | 362 | resizeToParentWidth_WidgetFlag | resizeWidthOfChildren_WidgetFlag | |
303 | resizeToParentWidth_WidgetFlag | | 363 | arrangeHeight_WidgetFlag, |
304 | resizeWidthOfChildren_WidgetFlag | | ||
305 | arrangeHeight_WidgetFlag, | ||
306 | iTrue); | 364 | iTrue); |
307 | return pad; | 365 | return pad; |
308 | } | 366 | } |
@@ -311,7 +369,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
311 | const iBool isInput = isInstance_Object(value, &Class_InputWidget); | 369 | const iBool isInput = isInstance_Object(value, &Class_InputWidget); |
312 | iWidget *div = new_Widget(); | 370 | iWidget *div = new_Widget(); |
313 | setFlags_Widget(div, | 371 | setFlags_Widget(div, |
314 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | 372 | borderTop_WidgetFlag | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | |
315 | resizeWidthOfChildren_WidgetFlag | | 373 | resizeWidthOfChildren_WidgetFlag | |
316 | arrangeHorizontal_WidgetFlag, iTrue); | 374 | arrangeHorizontal_WidgetFlag, iTrue); |
317 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | 375 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); |
@@ -321,7 +379,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
321 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | 379 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); |
322 | setFont_LabelWidget(heading, labelFont_()); | 380 | setFont_LabelWidget(heading, labelFont_()); |
323 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | 381 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); |
324 | if (isInput) { | 382 | if (isInput && ~value->flags & fixedWidth_WidgetFlag) { |
325 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | 383 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); |
326 | } | 384 | } |
327 | else if (isInstance_Object(value, &Class_LabelWidget) && | 385 | else if (isInstance_Object(value, &Class_LabelWidget) && |
@@ -337,7 +395,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
337 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | 395 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); |
338 | addChild_Widget(div, iClob(value)); | 396 | addChild_Widget(div, iClob(value)); |
339 | } | 397 | } |
340 | printTree_Widget(div); | 398 | // printTree_Widget(div); |
341 | return div; | 399 | return div; |
342 | } | 400 | } |
343 | 401 | ||
@@ -347,6 +405,7 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | |||
347 | setId_Widget(panel, "panel"); | 405 | setId_Widget(panel, "panel"); |
348 | setUserData_Object(panelButton, panel); | 406 | setUserData_Object(panelButton, panel); |
349 | setBackgroundColor_Widget(panel, uiBackground_ColorId); | 407 | setBackgroundColor_Widget(panel, uiBackground_ColorId); |
408 | setDrawBufferEnabled_Widget(panel, iTrue); | ||
350 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); | 409 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); |
351 | if (titleText) { | 410 | if (titleText) { |
352 | iLabelWidget *title = | 411 | iLabelWidget *title = |
@@ -366,7 +425,396 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | |||
366 | return panel; | 425 | return panel; |
367 | } | 426 | } |
368 | 427 | ||
369 | void finalizeSheet_Mobile(iWidget *sheet) { | 428 | //void finalizeSheet_Mobile(iWidget *sheet) { |
429 | // arrange_Widget(sheet); | ||
430 | // postRefresh_App(); | ||
431 | //} | ||
432 | |||
433 | static size_t countItems_(const iMenuItem *itemsNullTerminated) { | ||
434 | size_t num = 0; | ||
435 | for (; itemsNullTerminated->label; num++, itemsNullTerminated++) {} | ||
436 | return num; | ||
437 | } | ||
438 | |||
439 | static iBool dropdownHeadingHandler_(iWidget *d, const char *cmd) { | ||
440 | if (isVisible_Widget(d) && | ||
441 | equal_Command(cmd, "mouse.clicked") && contains_Widget(d, coord_Command(cmd)) && | ||
442 | arg_Command(cmd)) { | ||
443 | postCommand_Widget(userData_Object(d), | ||
444 | cstr_String(command_LabelWidget(userData_Object(d)))); | ||
445 | return iTrue; | ||
446 | } | ||
447 | return iFalse; | ||
448 | } | ||
449 | |||
450 | static iBool inputHeadingHandler_(iWidget *d, const char *cmd) { | ||
451 | if (isVisible_Widget(d) && | ||
452 | equal_Command(cmd, "mouse.clicked") && contains_Widget(d, coord_Command(cmd)) && | ||
453 | arg_Command(cmd)) { | ||
454 | setFocus_Widget(userData_Object(d)); | ||
455 | return iTrue; | ||
456 | } | ||
457 | return iFalse; | ||
458 | } | ||
459 | |||
460 | void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | ||
461 | iWidget * widget = NULL; | ||
462 | iLabelWidget *heading = NULL; | ||
463 | iWidget * value = NULL; | ||
464 | const char * spec = item->label; | ||
465 | const char * id = cstr_Rangecc(range_Command(spec, "id")); | ||
466 | const char * label = hasLabel_Command(spec, "text") | ||
467 | ? suffixPtr_Command(spec, "text") | ||
468 | : format_CStr("${%s}", id); | ||
469 | if (hasLabel_Command(spec, "device") && deviceType_App() != argLabel_Command(spec, "device")) { | ||
470 | return; | ||
471 | } | ||
472 | if (equal_Command(spec, "title")) { | ||
473 | iLabelWidget *title = addChildFlags_Widget(panel, | ||
474 | iClob(new_LabelWidget(label, NULL)), | ||
475 | alignLeft_WidgetFlag | frameless_WidgetFlag | | ||
476 | collapse_WidgetFlag); | ||
477 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
478 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
479 | setAllCaps_LabelWidget(title, iTrue); | ||
480 | setId_Widget(as_Widget(title), id); | ||
481 | } | ||
482 | else if (equal_Command(spec, "heading")) { | ||
483 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
484 | heading = makeHeading_Widget(label); | ||
485 | setAllCaps_LabelWidget(heading, iTrue); | ||
486 | setRemoveTrailingColon_LabelWidget(heading, iTrue); | ||
487 | addChild_Widget(panel, iClob(heading)); | ||
488 | setId_Widget(as_Widget(heading), id); | ||
489 | } | ||
490 | else if (equal_Command(spec, "toggle")) { | ||
491 | iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id); | ||
492 | setFont_LabelWidget(toggle, labelFont_()); | ||
493 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), | ||
494 | as_Widget(toggle)); | ||
495 | } | ||
496 | else if (equal_Command(spec, "dropdown")) { | ||
497 | const iMenuItem *dropItems = item->data; | ||
498 | iLabelWidget *drop = makeMenuButton_LabelWidget(dropItems[0].label, | ||
499 | dropItems, countItems_(dropItems)); | ||
500 | value = as_Widget(drop); | ||
501 | setFont_LabelWidget(drop, labelFont_()); | ||
502 | setFlags_Widget(as_Widget(drop), | ||
503 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
504 | frameless_WidgetFlag, iTrue); | ||
505 | setId_Widget(as_Widget(drop), id); | ||
506 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop)); | ||
507 | setCommandHandler_Widget(widget, dropdownHeadingHandler_); | ||
508 | setUserData_Object(widget, drop); | ||
509 | } | ||
510 | else if (equal_Command(spec, "radio") || equal_Command(spec, "buttons")) { | ||
511 | const iBool isRadio = equal_Command(spec, "radio"); | ||
512 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
513 | iLabelWidget *head = makeHeading_Widget(label); | ||
514 | setAllCaps_LabelWidget(head, iTrue); | ||
515 | setRemoveTrailingColon_LabelWidget(head, iTrue); | ||
516 | addChild_Widget(panel, iClob(head)); | ||
517 | widget = new_Widget(); | ||
518 | setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId); | ||
519 | setPadding_Widget(widget, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
520 | setFlags_Widget(widget, | ||
521 | borderTop_WidgetFlag | | ||
522 | borderBottom_WidgetFlag | | ||
523 | arrangeHorizontal_WidgetFlag | | ||
524 | arrangeHeight_WidgetFlag | | ||
525 | resizeToParentWidth_WidgetFlag | | ||
526 | resizeWidthOfChildren_WidgetFlag, | ||
527 | iTrue); | ||
528 | setId_Widget(widget, id); | ||
529 | for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) { | ||
530 | const char * radId = cstr_Rangecc(range_Command(radioItem->label, "id")); | ||
531 | int64_t flags = noBackground_WidgetFlag; | ||
532 | iLabelWidget *button; | ||
533 | if (isRadio) { | ||
534 | const char *radLabel = | ||
535 | hasLabel_Command(radioItem->label, "label") | ||
536 | ? format_CStr("${%s}", | ||
537 | cstr_Rangecc(range_Command(radioItem->label, "label"))) | ||
538 | : suffixPtr_Command(radioItem->label, "text"); | ||
539 | button = new_LabelWidget(radLabel, radioItem->command); | ||
540 | flags |= radio_WidgetFlag; | ||
541 | } | ||
542 | else { | ||
543 | button = (iLabelWidget *) makeToggle_Widget(radId); | ||
544 | setTextCStr_LabelWidget(button, format_CStr("${%s}", radId)); | ||
545 | setFlags_Widget(as_Widget(button), fixedWidth_WidgetFlag, iFalse); | ||
546 | updateSize_LabelWidget(button); | ||
547 | } | ||
548 | setId_Widget(as_Widget(button), radId); | ||
549 | setFont_LabelWidget(button, defaultMedium_FontId); | ||
550 | addChildFlags_Widget(widget, iClob(button), flags); | ||
551 | } | ||
552 | } | ||
553 | else if (equal_Command(spec, "input")) { | ||
554 | iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); | ||
555 | if (hasLabel_Command(spec, "hint")) { | ||
556 | setHint_InputWidget(input, cstr_Lang(cstr_Rangecc(range_Command(spec, "hint")))); | ||
557 | } | ||
558 | setId_Widget(as_Widget(input), id); | ||
559 | setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); | ||
560 | setSelectAllOnFocus_InputWidget(input, argLabel_Command(spec, "selectall")); | ||
561 | setFont_InputWidget(input, labelFont_()); | ||
562 | if (argLabel_Command(spec, "noheading")) { | ||
563 | widget = makeValuePadding_(as_Widget(input)); | ||
564 | setFlags_Widget(widget, expand_WidgetFlag, iTrue); | ||
565 | } | ||
566 | else { | ||
567 | setContentPadding_InputWidget(input, 3 * gap_UI, 0); | ||
568 | if (hasLabel_Command(spec, "unit")) { | ||
569 | iWidget *unit = addChildFlags_Widget( | ||
570 | as_Widget(input), | ||
571 | iClob(new_LabelWidget( | ||
572 | format_CStr("${%s}", cstr_Rangecc(range_Command(spec, "unit"))), NULL)), | ||
573 | frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag | | ||
574 | resizeToParentHeight_WidgetFlag); | ||
575 | setContentPadding_InputWidget(input, -1, width_Widget(unit) - 4 * gap_UI); | ||
576 | } | ||
577 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), | ||
578 | as_Widget(input)); | ||
579 | setCommandHandler_Widget(widget, inputHeadingHandler_); | ||
580 | setUserData_Object(widget, input); | ||
581 | } | ||
582 | } | ||
583 | else if (equal_Command(spec, "button")) { | ||
584 | widget = as_Widget(heading = makePanelButton_(label, item->command)); | ||
585 | setFlags_Widget(widget, selected_WidgetFlag, argLabel_Command(spec, "selected") != 0); | ||
586 | } | ||
587 | else if (equal_Command(spec, "label")) { | ||
588 | iLabelWidget *lab = new_LabelWidget(label, NULL); | ||
589 | widget = as_Widget(lab); | ||
590 | setId_Widget(widget, id); | ||
591 | setWrap_LabelWidget(lab, !argLabel_Command(spec, "nowrap")); | ||
592 | setFlags_Widget(widget, | ||
593 | fixedHeight_WidgetFlag | | ||
594 | (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), | ||
595 | iTrue); | ||
596 | } | ||
597 | else if (equal_Command(spec, "padding")) { | ||
598 | float height = 1.5f; | ||
599 | if (hasLabel_Command(spec, "arg")) { | ||
600 | height *= argfLabel_Command(spec, "arg"); | ||
601 | } | ||
602 | widget = makePadding_Widget(lineHeight_Text(labelFont_()) * height); | ||
603 | } | ||
604 | /* Apply common styling to the heading. */ | ||
605 | if (heading) { | ||
606 | setRemoveTrailingColon_LabelWidget(heading, iTrue); | ||
607 | const iChar icon = toInt_String(string_Command(item->label, "icon")); | ||
608 | if (icon) { | ||
609 | setIcon_LabelWidget(heading, icon); | ||
610 | } | ||
611 | if (value && as_Widget(heading) != value) { | ||
612 | as_Widget(heading)->sizeRef = value; /* heading height matches value widget */ | ||
613 | } | ||
614 | } | ||
615 | if (widget) { | ||
616 | setFlags_Widget(widget, | ||
617 | collapse_WidgetFlag | hidden_WidgetFlag, | ||
618 | argLabel_Command(spec, "collapse") != 0); | ||
619 | addChild_Widget(panel, iClob(widget)); | ||
620 | } | ||
621 | } | ||
622 | |||
623 | void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated) { | ||
624 | for (const iMenuItem *item = itemsNullTerminated; item->label; item++) { | ||
625 | makePanelItem_Mobile(panel, item); | ||
626 | } | ||
627 | } | ||
628 | |||
629 | static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n) { | ||
630 | if (n <= 1) { | ||
631 | return NULL; | ||
632 | } | ||
633 | for (size_t i = 0; i < n; i++) { | ||
634 | if (!iCmpStr(items[i].label, "${cancel}") || !iCmpStr(items[i].label, "${close}")) { | ||
635 | return &items[i]; | ||
636 | } | ||
637 | } | ||
638 | return NULL; | ||
639 | } | ||
640 | |||
641 | iWidget *makePanels_Mobile(const char *id, | ||
642 | const iMenuItem *itemsNullTerminated, | ||
643 | const iMenuItem *actions, size_t numActions) { | ||
644 | return makePanelsParent_Mobile(get_Root()->widget, id, itemsNullTerminated, actions, numActions); | ||
645 | } | ||
646 | |||
647 | iWidget *makePanelsParent_Mobile(iWidget *parentWidget, | ||
648 | const char *id, | ||
649 | const iMenuItem *itemsNullTerminated, | ||
650 | const iMenuItem *actions, size_t numActions) { | ||
651 | iWidget *panels = new_Widget(); | ||
652 | setId_Widget(panels, id); | ||
653 | initPanels_Mobile(panels, parentWidget, itemsNullTerminated, actions, numActions); | ||
654 | return panels; | ||
655 | } | ||
656 | |||
657 | void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, | ||
658 | const iMenuItem *itemsNullTerminated, | ||
659 | const iMenuItem *actions, size_t numActions) { | ||
660 | /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, | ||
661 | the detail panels slide in from the right and cover the top panel. In a landscape layout, | ||
662 | the detail panels are always visible on the side. */ | ||
663 | setBackgroundColor_Widget(panels, uiBackground_ColorId); | ||
664 | setFlags_Widget(panels, | ||
665 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | | ||
666 | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | | ||
667 | /*overflowScrollable_WidgetFlag |*/ leftEdgeDraggable_WidgetFlag, | ||
668 | iTrue); | ||
669 | setFlags_Widget(panels, overflowScrollable_WidgetFlag, iFalse); | ||
670 | /* The top-level split between main and detail panels. */ | ||
671 | iWidget *mainDetailSplit = makeHDiv_Widget(); { | ||
672 | setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); | ||
673 | setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
674 | setId_Widget(mainDetailSplit, "mdsplit"); | ||
675 | addChild_Widget(panels, iClob(mainDetailSplit)); | ||
676 | } | ||
677 | /* The panel roots. */ | ||
678 | iWidget *topPanel = new_Widget(); { | ||
679 | setId_Widget(topPanel, "panel.top"); | ||
680 | setDrawBufferEnabled_Widget(topPanel, iTrue); | ||
681 | setCommandHandler_Widget(topPanel, topPanelHandler_); | ||
682 | setFlags_Widget(topPanel, | ||
683 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
684 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
685 | commandOnClick_WidgetFlag, | ||
686 | iTrue); | ||
687 | addChild_Widget(mainDetailSplit, iClob(topPanel)); | ||
688 | setId_Widget(addChild_Widget(topPanel, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
689 | } | ||
690 | iWidget *detailStack = new_Widget(); { | ||
691 | setId_Widget(detailStack, "detailstack"); | ||
692 | setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | ||
693 | addChild_Widget(mainDetailSplit, iClob(detailStack)); | ||
694 | } | ||
695 | /* Slide top panel with detail panels. */ { | ||
696 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); | ||
697 | topPanel->offsetRef = detailStack; | ||
698 | } | ||
699 | /* Navigation bar at the top. */ | ||
700 | iLabelWidget *naviBack; | ||
701 | iWidget *navi = new_Widget(); { | ||
702 | setId_Widget(navi, "panel.navi"); | ||
703 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
704 | setId_Widget(addChildFlags_Widget(navi, | ||
705 | iClob(new_LabelWidget("", NULL)), | ||
706 | alignLeft_WidgetFlag | fixedPosition_WidgetFlag | | ||
707 | fixedSize_WidgetFlag | hidden_WidgetFlag | | ||
708 | frameless_WidgetFlag), | ||
709 | "detailtitle"); | ||
710 | naviBack = addChildFlags_Widget( | ||
711 | navi, | ||
712 | iClob(newKeyMods_LabelWidget( | ||
713 | leftAngle_Icon " ${panel.back}", SDLK_ESCAPE, 0, "panel.close")), | ||
714 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | ||
715 | extraPadding_WidgetFlag); | ||
716 | checkIcon_LabelWidget(naviBack); | ||
717 | setId_Widget(as_Widget(naviBack), "panel.back"); | ||
718 | setFont_LabelWidget(naviBack, labelFont_()); | ||
719 | addChildFlags_Widget(panels, iClob(navi), | ||
720 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
721 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
722 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
723 | } | ||
724 | iBool haveDetailPanels = iFalse; | ||
725 | /* Create panel contents based on provided items. */ | ||
726 | for (size_t i = 0; itemsNullTerminated[i].label; i++) { | ||
727 | const iMenuItem *item = &itemsNullTerminated[i]; | ||
728 | if (equal_Command(item->label, "panel")) { | ||
729 | haveDetailPanels = iTrue; | ||
730 | const char *id = cstr_Rangecc(range_Command(item->label, "id")); | ||
731 | const iString *label = hasLabel_Command(item->label, "text") | ||
732 | ? collect_String(suffix_Command(item->label, "text")) | ||
733 | : collectNewFormat_String("${%s}", id); | ||
734 | iLabelWidget * button = | ||
735 | addChildFlags_Widget(topPanel, | ||
736 | iClob(makePanelButton_(cstr_String(label), "panel.open")), | ||
737 | chevron_WidgetFlag | borderTop_WidgetFlag); | ||
738 | const iChar icon = toInt_String(string_Command(item->label, "icon")); | ||
739 | if (icon) { | ||
740 | setIcon_LabelWidget(button, icon); | ||
741 | } | ||
742 | iWidget *panel = addChildPanel_(detailStack, button, NULL); | ||
743 | makePanelItems_Mobile(panel, item->data); | ||
744 | } | ||
745 | else { | ||
746 | makePanelItem_Mobile(topPanel, item); | ||
747 | } | ||
748 | } | ||
749 | /* Actions. */ | ||
750 | if (numActions) { | ||
751 | /* Some actions go in the navigation bar and some go on the top panel. */ | ||
752 | const iMenuItem *cancelItem = findDialogCancelAction_(actions, numActions); | ||
753 | const iMenuItem *defaultItem = &actions[numActions - 1]; | ||
754 | iAssert(defaultItem); | ||
755 | if (defaultItem && !cancelItem) { | ||
756 | updateTextCStr_LabelWidget(naviBack, defaultItem->label); | ||
757 | setCommand_LabelWidget(naviBack, collectNewCStr_String(defaultItem->command)); | ||
758 | setFlags_Widget(as_Widget(naviBack), alignLeft_WidgetFlag, iFalse); | ||
759 | setFlags_Widget(as_Widget(naviBack), alignRight_WidgetFlag, iTrue); | ||
760 | setIcon_LabelWidget(naviBack, 0); | ||
761 | setFont_LabelWidget(naviBack, labelBoldFont_()); | ||
762 | } | ||
763 | else if (defaultItem && defaultItem != cancelItem) { | ||
764 | if (!haveDetailPanels) { | ||
765 | updateTextCStr_LabelWidget(naviBack, cancelItem->label); | ||
766 | setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command | ||
767 | ? cancelItem->command | ||
768 | : "cancel")); | ||
769 | } | ||
770 | iLabelWidget *defaultButton = new_LabelWidget(defaultItem->label, defaultItem->command); | ||
771 | setFont_LabelWidget(defaultButton, labelBoldFont_()); | ||
772 | setFlags_Widget(as_Widget(defaultButton), | ||
773 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
774 | noBackground_WidgetFlag, | ||
775 | iTrue); | ||
776 | addChildFlags_Widget(as_Widget(naviBack), iClob(defaultButton), | ||
777 | moveToParentRightEdge_WidgetFlag); | ||
778 | updateSize_LabelWidget(defaultButton); | ||
779 | } | ||
780 | /* All other actions are added as buttons. */ | ||
781 | iBool needPadding = iTrue; | ||
782 | for (size_t i = 0; i < numActions; i++) { | ||
783 | const iMenuItem *act = &actions[i]; | ||
784 | if (act == cancelItem || act == defaultItem) { | ||
785 | continue; | ||
786 | } | ||
787 | const char *label = act->label; | ||
788 | if (*label == '*' || *label == '&') { | ||
789 | continue; /* Special value selection items for a Question dialog. */ | ||
790 | } | ||
791 | if (!iCmpStr(label, "---")) { | ||
792 | continue; /* Separator. */ | ||
793 | } | ||
794 | if (needPadding) { | ||
795 | makePanelItem_Mobile(topPanel, &(iMenuItem){ "padding" }); | ||
796 | needPadding = iFalse; | ||
797 | } | ||
798 | makePanelItem_Mobile( | ||
799 | topPanel, | ||
800 | &(iMenuItem){ format_CStr("button text:" uiTextAction_ColorEscape "%s", act->label), | ||
801 | 0, | ||
802 | 0, | ||
803 | act->command }); | ||
804 | } | ||
805 | } | ||
806 | /* Finalize the layout. */ | ||
807 | if (parentWidget) { | ||
808 | addChild_Widget(parentWidget, iClob(panels)); | ||
809 | } | ||
810 | mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ | ||
811 | updatePanelSheetMetrics_(panels); | ||
812 | arrange_Widget(panels); | ||
813 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
814 | // printTree_Widget(panels); | ||
815 | } | ||
816 | |||
817 | #if 0 | ||
370 | /* The sheet contents are completely rearranged and restyled on a phone. | 818 | /* The sheet contents are completely rearranged and restyled on a phone. |
371 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | 819 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already |
372 | scrollable so they can be taller than the display. In hindsight, it may have been | 820 | scrollable so they can be taller than the display. In hindsight, it may have been |
@@ -397,7 +845,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
397 | │ │ └┤ ││ │ │└┤ ││ | 845 | │ │ └┤ ││ │ │└┤ ││ |
398 | │ │ └───────────────────┘│ │ │ └──────┘ | 846 | │ │ └───────────────────┘│ │ │ └──────┘ |
399 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ | 847 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ |
400 | offscreen | 848 | underneath |
401 | */ | 849 | */ |
402 | /* Modify the top sheet to act as a fullscreen background. */ | 850 | /* Modify the top sheet to act as a fullscreen background. */ |
403 | setPadding1_Widget(sheet, 0); | 851 | setPadding1_Widget(sheet, 0); |
@@ -772,9 +1220,10 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
772 | } | 1220 | } |
773 | postRefresh_App(); | 1221 | postRefresh_App(); |
774 | } | 1222 | } |
1223 | #endif | ||
775 | 1224 | ||
776 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | 1225 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { |
777 | if (!useMobileSheetLayout_()) { | 1226 | if (!isUsingPanelLayout_Mobile()) { |
778 | return; | 1227 | return; |
779 | } | 1228 | } |
780 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; | 1229 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; |
@@ -794,8 +1243,10 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
794 | } | 1243 | } |
795 | } | 1244 | } |
796 | 1245 | ||
797 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | 1246 | void setupSheetTransition_Mobile(iWidget *sheet, int flags) { |
798 | if (!useMobileSheetLayout_()) { | 1247 | const iBool isIncoming = (flags & incoming_TransitionFlag) != 0; |
1248 | const int dir = flags & dirMask_TransitionFlag; | ||
1249 | if (!isUsingPanelLayout_Mobile()) { | ||
799 | if (prefs_App()->uiAnimations) { | 1250 | if (prefs_App()->uiAnimations) { |
800 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); | 1251 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); |
801 | if (isIncoming) { | 1252 | if (isIncoming) { |
@@ -808,17 +1259,51 @@ void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
808 | } | 1259 | } |
809 | return; | 1260 | return; |
810 | } | 1261 | } |
811 | if(isSideBySideLayout_()) { | 1262 | if (isSideBySideLayout_()) { |
1263 | /* TODO: Landscape transitions? */ | ||
812 | return; | 1264 | return; |
813 | } | 1265 | } |
814 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | 1266 | setFlags_Widget(sheet, |
1267 | horizontalOffset_WidgetFlag, | ||
1268 | dir == right_TransitionDir || dir == left_TransitionDir); | ||
815 | if (isIncoming) { | 1269 | if (isIncoming) { |
816 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | 1270 | switch (dir) { |
1271 | case right_TransitionDir: | ||
1272 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | ||
1273 | break; | ||
1274 | case left_TransitionDir: | ||
1275 | setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 0, 0); | ||
1276 | break; | ||
1277 | case top_TransitionDir: | ||
1278 | setVisualOffset_Widget( | ||
1279 | sheet, -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), 0, 0); | ||
1280 | break; | ||
1281 | case bottom_TransitionDir: | ||
1282 | setVisualOffset_Widget(sheet, height_Widget(sheet), 0, 0); | ||
1283 | break; | ||
1284 | } | ||
817 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); | 1285 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); |
818 | } | 1286 | } |
819 | else { | 1287 | else { |
820 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; | 1288 | switch (dir) { |
821 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | 1289 | case right_TransitionDir: { |
822 | wasDragged ? 0 : easeIn_AnimFlag); | 1290 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; |
1291 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | ||
1292 | wasDragged ? 0 : easeIn_AnimFlag); | ||
1293 | break; | ||
1294 | } | ||
1295 | case left_TransitionDir: | ||
1296 | setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 200, easeIn_AnimFlag); | ||
1297 | break; | ||
1298 | case top_TransitionDir: | ||
1299 | setVisualOffset_Widget(sheet, | ||
1300 | -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), | ||
1301 | 200, | ||
1302 | easeIn_AnimFlag); | ||
1303 | break; | ||
1304 | case bottom_TransitionDir: | ||
1305 | setVisualOffset_Widget(sheet, height_Widget(sheet), 200, easeIn_AnimFlag); | ||
1306 | break; | ||
1307 | } | ||
823 | } | 1308 | } |
824 | } | 1309 | } |