summaryrefslogtreecommitdiff
path: root/src/ui/mobile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/mobile.c')
-rw-r--r--src/ui/mobile.c704
1 files changed, 704 insertions, 0 deletions
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
new file mode 100644
index 00000000..f93d4352
--- /dev/null
+++ b/src/ui/mobile.c
@@ -0,0 +1,704 @@
1/* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi>
2
3Redistribution and use in source and binary forms, with or without
4modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this
7 list of conditions and the following disclaimer.
82. Redistributions in binary form must reproduce the above copyright notice,
9 this list of conditions and the following disclaimer in the documentation
10 and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23#include "mobile.h"
24
25#include "app.h"
26#include "command.h"
27#include "defs.h"
28#include "inputwidget.h"
29#include "labelwidget.h"
30#include "root.h"
31#include "text.h"
32#include "widget.h"
33
34#if defined (iPlatformAppleMobile)
35# include "ios.h"
36#endif
37
38static void updatePanelSheetMetrics_(iWidget *sheet) {
39 iWidget *navi = findChild_Widget(sheet, "panel.navi");
40 iWidget *naviPad = child_Widget(navi, 0);
41 int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI;
42#if defined (iPlatformAppleMobile)
43 float left, right, top, bottom;
44 safeAreaInsets_iOS(&left, &top, &right, &bottom);
45 setPadding_Widget(sheet, left, 0, right, 0);
46 navi->rect.pos = init_I2(left, top);
47 iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) {
48 iWidget *pad = *i.value;
49 setFixedSize_Widget(pad, init1_I2(naviHeight));
50 }
51#endif
52 setFixedSize_Widget(navi, init_I2(-1, naviHeight));
53}
54
55static iWidget *findDetailStack_(iWidget *topPanel) {
56 return findChild_Widget(parent_Widget(topPanel), "detailstack");
57}
58
59static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) {
60 if (equal_Command(cmd, "panel.open")) {
61 iWidget *button = pointer_Command(cmd);
62 iWidget *panel = userData_Object(button);
63// openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2()));
64// setFlags_Widget(panel, hidden_WidgetFlag, iFalse);
65 iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) {
66 iWidget *child = i.object;
67 setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, child != panel);
68 }
69 return iTrue;
70 }
71 if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) &&
72 argLabel_Command(cmd, "button") == SDL_BUTTON_X1) {
73 postCommand_App("panel.close");
74 return iTrue;
75 }
76 if (equal_Command(cmd, "panel.close")) {
77 iBool wasClosed = iFalse;
78 if (isPortrait_App()) {
79 iForEach(ObjectList, i, children_Widget(parent_Widget(topPanel))) {
80 iWidget *child = i.object;
81 if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) {
82 // closeMenu_Widget(child);
83 setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, iTrue);
84 setFocus_Widget(NULL);
85 updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back");
86 wasClosed = iTrue;
87 }
88 }
89 }
90 if (!wasClosed) {
91 postCommand_App("prefs.dismiss");
92 }
93 return iTrue;
94 }
95 if (equal_Command(cmd, "document.changed")) {
96 postCommand_App("prefs.dismiss");
97 return iFalse;
98 }
99 if (equal_Command(cmd, "window.resized")) {
100 // sheet > mdsplit > panel.top
101 updatePanelSheetMetrics_(parent_Widget(parent_Widget(topPanel)));
102 }
103 return iFalse;
104}
105
106static iBool isTwoColumnPage_(iWidget *d) {
107 if (cmp_String(id_Widget(d), "dialogbuttons") == 0 ||
108 cmp_String(id_Widget(d), "prefs.tabs") == 0) {
109 return iFalse;
110 }
111 if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) {
112 return class_Widget(child_Widget(d, 0)) == &Class_Widget &&
113 class_Widget(child_Widget(d, 1)) == &Class_Widget;
114 }
115 return iFalse;
116}
117
118static iBool isOmittedPref_(const iString *id) {
119 static const char *omittedPrefs[] = {
120 "prefs.smoothscroll",
121 "prefs.imageloadscroll",
122 "prefs.pinsplit",
123 "prefs.retainwindow",
124 "prefs.ca.file",
125 "prefs.ca.path",
126 };
127 iForIndices(i, omittedPrefs) {
128 if (cmp_String(id, omittedPrefs[i]) == 0) {
129 return iTrue;
130 }
131 }
132 return iFalse;
133}
134
135enum iPrefsElement {
136 panelTitle_PrefsElement,
137 heading_PrefsElement,
138 toggle_PrefsElement,
139 dropdown_PrefsElement,
140 radioButton_PrefsElement,
141 textInput_PrefsElement,
142};
143
144static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags,
145 enum iPrefsElement elementType,
146 enum iPrefsElement precedingElementType) {
147 /* Erase redundant/unused headings. */
148 if (precedingElementType == heading_PrefsElement &&
149 (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) {
150 iRelease(removeChild_Widget(panel, lastChild_Widget(panel)));
151 if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) {
152 iRelease(removeChild_Widget(panel, lastChild_Widget(panel)));
153 }
154 }
155 if (child) {
156 /* Insert padding between different element types. */
157 if (precedingElementType != panelTitle_PrefsElement) {
158 if (elementType == heading_PrefsElement ||
159 (elementType == toggle_PrefsElement &&
160 precedingElementType != toggle_PrefsElement &&
161 precedingElementType != heading_PrefsElement) ||
162 (elementType == dropdown_PrefsElement &&
163 precedingElementType != dropdown_PrefsElement &&
164 precedingElementType != heading_PrefsElement) ||
165 (elementType == textInput_PrefsElement &&
166 precedingElementType != textInput_PrefsElement &&
167 precedingElementType != heading_PrefsElement)) {
168 addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
169 }
170 }
171 if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) ||
172 (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) {
173 flags |= borderTop_WidgetFlag;
174 }
175 return addChildFlags_Widget(panel, child, flags);
176 }
177 return NULL;
178}
179
180static void stripTrailingColon_(iLabelWidget *label) {
181 const iString *text = text_LabelWidget(label);
182 if (endsWith_String(text, ":")) {
183 iString *mod = copy_String(text);
184 removeEnd_String(mod, 1);
185 updateText_LabelWidget(label, mod);
186 delete_String(mod);
187 }
188}
189
190static iLabelWidget *makePanelButton_(const char *text, const char *command) {
191 iLabelWidget *btn = new_LabelWidget(text, command);
192 setFlags_Widget(as_Widget(btn),
193 borderBottom_WidgetFlag | alignLeft_WidgetFlag |
194 frameless_WidgetFlag | extraPadding_WidgetFlag,
195 iTrue);
196 checkIcon_LabelWidget(btn);
197 setFont_LabelWidget(btn, defaultBig_FontId);
198 setTextColor_LabelWidget(btn, uiTextStrong_ColorId);
199 setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId);
200 return btn;
201}
202
203static iWidget *makeValuePadding_(iWidget *value) {
204 iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL;
205 if (input) {
206 setFont_InputWidget(input, defaultBig_FontId);
207 setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI);
208 }
209 iWidget *pad = new_Widget();
210 setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId);
211 setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI);
212 addChild_Widget(pad, iClob(value));
213 setFlags_Widget(pad,
214 borderBottom_WidgetFlag |
215 arrangeVertical_WidgetFlag |
216 resizeToParentWidth_WidgetFlag |
217 resizeWidthOfChildren_WidgetFlag |
218 arrangeHeight_WidgetFlag,
219 iTrue);
220 return pad;
221}
222
223static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) {
224 iWidget *div = new_Widget();
225 setFlags_Widget(div,
226 borderBottom_WidgetFlag | arrangeHeight_WidgetFlag |
227 resizeWidthOfChildren_WidgetFlag |
228 arrangeHorizontal_WidgetFlag, iTrue);
229 setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId);
230 setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI);
231 addChildFlags_Widget(div, iClob(heading), 0);
232 //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value)));
233 setFont_LabelWidget(heading, defaultBig_FontId);
234 setTextColor_LabelWidget(heading, uiTextStrong_ColorId);
235 if (isInstance_Object(value, &Class_InputWidget)) {
236 addChildFlags_Widget(div, iClob(value), expand_WidgetFlag);
237 }
238 else if (isInstance_Object(value, &Class_LabelWidget) &&
239 cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) {
240 addChildFlags_Widget(div, iClob(value), expand_WidgetFlag);
241 /* TODO: This doesn't work? */
242// setCommand_LabelWidget(heading,
243// collectNewFormat_String("!%s ptr:%p",
244// cstr_String(command_LabelWidget((iLabelWidget *) value)),
245// value));
246 }
247 else {
248 addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag);
249 addChild_Widget(div, iClob(value));
250 }
251 return div;
252}
253
254static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton,
255 const iString *titleText) {
256 iWidget *panel = new_Widget();
257 setId_Widget(panel, "panel");
258 setUserData_Object(panelButton, panel);
259 setBackgroundColor_Widget(panel, uiBackground_ColorId);
260 setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad");
261 if (titleText) {
262 iLabelWidget *title =
263 addChildFlags_Widget(panel,
264 iClob(new_LabelWidget(cstr_String(titleText), NULL)),
265 alignLeft_WidgetFlag | frameless_WidgetFlag);
266 setFont_LabelWidget(title, uiLabelLargeBold_FontId);
267 setTextColor_LabelWidget(title, uiHeading_ColorId);
268 }
269 addChildFlags_Widget(parent,
270 iClob(panel),
271 focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag |
272 arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
273 arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag |
274 //horizontalOffset_WidgetFlag | edgeDraggable_WidgetFlag |
275 commandOnClick_WidgetFlag);
276 return panel;
277}
278
279void finalizeSheet_Mobile(iWidget *sheet) {
280 /* The sheet contents are completely rearranged and restyled on a phone.
281 We'll set up a linear fullscreen arrangement of the widgets. Sheets are already
282 scrollable so they can be taller than the display. In hindsight, it may have been
283 easier to create phone versions of each dialog, but at least this works with any
284 future changes to the UI (..."works"). At least this way it is possible to enforce
285 a consistent styling. */
286 if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) {
287 if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) {
288 /* Already finalized. */
289 arrange_Widget(sheet);
290 postRefresh_App();
291 return;
292 }
293 /* Landscape Layout Portrait Layout
294
295 ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐
296 │ │┌───────────────────┐ │ │ │Detail
297 │ ││┌──────────────────┴┐ │ │ │Stack │
298 │ │││┌──────────────────┴┐│ │ │┌──────┐
299 │ ││││ ││ │ ││┌─────┴┐│
300 │ ││││ ││ │ │││ │
301 │Top Panel││││ ││ │Top Panel│││ ││
302 │ ││││ Panels ││ │ │││Panels│
303 │ ││││ ││ │ │││ ││
304 │ │└┤│ ││ │ │││ │
305 │ │ └┤ ││ │ │└┤ ││
306 │ │ └───────────────────┘│ │ │ └──────┘
307 └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘
308 offscreen
309 */
310 /* Modify the top sheet to act as a fullscreen background. */
311 setPadding1_Widget(sheet, 0);
312 setBackgroundColor_Widget(sheet, uiBackground_ColorId);
313 setFlags_Widget(sheet,
314 keepOnTop_WidgetFlag |
315 parentCannotResize_WidgetFlag |
316 arrangeSize_WidgetFlag |
317 centerHorizontal_WidgetFlag |
318 arrangeVertical_WidgetFlag |
319 arrangeHorizontal_WidgetFlag |
320 overflowScrollable_WidgetFlag,
321 iFalse);
322 setFlags_Widget(sheet,
323 frameless_WidgetFlag |
324 resizeWidthOfChildren_WidgetFlag |
325 edgeDraggable_WidgetFlag |
326 commandOnClick_WidgetFlag,
327 iTrue);
328 iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */
329 iPtrArray * panelButtons = collect_PtrArray(new_PtrArray());
330 iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs");
331 iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0));
332 const iBool isPrefs = (prefsTabs != NULL);
333 const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag |
334 frameless_WidgetFlag | extraPadding_WidgetFlag;
335 iWidget *mainDetailSplit = makeHDiv_Widget();
336 setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse);
337 setId_Widget(mainDetailSplit, "mdsplit");
338 iWidget *topPanel = new_Widget(); {
339 setId_Widget(topPanel, "panel.top");
340 setCommandHandler_Widget(topPanel, topPanelHandler_);
341 setFlags_Widget(topPanel,
342 arrangeVertical_WidgetFlag |
343 resizeWidthOfChildren_WidgetFlag |
344 arrangeHeight_WidgetFlag |
345 overflowScrollable_WidgetFlag |
346 commandOnClick_WidgetFlag,
347 iTrue);
348 addChild_Widget(mainDetailSplit, iClob(topPanel));
349 }
350 iWidget *detailStack = new_Widget(); {
351 setId_Widget(detailStack, "detailstack");
352 setFlags_Widget(detailStack, resizeWidthOfChildren_WidgetFlag, iTrue);
353 addChild_Widget(mainDetailSplit, iClob(detailStack));
354 }
355 //setFlags_Widget(topPanel, topPanelOffset_WidgetFlag, iTrue); /* slide with children */
356 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
357 if (prefsTabs) {
358 iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */
359 iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons")));
360 /* Pull out the pages and make them panels. */
361 iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages");
362 size_t pageCount = tabCount_Widget(prefsTabs);
363 for (size_t i = 0; i < pageCount; i++) {
364 iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0))));
365 iWidget *page = removeTabPage_Widget(prefsTabs, 0);
366 iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */
367 pushBack_PtrArray(contents, ref_Object(pageContent));
368 iLabelWidget *panelButton;
369 pushBack_PtrArray(panelButtons,
370 addChildFlags_Widget(topPanel,
371 iClob(panelButton = makePanelButton_(
372 i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text),
373 "panel.open")),
374 (i == 0 ? borderTop_WidgetFlag : 0) |
375 chevron_WidgetFlag));
376 const iChar icons[] = {
377 0x02699, /* gear */
378 0x1f4f1, /* mobile phone */
379 0x1f3a8, /* palette */
380 0x1f523,
381 0x1f5a7, /* computer network */
382 };
383 setIcon_LabelWidget(panelButton, icons[i]);
384// setFont_LabelWidget(panelButton, defaultBig_FontId);
385// setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId);
386 iRelease(page);
387 delete_String(text);
388 }
389 destroy_Widget(prefsTabs);
390 }
391 iForEach(ObjectList, i, children_Widget(sheet)) {
392 iWidget *child = i.object;
393 if (isTwoColumnPage_(child)) {
394 pushBack_PtrArray(contents, removeChild_Widget(sheet, child));
395 }
396 else {
397 removeChild_Widget(sheet, child);
398 addChild_Widget(topPanel, child);
399 iRelease(child);
400 }
401 }
402 const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons));
403 addChild_Widget(sheet, iClob(mainDetailSplit));
404 iForEach(PtrArray, j, contents) {
405 iWidget *owner = topPanel;
406 if (useSlidePanels) {
407 /* Create a new child panel. */
408 iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j));
409 owner = addChildPanel_(detailStack, button,
410 collect_String(upper_String(text_LabelWidget(button))));
411 }
412 iWidget *pageContent = j.ptr;
413 iWidget *headings = child_Widget(pageContent, 0);
414 iWidget *values = child_Widget(pageContent, 1);
415 enum iPrefsElement prevElement = panelTitle_PrefsElement;
416 /* Identify the types of controls in the dialog and restyle/organize them. */
417 while (!isEmpty_ObjectList(children_Widget(headings))) {
418 iWidget *heading = child_Widget(headings, 0);
419 iWidget *value = child_Widget(values, 0);
420 removeChild_Widget(headings, heading);
421 removeChild_Widget(values, value);
422 /* Can we ignore these widgets? */
423 if (isOmittedPref_(id_Widget(value)) ||
424 (class_Widget(heading) == &Class_Widget &&
425 class_Widget(value) == &Class_Widget) /* just padding */) {
426 iRelease(heading);
427 iRelease(value);
428 continue;
429 }
430 enum iPrefsElement element = toggle_PrefsElement;
431 iLabelWidget *headingLabel = NULL;
432 iLabelWidget *valueLabel = NULL;
433 iInputWidget *valueInput = NULL;
434 const iBool isMenuButton = findChild_Widget(value, "menu") != NULL;
435 if (isInstance_Object(heading, &Class_LabelWidget)) {
436 headingLabel = (iLabelWidget *) heading;
437 stripTrailingColon_(headingLabel);
438 }
439 if (isInstance_Object(value, &Class_LabelWidget)) {
440 valueLabel = (iLabelWidget *) value;
441 setFont_LabelWidget(valueLabel, defaultBig_FontId);
442 }
443 if (isInstance_Object(value, &Class_InputWidget)) {
444 valueInput = (iInputWidget *) value;
445 setFlags_Widget(value, borderBottom_WidgetFlag, iFalse);
446 element = textInput_PrefsElement;
447 }
448 if (childCount_Widget(value) >= 2) {
449 if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) {
450 element = textInput_PrefsElement;
451 setPadding_Widget(value, 0, 0, gap_UI, 0);
452 valueInput = child_Widget(value, 0);
453 }
454 }
455 if (valueInput) {
456 setFont_InputWidget(valueInput, defaultBig_FontId);
457 setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0);
458 }
459 /* Toggles have the button on the right. */
460 if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) {
461 element = toggle_PrefsElement;
462 addPanelChild_(owner,
463 iClob(makeValuePaddingWithHeading_(headingLabel, value)),
464 0,
465 element,
466 prevElement);
467 }
468 else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) {
469 element = heading_PrefsElement;
470 iRelease(value);
471 addPanelChild_(owner, iClob(heading), 0, element, prevElement);
472 setFont_LabelWidget(headingLabel, uiLabel_FontId);
473 }
474 else if (isMenuButton) {
475 element = dropdown_PrefsElement;
476 setFlags_Widget(value,
477 alignRight_WidgetFlag | noBackground_WidgetFlag |
478 frameless_WidgetFlag, iTrue);
479 setFlags_Widget(value, alignLeft_WidgetFlag, iFalse);
480 iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0,
481 element, prevElement);
482 pad->padding[2] = gap_UI;
483 }
484 else if (valueInput) {
485 addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0,
486 element, prevElement);
487 }
488 else {
489 if (childCount_Widget(value) >= 2) {
490 element = radioButton_PrefsElement;
491 /* Always padding before radio buttons. */
492 addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
493 }
494 addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag);
495 if (headingLabel) {
496 setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId);
497 setText_LabelWidget(headingLabel,
498 collect_String(upper_String(text_LabelWidget(headingLabel))));
499 }
500 addPanelChild_(owner, iClob(value), 0, element, prevElement);
501 /* Radio buttons expand to fill the space. */
502 if (element == radioButton_PrefsElement) {
503 setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId);
504 setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI);
505 setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse);
506 setFlags_Widget(value,
507 borderBottom_WidgetFlag |
508 resizeToParentWidth_WidgetFlag |
509 resizeWidthOfChildren_WidgetFlag,
510 iTrue);
511 iForEach(ObjectList, sub, children_Widget(value)) {
512 if (isInstance_Object(sub.object, &Class_LabelWidget)) {
513 iLabelWidget *opt = sub.object;
514 setFont_LabelWidget(opt, defaultMedium_FontId);
515 setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue);
516 }
517 }
518 }
519 }
520 prevElement = element;
521 }
522 addPanelChild_(owner, NULL, 0, 0, prevElement);
523 destroy_Widget(pageContent);
524 setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue);
525 }
526 destroyPending_Root(sheet->root);
527 /* Additional elements for preferences. */
528 if (isPrefs) {
529 addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId))));
530 addChildFlags_Widget(topPanel,
531 iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")),
532 borderTop_WidgetFlag);
533 iLabelWidget *aboutButton = addChildFlags_Widget(topPanel,
534 iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")),
535 chevron_WidgetFlag);
536 /* The About panel. */ {
537 iWidget *panel = addChildPanel_(detailStack, aboutButton, NULL);
538 iString *msg = collectNew_String();
539 setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION);
540#if defined (iPlatformAppleMobile)
541 appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")");
542#endif
543 addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)),
544 frameless_WidgetFlag);
545 addChildFlags_Widget(panel,
546 iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi",
547 "!open url:https://skyjake.fi/@jk")),
548 borderTop_WidgetFlag);
549 addChildFlags_Widget(panel,
550 iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}",
551 "!open url:about:version")),
552 0);
553 addChildFlags_Widget(panel,
554 iClob(makePanelButton_(info_Icon " ${menu.aboutpages}",
555 "!open url:about:about")),
556 0);
557 addChildFlags_Widget(panel,
558 iClob(makePanelButton_(bug_Icon " ${menu.debug}",
559 "!open url:about:debug")),
560 0);
561 }
562 }
563 else {
564 setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue);
565 /* Update heading style. */
566 setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId);
567 setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue);
568 }
569 if (findChild_Widget(sheet, "valueinput.prompt")) {
570 iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt");
571 setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue);
572 iInputWidget *input = findChild_Widget(sheet, "input");
573 removeChild_Widget(parent_Widget(input), input);
574 addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input))));
575 }
576 /* Top padding for each panel, to account for the overlaid navbar. */ {
577 setId_Widget(addChildPos_Widget(topPanel,
578 iClob(makePadding_Widget(0)), front_WidgetAddPos),
579 "panel.toppad");
580 }
581 /* Navbar. */ {
582 iWidget *navi = new_Widget();
583 setId_Widget(navi, "panel.navi");
584 setBackgroundColor_Widget(navi, uiBackground_ColorId);
585 addChild_Widget(navi, iClob(makePadding_Widget(0)));
586 iLabelWidget *back = addChildFlags_Widget(navi,
587 iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")),
588 noBackground_WidgetFlag | frameless_WidgetFlag |
589 alignLeft_WidgetFlag | extraPadding_WidgetFlag);
590 checkIcon_LabelWidget(back);
591 setId_Widget(as_Widget(back), "panel.back");
592 setFont_LabelWidget(back, defaultBig_FontId);
593 if (!isPrefs) {
594 /* Pick up the dialog buttons for the navbar. */
595 iWidget *buttons = findChild_Widget(sheet, "dialogbuttons");
596 iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel");
597// if (!cancel) {
598// cancel = findMenuItem_Widget(buttons, "translation.cancel");
599// }
600 if (cancel) {
601 updateText_LabelWidget(back, text_LabelWidget(cancel));
602 setCommand_LabelWidget(back, command_LabelWidget(cancel));
603 }
604 iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons);
605 if (def && !cancel) {
606 updateText_LabelWidget(back, text_LabelWidget(def));
607 setCommand_LabelWidget(back, command_LabelWidget(def));
608 setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse);
609 setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue);
610 setIcon_LabelWidget(back, 0);
611 setFont_LabelWidget(back, defaultBigBold_FontId);
612 }
613 else if (def != cancel) {
614 removeChild_Widget(buttons, def);
615 setFont_LabelWidget(def, defaultBigBold_FontId);
616 setFlags_Widget(as_Widget(def),
617 frameless_WidgetFlag | extraPadding_WidgetFlag |
618 noBackground_WidgetFlag, iTrue);
619 addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag);
620 updateSize_LabelWidget(def);
621 }
622 /* Action buttons are added in the bottom as extra buttons. */ {
623 iBool isFirstAction = iTrue;
624 iForEach(ObjectList, i, children_Widget(buttons)) {
625 if (isInstance_Object(i.object, &Class_LabelWidget) &&
626 i.object != cancel && i.object != def) {
627 iLabelWidget *item = i.object;
628 setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId);
629 setFont_LabelWidget(item, defaultBig_FontId);
630 removeChild_Widget(buttons, item);
631 addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags |
632 (isFirstAction ? borderTop_WidgetFlag : 0));
633 updateSize_LabelWidget(item);
634 isFirstAction = iFalse;
635 }
636 }
637 }
638 iRelease(removeChild_Widget(parent_Widget(buttons), buttons));
639 /* Styling for remaining elements. */
640 iForEach(ObjectList, i, children_Widget(topPanel)) {
641 if (isInstance_Object(i.object, &Class_LabelWidget) &&
642 isEmpty_String(command_LabelWidget(i.object)) &&
643 isEmpty_String(id_Widget(i.object))) {
644 setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue);
645 if (font_LabelWidget(i.object) == uiLabel_FontId) {
646 setFont_LabelWidget(i.object, uiContent_FontId);
647 }
648 }
649 }
650 }
651 addChildFlags_Widget(sheet, iClob(navi),
652 drawBackgroundToVerticalSafeArea_WidgetFlag |
653 arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
654 resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag);
655 }
656 updatePanelSheetMetrics_(sheet);
657 iAssert(sheet->parent);
658 arrange_Widget(sheet->parent);
659 postCommand_App("widget.overflow"); /* with the correct dimensions */
660 //puts("---- MOBILE LAYOUT ----");
661 //printTree_Widget(sheet);
662 }
663 else {
664 arrange_Widget(sheet);
665 }
666 postRefresh_App();
667}
668
669void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) {
670 if (deviceType_App() != phone_AppDeviceType) {
671 return;
672 }
673 const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0;
674 if (isSlidePanel && isLandscape_App()) {
675 return;
676 }
677 if (isIncoming) {
678 setVisualOffset_Widget(sheet, isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), 0, 0);
679 setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag);
680 }
681 else {
682 const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1;
683 setVisualOffset_Widget(sheet,
684 isSlidePanel ? width_Widget(sheet) : height_Widget(sheet),
685 wasDragged ? 100 : 200,
686 wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag);
687 }
688}
689
690void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) {
691 if (deviceType_App() != phone_AppDeviceType || isLandscape_App()) {
692 return;
693 }
694 if (isIncoming) {
695 setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue);
696 setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0);
697 setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag);
698 }
699 else {
700 const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0;
701 setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200,
702 wasDragged ? 0 : easeIn_AnimFlag);
703 }
704}