diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-16 13:21:36 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2021-05-16 13:21:36 +0300 |
commit | 97f9a1c9bf49ca67fe1f99c57aa70e0bdf64a466 (patch) | |
tree | b680da6408f78c4555edf3f6b9e86c6ce8925a53 /src/ui | |
parent | 19f29bc4382b3dfe9d3c21ddabd5501919c76566 (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.c | 4 | ||||
-rw-r--r-- | src/ui/mobile.c | 704 | ||||
-rw-r--r-- | src/ui/mobile.h | 32 | ||||
-rw-r--r-- | src/ui/root.c | 106 | ||||
-rw-r--r-- | src/ui/util.c | 719 | ||||
-rw-r--r-- | src/ui/util.h | 4 |
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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, 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 | |||
38 | static 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 | |||
55 | static iWidget *findDetailStack_(iWidget *topPanel) { | ||
56 | return findChild_Widget(parent_Widget(topPanel), "detailstack"); | ||
57 | } | ||
58 | |||
59 | static 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 | |||
106 | static 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 | |||
118 | static 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 | |||
135 | enum iPrefsElement { | ||
136 | panelTitle_PrefsElement, | ||
137 | heading_PrefsElement, | ||
138 | toggle_PrefsElement, | ||
139 | dropdown_PrefsElement, | ||
140 | radioButton_PrefsElement, | ||
141 | textInput_PrefsElement, | ||
142 | }; | ||
143 | |||
144 | static 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 | |||
180 | static 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 | |||
190 | static 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 | |||
203 | static 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 | |||
223 | static 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 | |||
254 | static 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 | |||
279 | void 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 | |||
669 | void 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 | |||
690 | void 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 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. 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 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY 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 | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include <the_Foundation/defs.h> | ||
26 | |||
27 | iDeclareType(Widget) | ||
28 | |||
29 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | ||
30 | |||
31 | void finalizeSheet_Mobile (iWidget *sheet); | ||
32 | void 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 | ||
479 | static void checkLoadAnimation_Root_(iRoot *d) { | 479 | static void checkLoadAnimation_Root_(iRoot *d) { |
@@ -539,9 +539,12 @@ static iBool willPerformSearchQuery_(const iString *userInput) { | |||
539 | 539 | ||
540 | static void updateUrlInputContentPadding_(iWidget *navBar) { | 540 | static 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 | ||
547 | static void showSearchQueryIndicator_(iBool show) { | 550 | static 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 | ||
846 | void closeMenu_Widget(iWidget *d) { | 843 | void 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 | ||
864 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { | 854 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { |
@@ -1127,46 +1117,6 @@ size_t tabCount_Widget(const iWidget *tabs) { | |||
1127 | 1117 | ||
1128 | /*-----------------------------------------------------------------------------------------------*/ | 1118 | /*-----------------------------------------------------------------------------------------------*/ |
1129 | 1119 | ||
1130 | static 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 | |||
1138 | iBool 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 | ||
1171 | iWidget *makeSheet_Widget(const char *id) { | 1121 | iWidget *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 | ||
1186 | static 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 | |||
1203 | static 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 | |||
1257 | static 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 | |||
1269 | static 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 | |||
1286 | enum iPrefsElement { | ||
1287 | panelTitle_PrefsElement, | ||
1288 | heading_PrefsElement, | ||
1289 | toggle_PrefsElement, | ||
1290 | dropdown_PrefsElement, | ||
1291 | radioButton_PrefsElement, | ||
1292 | textInput_PrefsElement, | ||
1293 | }; | ||
1294 | |||
1295 | static 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 | |||
1331 | static 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 | |||
1341 | static 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 | |||
1354 | static 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 | |||
1374 | static 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 | |||
1405 | static 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 | |||
1430 | void 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 | |||
1790 | void 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 | |||
1818 | static void acceptValueInput_(iWidget *dlg) { | 1136 | static 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 | ||
2259 | void 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 | |||
2276 | iWidget *makePreferences_Widget(void) { | 1577 | iWidget *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 | ||
258 | iWidget * makeSheet_Widget (const char *id); | 260 | iWidget * makeSheet_Widget (const char *id); |
259 | void finalizeSheet_Widget (iWidget *sheet); | ||
260 | void setupSheetTransition_Widget (iWidget *sheet, iBool isIncoming); | ||
261 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); | 261 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); |
262 | 262 | ||
263 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 263 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |