summaryrefslogtreecommitdiff
path: root/src/ui
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
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')
-rw-r--r--src/ui/documentwidget.c4
-rw-r--r--src/ui/mobile.c704
-rw-r--r--src/ui/mobile.h32
-rw-r--r--src/ui/root.c106
-rw-r--r--src/ui/util.c719
-rw-r--r--src/ui/util.h4
6 files changed, 809 insertions, 760 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 32cbaca9..9d60f86f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1651,7 +1651,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
1651 break; 1651 break;
1652 } 1652 }
1653 case categorySuccess_GmStatusCode: 1653 case categorySuccess_GmStatusCode:
1654 reset_SmoothScroll(&d->scrollY); 1654 //reset_SmoothScroll(&d->scrollY);
1655 reset_GmDocument(d->doc); /* new content incoming */ 1655 reset_GmDocument(d->doc); /* new content incoming */
1656 delete_Gempub(d->sourceGempub); 1656 delete_Gempub(d->sourceGempub);
1657 d->sourceGempub = NULL; 1657 d->sourceGempub = NULL;
@@ -2307,7 +2307,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2307 } 2307 }
2308 updateFetchProgress_DocumentWidget_(d); 2308 updateFetchProgress_DocumentWidget_(d);
2309 checkResponse_DocumentWidget_(d); 2309 checkResponse_DocumentWidget_(d);
2310 if (category_GmStatusCode(status_GmRequest(d->request)) == success_GmStatusCode) { 2310 if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) {
2311 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ 2311 init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */
2312 } 2312 }
2313 d->state = ready_RequestState; 2313 d->state = ready_RequestState;
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}
diff --git a/src/ui/mobile.h b/src/ui/mobile.h
new file mode 100644
index 00000000..92b2280a
--- /dev/null
+++ b/src/ui/mobile.h
@@ -0,0 +1,32 @@
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#pragma once
24
25#include <the_Foundation/defs.h>
26
27iDeclareType(Widget)
28
29void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming);
30
31void finalizeSheet_Mobile (iWidget *sheet);
32void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming);
diff --git a/src/ui/root.c b/src/ui/root.c
index f37d274d..6cf3f424 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -469,11 +469,11 @@ static void setReloadLabel_Root_(iRoot *d, iBool animating) {
469 const iBool isMobile = deviceType_App() != desktop_AppDeviceType; 469 const iBool isMobile = deviceType_App() != desktop_AppDeviceType;
470 iLabelWidget *label = findChild_Widget(d->widget, "reload"); 470 iLabelWidget *label = findChild_Widget(d->widget, "reload");
471 updateTextCStr_LabelWidget( 471 updateTextCStr_LabelWidget(
472 label, animating ? loadAnimationCStr_() : (isMobile ? pageMenuCStr_ : reloadCStr_)); 472 label, animating ? loadAnimationCStr_() : (/*isMobile ? pageMenuCStr_ :*/ reloadCStr_));
473 if (isMobile) { 473// if (isMobile) {
474 setCommand_LabelWidget(label, 474// setCommand_LabelWidget(label,
475 collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); 475// collectNewCStr_String(animating ? "navigate.reload" : "menu.open"));
476 } 476// }
477} 477}
478 478
479static void checkLoadAnimation_Root_(iRoot *d) { 479static void checkLoadAnimation_Root_(iRoot *d) {
@@ -539,9 +539,12 @@ static iBool willPerformSearchQuery_(const iString *userInput) {
539 539
540static void updateUrlInputContentPadding_(iWidget *navBar) { 540static void updateUrlInputContentPadding_(iWidget *navBar) {
541 iInputWidget *url = findChild_Widget(navBar, "url"); 541 iInputWidget *url = findChild_Widget(navBar, "url");
542 const iWidget *indicators = findChild_Widget(navBar, "url.rightembed"); 542 const int lockWidth = width_Widget(findChild_Widget(navBar, "navbar.lock"));
543 setContentPadding_InputWidget(url, -1, 543 const int indicatorsWidth = width_Widget(findChild_Widget(navBar, "url.rightembed"));
544 width_Widget(indicators)); 544 /* The indicators widget has a padding that covers the urlButtons area. */
545 setContentPadding_InputWidget(url,
546 lockWidth - 2 * gap_UI, // * 0.75f,
547 indicatorsWidth);
545} 548}
546 549
547static void showSearchQueryIndicator_(iBool show) { 550static void showSearchQueryIndicator_(iBool show) {
@@ -588,12 +591,12 @@ static void updateNavBarSize_(iWidget *navBar) {
588 updateSize_LabelWidget(label); 591 updateSize_LabelWidget(label);
589 } 592 }
590 } 593 }
594 updateUrlInputContentPadding_(navBar);
591 /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ 595 /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */
592 /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ 596// const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock"));
593 const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); 597// setContentPadding_InputWidget(findChild_Widget(navBar, "url"),
594 setContentPadding_InputWidget(findChild_Widget(navBar, "url"), 598// embedButtonWidth * 0.75f,
595 embedButtonWidth * 0.75f, 599// embedButtonWidth * 0.75f);
596 embedButtonWidth * 0.75f);
597 } 600 }
598 if (isPhone) { 601 if (isPhone) {
599 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", 602 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar",
@@ -904,15 +907,19 @@ void updateMetrics_Root(iRoot *d) {
904 iWidget *url = findChild_Widget(d->widget, "url"); 907 iWidget *url = findChild_Widget(d->widget, "url");
905 iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); 908 iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed");
906 iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); 909 iWidget *embedPad = findChild_Widget(navBar, "url.embedpad");
910 iWidget *urlButtons = findChild_Widget(navBar, "url.buttons");
907 setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); 911 setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI);
908 navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ 912 navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */
909 updateSize_LabelWidget((iLabelWidget *) lock); 913// updateSize_LabelWidget((iLabelWidget *) lock);
910 setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); 914// updateSize_LabelWidget((iLabelWidget *) findChild_Widget(navBar, "reload"));
911 setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, 915// arrange_Widget(urlButtons);
912 width_Widget(lock) * 0.75); 916 setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1));
917// setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75,
918// width_Widget(lock) * 0.75);
913 rightEmbed->rect.pos.y = gap_UI; 919 rightEmbed->rect.pos.y = gap_UI;
914 updatePadding_Root(d); 920 updatePadding_Root(d);
915 arrange_Widget(d->widget); 921 arrange_Widget(d->widget);
922 updateUrlInputContentPadding_(navBar);
916 postRefresh_App(); 923 postRefresh_App();
917} 924}
918 925
@@ -1060,7 +1067,7 @@ void createUserInterface_Root(iRoot *d) {
1060 setNoAutoMinHeight_LabelWidget(fprog, iTrue); 1067 setNoAutoMinHeight_LabelWidget(fprog, iTrue);
1061 addChildFlags_Widget(rightEmbed, 1068 addChildFlags_Widget(rightEmbed,
1062 iClob(fprog), 1069 iClob(fprog),
1063 collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); 1070 collapse_WidgetFlag | hidden_WidgetFlag | frameless_WidgetFlag);
1064 } 1071 }
1065 /* Download progress indicator is also inside the input field, but hidden normally. */ { 1072 /* Download progress indicator is also inside the input field, but hidden normally. */ {
1066 iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); 1073 iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL);
@@ -1069,7 +1076,7 @@ void createUserInterface_Root(iRoot *d) {
1069 setAlignVisually_LabelWidget(progress, iTrue); 1076 setAlignVisually_LabelWidget(progress, iTrue);
1070 setNoAutoMinHeight_LabelWidget(progress, iTrue); 1077 setNoAutoMinHeight_LabelWidget(progress, iTrue);
1071 addChildFlags_Widget( 1078 addChildFlags_Widget(
1072 rightEmbed, iClob(progress), collapse_WidgetFlag); 1079 rightEmbed, iClob(progress), collapse_WidgetFlag | hidden_WidgetFlag);
1073 } 1080 }
1074 /* Pinning indicator. */ { 1081 /* Pinning indicator. */ {
1075 iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); 1082 iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL);
@@ -1079,39 +1086,44 @@ void createUserInterface_Root(iRoot *d) {
1079 setNoAutoMinHeight_LabelWidget(pin, iTrue); 1086 setNoAutoMinHeight_LabelWidget(pin, iTrue);
1080 addChildFlags_Widget(rightEmbed, 1087 addChildFlags_Widget(rightEmbed,
1081 iClob(pin), 1088 iClob(pin),
1082 collapse_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); 1089 collapse_WidgetFlag | hidden_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag);
1090 }
1091 iWidget *urlButtons = new_Widget();
1092 setId_Widget(urlButtons, "url.buttons");
1093 setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
1094 /* Mobile page menu. */
1095 if (deviceType_App() != desktop_AppDeviceType) {
1096 iLabelWidget *pageMenuButton;
1097 /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */
1098 pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_,
1099 (iMenuItem[]){
1100 { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" },
1101 { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" },
1102 { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" },
1103 { "---", 0, 0, NULL },
1104 { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
1105 { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" },
1106 { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" },
1107 { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" },
1108 { "---", 0, 0, NULL },
1109 { "${menu.page.copyurl}", 0, 0, "document.copylink" },
1110 { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" },
1111 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } },
1112 12);
1113 setId_Widget(as_Widget(pageMenuButton), "pagemenubutton");
1114 setFont_LabelWidget(pageMenuButton, uiContentBold_FontId);
1115 setAlignVisually_LabelWidget(pageMenuButton, iTrue);
1116 addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags);
1117 updateSize_LabelWidget(pageMenuButton);
1083 } 1118 }
1084 /* Reload button. */ { 1119 /* Reload button. */ {
1085 iLabelWidget *reload; 1120 iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload");
1086 if (deviceType_App() == desktop_AppDeviceType) {
1087 reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload");
1088 }
1089 else {
1090 /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */
1091 reload = makeMenuButton_LabelWidget(pageMenuCStr_,
1092 (iMenuItem[]){
1093 { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" },
1094 { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" },
1095 { "---", 0, 0, NULL },
1096 { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" },
1097 { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" },
1098 { "---", 0, 0, NULL },
1099 { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
1100 { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" },
1101 { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" },
1102 { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" },
1103 { "---", 0, 0, NULL },
1104 { "${menu.page.copyurl}", 0, 0, "document.copylink" },
1105 { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" },
1106 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } },
1107 14);
1108 setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId);
1109 setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue);
1110 }
1111 setId_Widget(as_Widget(reload), "reload"); 1121 setId_Widget(as_Widget(reload), "reload");
1112 addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); 1122 addChildFlags_Widget(urlButtons, iClob(reload), embedFlags);
1113 updateSize_LabelWidget(reload); 1123 updateSize_LabelWidget(reload);
1114 } 1124 }
1125 addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag);
1126 arrange_Widget(urlButtons);
1115 setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); 1127 setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad");
1116 } 1128 }
1117 if (deviceType_App() != desktop_AppDeviceType) { 1129 if (deviceType_App() != desktop_AppDeviceType) {
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}
diff --git a/src/ui/util.h b/src/ui/util.h
index 6185945f..43aeb172 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25#include "mobile.h"
26
25#include <the_Foundation/string.h> 27#include <the_Foundation/string.h>
26#include <the_Foundation/rect.h> 28#include <the_Foundation/rect.h>
27#include <the_Foundation/vec2.h> 29#include <the_Foundation/vec2.h>
@@ -256,8 +258,6 @@ size_t tabCount_Widget (const iWidget *tabs);
256/*-----------------------------------------------------------------------------------------------*/ 258/*-----------------------------------------------------------------------------------------------*/
257 259
258iWidget * makeSheet_Widget (const char *id); 260iWidget * makeSheet_Widget (const char *id);
259void finalizeSheet_Widget (iWidget *sheet);
260void setupSheetTransition_Widget (iWidget *sheet, iBool isIncoming);
261iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); 261iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions);
262 262
263iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, 263iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values,