diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/app.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 3 | ||||
-rw-r--r-- | src/ui/root.c | 1224 | ||||
-rw-r--r-- | src/ui/root.h | 13 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 3 | ||||
-rw-r--r-- | src/ui/widget.c | 5 | ||||
-rw-r--r-- | src/ui/window.c | 1186 | ||||
-rw-r--r-- | src/ui/window.h | 1 |
9 files changed, 1265 insertions, 1175 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index bf624a1d..1813fd70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -169,6 +169,8 @@ set (SOURCES | |||
169 | src/ui/metrics.h | 169 | src/ui/metrics.h |
170 | src/ui/paint.c | 170 | src/ui/paint.c |
171 | src/ui/paint.h | 171 | src/ui/paint.h |
172 | src/ui/root.c | ||
173 | src/ui/root.h | ||
172 | src/ui/mediaui.c | 174 | src/ui/mediaui.c |
173 | src/ui/mediaui.h | 175 | src/ui/mediaui.h |
174 | src/ui/scrollwidget.c | 176 | src/ui/scrollwidget.c |
@@ -39,6 +39,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
39 | #include "ui/inputwidget.h" | 39 | #include "ui/inputwidget.h" |
40 | #include "ui/keys.h" | 40 | #include "ui/keys.h" |
41 | #include "ui/labelwidget.h" | 41 | #include "ui/labelwidget.h" |
42 | #include "ui/root.h" | ||
42 | #include "ui/sidebarwidget.h" | 43 | #include "ui/sidebarwidget.h" |
43 | #include "ui/text.h" | 44 | #include "ui/text.h" |
44 | #include "ui/util.h" | 45 | #include "ui/util.h" |
@@ -1662,7 +1663,7 @@ iBool handleCommand_App(const char *cmd) { | |||
1662 | else if (equal_Command(cmd, "hidetoolbarscroll")) { | 1663 | else if (equal_Command(cmd, "hidetoolbarscroll")) { |
1663 | d->prefs.hideToolbarOnScroll = arg_Command(cmd); | 1664 | d->prefs.hideToolbarOnScroll = arg_Command(cmd); |
1664 | if (!d->prefs.hideToolbarOnScroll) { | 1665 | if (!d->prefs.hideToolbarOnScroll) { |
1665 | showToolbars_Window(d->window, iTrue); | 1666 | showToolbars_Root(get_Root(), iTrue); |
1666 | } | 1667 | } |
1667 | return iTrue; | 1668 | return iTrue; |
1668 | } | 1669 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index cdf3faa5..ee44933e 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -42,6 +42,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
42 | #include "media.h" | 42 | #include "media.h" |
43 | #include "paint.h" | 43 | #include "paint.h" |
44 | #include "periodic.h" | 44 | #include "periodic.h" |
45 | #include "root.h" | ||
45 | #include "mediaui.h" | 46 | #include "mediaui.h" |
46 | #include "scrollwidget.h" | 47 | #include "scrollwidget.h" |
47 | #include "touch.h" | 48 | #include "touch.h" |
@@ -1353,7 +1354,7 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1353 | /* Show and hide toolbar on scroll. */ | 1354 | /* Show and hide toolbar on scroll. */ |
1354 | if (deviceType_App() == phone_AppDeviceType) { | 1355 | if (deviceType_App() == phone_AppDeviceType) { |
1355 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { | 1356 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { |
1356 | showToolbars_Window(get_Window(), offset < 0); | 1357 | showToolbars_Root(get_Root(), offset < 0); |
1357 | } | 1358 | } |
1358 | } | 1359 | } |
1359 | updateVisible_DocumentWidget_(d); | 1360 | updateVisible_DocumentWidget_(d); |
diff --git a/src/ui/root.c b/src/ui/root.c new file mode 100644 index 00000000..50c4992e --- /dev/null +++ b/src/ui/root.c | |||
@@ -0,0 +1,1224 @@ | |||
1 | /* Copyright 2020 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 "root.h" | ||
24 | |||
25 | #include "app.h" | ||
26 | #include "bookmarks.h" | ||
27 | #include "command.h" | ||
28 | #include "defs.h" | ||
29 | #include "documentwidget.h" | ||
30 | #include "embedded.h" | ||
31 | #include "inputwidget.h" | ||
32 | #include "keys.h" | ||
33 | #include "labelwidget.h" | ||
34 | #include "lookupwidget.h" | ||
35 | #include "sidebarwidget.h" | ||
36 | #include "window.h" | ||
37 | #include "../visited.h" | ||
38 | #include "../history.h" | ||
39 | #include "../gmcerts.h" | ||
40 | #include "../gmutil.h" | ||
41 | #include "../visited.h" | ||
42 | |||
43 | #if defined (iPlatformMsys) | ||
44 | # include "../win32.h" | ||
45 | #endif | ||
46 | #if defined (iPlatformAppleDesktop) | ||
47 | # include "macos.h" | ||
48 | #endif | ||
49 | #if defined (iPlatformAppleMobile) | ||
50 | # include "ios.h" | ||
51 | #endif | ||
52 | |||
53 | #include <SDL_timer.h> | ||
54 | |||
55 | #if defined (iPlatformAppleDesktop) | ||
56 | # define iHaveNativeMenus | ||
57 | #endif | ||
58 | |||
59 | #if defined (iPlatformPcDesktop) | ||
60 | /* TODO: Submenus wouldn't hurt here. */ | ||
61 | static const iMenuItem navMenuItems_[] = { | ||
62 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
63 | { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, | ||
64 | { "---", 0, 0, NULL }, | ||
65 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, | ||
66 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
67 | { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" }, | ||
68 | { "---", 0, 0, NULL }, | ||
69 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
70 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | ||
71 | { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, | ||
72 | { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, | ||
73 | { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, | ||
74 | { "---", 0, 0, NULL }, | ||
75 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
76 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | ||
77 | { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" }, | ||
78 | { "---", 0, 0, NULL }, | ||
79 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
80 | { "---", 0, 0, NULL }, | ||
81 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
82 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | ||
83 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | ||
84 | { "---", 0, 0, NULL }, | ||
85 | { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } | ||
86 | }; | ||
87 | #endif | ||
88 | |||
89 | #if defined (iPlatformAppleMobile) | ||
90 | /* Tablet menu. */ | ||
91 | static const iMenuItem tabletNavMenuItems_[] = { | ||
92 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
93 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | ||
94 | { "---", 0, 0, NULL }, | ||
95 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | ||
96 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
97 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | ||
98 | { "---", 0, 0, NULL }, | ||
99 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
100 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | ||
101 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
102 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
103 | { "---", 0, 0, NULL }, | ||
104 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
105 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | ||
106 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | ||
107 | }; | ||
108 | |||
109 | /* Phone menu. */ | ||
110 | static const iMenuItem phoneNavMenuItems_[] = { | ||
111 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
112 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | ||
113 | { "---", 0, 0, NULL }, | ||
114 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | ||
115 | { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
116 | { "---", 0, 0, NULL }, | ||
117 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
118 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
119 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
120 | { "---", 0, 0, NULL }, | ||
121 | { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
122 | }; | ||
123 | #endif /* AppleMobile */ | ||
124 | |||
125 | #if defined (iPlatformAppleMobile) | ||
126 | static const iMenuItem identityButtonMenuItems_[] = { | ||
127 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | ||
128 | { "---", 0, 0, NULL }, | ||
129 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | ||
130 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | ||
131 | { "---", 0, 0, NULL }, | ||
132 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, | ||
133 | }; | ||
134 | #else /* desktop */ | ||
135 | static const iMenuItem identityButtonMenuItems_[] = { | ||
136 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | ||
137 | { "---", 0, 0, NULL }, | ||
138 | # if !defined (iPlatformAppleDesktop) | ||
139 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | ||
140 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | ||
141 | { "---", 0, 0, NULL }, | ||
142 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | ||
143 | # else | ||
144 | { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" }, | ||
145 | { "---", 0, 0, NULL }, | ||
146 | { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" }, | ||
147 | # endif | ||
148 | }; | ||
149 | #endif | ||
150 | |||
151 | static const char *reloadCStr_ = reload_Icon; | ||
152 | static const char *pageMenuCStr_ = midEllipsis_Icon; | ||
153 | |||
154 | /* TODO: A preference for these, maybe? */ | ||
155 | static const char *stopSeqCStr_[] = { | ||
156 | /* Corners */ | ||
157 | uiTextCaution_ColorEscape "\U0000230c", | ||
158 | uiTextCaution_ColorEscape "\U0000230d", | ||
159 | uiTextCaution_ColorEscape "\U0000230f", | ||
160 | uiTextCaution_ColorEscape "\U0000230e", | ||
161 | #if 0 | ||
162 | /* Rotating arrow */ | ||
163 | uiTextCaution_ColorEscape "\U00002b62", | ||
164 | uiTextCaution_ColorEscape "\U00002b68", | ||
165 | uiTextCaution_ColorEscape "\U00002b63", | ||
166 | uiTextCaution_ColorEscape "\U00002b69", | ||
167 | uiTextCaution_ColorEscape "\U00002b60", | ||
168 | uiTextCaution_ColorEscape "\U00002b66", | ||
169 | uiTextCaution_ColorEscape "\U00002b61", | ||
170 | uiTextCaution_ColorEscape "\U00002b67", | ||
171 | #endif | ||
172 | #if 0 | ||
173 | /* Star */ | ||
174 | uiTextCaution_ColorEscape "\u2bcc", | ||
175 | uiTextCaution_ColorEscape "\u2bcd", | ||
176 | uiTextCaution_ColorEscape "\u2bcc", | ||
177 | uiTextCaution_ColorEscape "\u2bcd", | ||
178 | uiTextCaution_ColorEscape "\u2bcc", | ||
179 | uiTextCaution_ColorEscape "\u2bcd", | ||
180 | uiTextCaution_ColorEscape "\u2bce", | ||
181 | uiTextCaution_ColorEscape "\u2bcf", | ||
182 | uiTextCaution_ColorEscape "\u2bce", | ||
183 | uiTextCaution_ColorEscape "\u2bcf", | ||
184 | uiTextCaution_ColorEscape "\u2bce", | ||
185 | uiTextCaution_ColorEscape "\u2bcf", | ||
186 | #endif | ||
187 | #if 0 | ||
188 | /* Pulsing circle */ | ||
189 | uiTextCaution_ColorEscape "\U0001f785", | ||
190 | uiTextCaution_ColorEscape "\U0001f786", | ||
191 | uiTextCaution_ColorEscape "\U0001f787", | ||
192 | uiTextCaution_ColorEscape "\U0001f788", | ||
193 | uiTextCaution_ColorEscape "\U0001f789", | ||
194 | uiTextCaution_ColorEscape "\U0001f789", | ||
195 | uiTextCaution_ColorEscape "\U0001f788", | ||
196 | uiTextCaution_ColorEscape "\U0001f787", | ||
197 | uiTextCaution_ColorEscape "\U0001f786", | ||
198 | #endif | ||
199 | #if 0 | ||
200 | /* Dancing dots */ | ||
201 | uiTextCaution_ColorEscape "\U0001fb00", | ||
202 | uiTextCaution_ColorEscape "\U0001fb01", | ||
203 | uiTextCaution_ColorEscape "\U0001fb07", | ||
204 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
205 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
206 | uiTextCaution_ColorEscape "\U0001fb03", | ||
207 | uiTextCaution_ColorEscape "\U0001fb00", | ||
208 | uiTextCaution_ColorEscape "\U0001fb01", | ||
209 | uiTextCaution_ColorEscape "\U0001fb07", | ||
210 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
211 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
212 | uiTextCaution_ColorEscape "\U0001fb03", | ||
213 | |||
214 | uiTextCaution_ColorEscape "\U0001fb7d", | ||
215 | uiTextCaution_ColorEscape "\U0001fb7e", | ||
216 | uiTextCaution_ColorEscape "\U0001fb7f", | ||
217 | uiTextCaution_ColorEscape "\U0001fb7c", | ||
218 | uiTextCaution_ColorEscape "\U0001fb7d", | ||
219 | uiTextCaution_ColorEscape "\U0001fb7e", | ||
220 | uiTextCaution_ColorEscape "\U0001fb7f", | ||
221 | uiTextCaution_ColorEscape "\U0001fb7c", | ||
222 | |||
223 | uiTextCaution_ColorEscape "\U0001fb00", | ||
224 | uiTextCaution_ColorEscape "\U0001fb01", | ||
225 | uiTextCaution_ColorEscape "\U0001fb07", | ||
226 | uiTextCaution_ColorEscape "\U0001fb03", | ||
227 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
228 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
229 | uiTextCaution_ColorEscape "\U0001fb07", | ||
230 | uiTextCaution_ColorEscape "\U0001fb03", | ||
231 | #endif | ||
232 | }; | ||
233 | |||
234 | static const int loadAnimIntervalMs_ = 133; | ||
235 | static int loadAnimIndex_ = 0; | ||
236 | |||
237 | static iWidget * activeRoot_ = NULL; | ||
238 | |||
239 | void setCurrent_Root(iWidget *root) { | ||
240 | activeRoot_ = root; | ||
241 | } | ||
242 | |||
243 | iWidget *get_Root(void) { | ||
244 | return activeRoot_; | ||
245 | } | ||
246 | |||
247 | static iBool handleRootCommands_(iWidget *root, const char *cmd) { | ||
248 | iUnused(root); | ||
249 | if (equal_Command(cmd, "menu.open")) { | ||
250 | iWidget *button = pointer_Command(cmd); | ||
251 | iWidget *menu = findChild_Widget(button, "menu"); | ||
252 | iAssert(menu); | ||
253 | if (!isVisible_Widget(menu)) { | ||
254 | openMenu_Widget(menu, init_I2(0, button->rect.size.y)); | ||
255 | } | ||
256 | else { | ||
257 | closeMenu_Widget(menu); | ||
258 | } | ||
259 | return iTrue; | ||
260 | } | ||
261 | else if (equal_Command(cmd, "contextclick")) { | ||
262 | iBool showBarMenu = iFalse; | ||
263 | if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) { | ||
264 | const iWidget *sidebar = findWidget_App("sidebar"); | ||
265 | const iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
266 | const iWidget *buttons = pointer_Command(cmd); | ||
267 | if (hasParent_Widget(buttons, sidebar) || | ||
268 | hasParent_Widget(buttons, sidebar2)) { | ||
269 | showBarMenu = iTrue; | ||
270 | } | ||
271 | } | ||
272 | if (equal_Rangecc(range_Command(cmd, "id"), "navbar")) { | ||
273 | showBarMenu = iTrue; | ||
274 | } | ||
275 | if (showBarMenu) { | ||
276 | openMenu_Widget(findWidget_App("barmenu"), coord_Command(cmd)); | ||
277 | return iTrue; | ||
278 | } | ||
279 | return iFalse; | ||
280 | } | ||
281 | else if (equal_Command(cmd, "focus.set")) { | ||
282 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); | ||
283 | return iTrue; | ||
284 | } | ||
285 | else if (equal_Command(cmd, "window.focus.lost")) { | ||
286 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ | ||
287 | setFocus_Widget(NULL); | ||
288 | #endif | ||
289 | setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId); | ||
290 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId); | ||
291 | return iFalse; | ||
292 | } | ||
293 | else if (equal_Command(cmd, "window.focus.gained")) { | ||
294 | setTextColor_LabelWidget(findWidget_App("winbar.app"), uiTextAppTitle_ColorId); | ||
295 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId); | ||
296 | return iFalse; | ||
297 | } | ||
298 | else if (equal_Command(cmd, "window.setrect")) { | ||
299 | const int snap = argLabel_Command(cmd, "snap"); | ||
300 | if (snap) { | ||
301 | iWindow *window = get_Window(); | ||
302 | iInt2 coord = coord_Command(cmd); | ||
303 | iInt2 size = init_I2(argLabel_Command(cmd, "width"), | ||
304 | argLabel_Command(cmd, "height")); | ||
305 | SDL_SetWindowPosition(window->win, coord.x, coord.y); | ||
306 | SDL_SetWindowSize(window->win, size.x, size.y); | ||
307 | window->place.snap = snap; | ||
308 | return iTrue; | ||
309 | } | ||
310 | } | ||
311 | else if (equal_Command(cmd, "window.restore")) { | ||
312 | setSnap_Window(get_Window(), none_WindowSnap); | ||
313 | return iTrue; | ||
314 | } | ||
315 | else if (equal_Command(cmd, "window.minimize")) { | ||
316 | SDL_MinimizeWindow(get_Window()->win); | ||
317 | return iTrue; | ||
318 | } | ||
319 | else if (equal_Command(cmd, "window.close")) { | ||
320 | SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT }); | ||
321 | return iTrue; | ||
322 | } | ||
323 | else if (deviceType_App() == phone_AppDeviceType && equal_Command(cmd, "window.resized")) { | ||
324 | /* Place the sidebar next to or under doctabs depending on orientation. */ | ||
325 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | ||
326 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
327 | removeChild_Widget(parent_Widget(sidebar), sidebar); | ||
328 | setButtonFont_SidebarWidget(sidebar, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
329 | setButtonFont_SidebarWidget(sidebar2, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
330 | // setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | ||
331 | // isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId | ||
332 | // : uiBackgroundSidebar_ColorId); | ||
333 | setFlags_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | ||
334 | borderTop_WidgetFlag, | ||
335 | isPortrait_App()); | ||
336 | if (isLandscape_App()) { | ||
337 | addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos); | ||
338 | setWidth_SidebarWidget(sidebar, 73.0f); | ||
339 | if (isVisible_Widget(findWidget_App("sidebar2"))) { | ||
340 | postCommand_App("sidebar2.toggle"); | ||
341 | } | ||
342 | } | ||
343 | else { | ||
344 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); | ||
345 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | ||
346 | } | ||
347 | return iFalse; | ||
348 | } | ||
349 | else if (handleCommand_App(cmd)) { | ||
350 | return iTrue; | ||
351 | } | ||
352 | return iFalse; | ||
353 | } | ||
354 | |||
355 | static void updateNavBarIdentity_(iWidget *navBar) { | ||
356 | const iGmIdentity *ident = | ||
357 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); | ||
358 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); | ||
359 | iWidget *tool = findWidget_App("toolbar.ident"); | ||
360 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); | ||
361 | setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); | ||
362 | /* Update menu. */ | ||
363 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); | ||
364 | setTextCStr_LabelWidget( | ||
365 | idItem, | ||
366 | ident ? format_CStr(uiTextAction_ColorEscape "%s", | ||
367 | cstrCollect_String(subject_TlsCertificate(ident->cert))) | ||
368 | : "${menu.identity.notactive}"); | ||
369 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); | ||
370 | } | ||
371 | |||
372 | static void updateNavDirButtons_(iWidget *navBar) { | ||
373 | const iHistory *history = history_DocumentWidget(document_App()); | ||
374 | setFlags_Widget(findChild_Widget(navBar, "navbar.back"), disabled_WidgetFlag, | ||
375 | atOldest_History(history)); | ||
376 | setFlags_Widget(findChild_Widget(navBar, "navbar.forward"), disabled_WidgetFlag, | ||
377 | atLatest_History(history)); | ||
378 | setFlags_Widget(findWidget_App("toolbar.back"), disabled_WidgetFlag, | ||
379 | atOldest_History(history)); | ||
380 | setFlags_Widget(findWidget_App("toolbar.forward"), disabled_WidgetFlag, | ||
381 | atLatest_History(history)); | ||
382 | } | ||
383 | |||
384 | static const char *loadAnimationCStr_(void) { | ||
385 | return stopSeqCStr_[loadAnimIndex_ % iElemCount(stopSeqCStr_)]; | ||
386 | } | ||
387 | |||
388 | static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) { | ||
389 | iUnused(window); | ||
390 | loadAnimIndex_++; | ||
391 | postCommand_App("window.reload.update"); | ||
392 | return interval; | ||
393 | } | ||
394 | |||
395 | static void setReloadLabel_Window_(iWindow *d, iBool animating) { | ||
396 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; | ||
397 | iLabelWidget *label = findChild_Widget(d->root, "reload"); | ||
398 | updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : | ||
399 | (isMobile ? pageMenuCStr_ : reloadCStr_)); | ||
400 | if (isMobile) { | ||
401 | setCommand_LabelWidget(label, | ||
402 | collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); | ||
403 | } | ||
404 | } | ||
405 | |||
406 | static void checkLoadAnimation_Window_(iWindow *d) { | ||
407 | const iBool isOngoing = isRequestOngoing_DocumentWidget(document_App()); | ||
408 | if (isOngoing && !d->loadAnimTimer) { | ||
409 | d->loadAnimTimer = SDL_AddTimer(loadAnimIntervalMs_, updateReloadAnimation_Window_, d); | ||
410 | } | ||
411 | else if (!isOngoing && d->loadAnimTimer) { | ||
412 | SDL_RemoveTimer(d->loadAnimTimer); | ||
413 | d->loadAnimTimer = 0; | ||
414 | } | ||
415 | setReloadLabel_Window_(d, isOngoing); | ||
416 | } | ||
417 | |||
418 | void updatePadding_Root(iWidget *d) { | ||
419 | #if defined (iPlatformAppleMobile) | ||
420 | iWidget *toolBar = findChild_Widget(d, "toolbar"); | ||
421 | float left, top, right, bottom; | ||
422 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
423 | /* Respect the safe area insets. */ { | ||
424 | setPadding_Widget(findChild_Widget(d, "navdiv"), left, top, right, 0); | ||
425 | if (toolBar) { | ||
426 | setPadding_Widget(toolBar, left, 0, right, bottom); | ||
427 | } | ||
428 | } | ||
429 | if (toolBar) { | ||
430 | /* TODO: get this from toolBar height, but it's buggy for some reason */ | ||
431 | const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; | ||
432 | setPadding_Widget(findChild_Widget(d, "sidebar"), 0, 0, 0, sidebarBottomPad); | ||
433 | setPadding_Widget(findChild_Widget(d, "sidebar2"), 0, 0, 0, sidebarBottomPad); | ||
434 | /* TODO: There seems to be unrelated layout glitch in the sidebar where its children | ||
435 | are not arranged correctly until it's hidden and reshown. */ | ||
436 | } | ||
437 | /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ | ||
438 | #else | ||
439 | iUnused(d); | ||
440 | #endif | ||
441 | } | ||
442 | |||
443 | void dismissPortraitPhoneSidebars_Root(iWidget *root) { | ||
444 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { | ||
445 | iWidget *sidebar = findChild_Widget(root, "sidebar"); | ||
446 | iWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
447 | if (isVisible_Widget(sidebar)) { | ||
448 | postCommand_App("sidebar.toggle"); | ||
449 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
450 | } | ||
451 | if (isVisible_Widget(sidebar2)) { | ||
452 | postCommand_App("sidebar2.toggle"); | ||
453 | setVisualOffset_Widget(sidebar2, height_Widget(sidebar2), 250, easeIn_AnimFlag); | ||
454 | } | ||
455 | // setFlags_Widget(findWidget_App("toolbar.ident"), noBackground_WidgetFlag, iTrue); | ||
456 | // setFlags_Widget(findWidget_App("toolbar.view"), noBackground_WidgetFlag, iTrue); | ||
457 | } | ||
458 | } | ||
459 | |||
460 | static iBool willPerformSearchQuery_(const iString *userInput) { | ||
461 | const iString *clean = collect_String(trimmed_String(userInput)); | ||
462 | if (isEmpty_String(clean)) { | ||
463 | return iFalse; | ||
464 | } | ||
465 | return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); | ||
466 | } | ||
467 | |||
468 | static void showSearchQueryIndicator_(iBool show) { | ||
469 | iWidget *indicator = findWidget_App("input.indicator.search"); | ||
470 | showCollapsed_Widget(indicator, show); | ||
471 | iAssert(isInstance_Object(parent_Widget(parent_Widget(indicator)), &Class_InputWidget)); | ||
472 | iInputWidget *url = (iInputWidget *) parent_Widget(parent_Widget(indicator)); | ||
473 | setContentPadding_InputWidget(url, -1, contentPadding_InputWidget(url).left + | ||
474 | (show ? width_Widget(indicator) : 0)); | ||
475 | } | ||
476 | |||
477 | static int navBarAvailableSpace_(iWidget *navBar) { | ||
478 | int avail = width_Rect(innerBounds_Widget(navBar)); | ||
479 | iConstForEach(ObjectList, i, children_Widget(navBar)) { | ||
480 | const iWidget *child = i.object; | ||
481 | if (~flags_Widget(child) & expand_WidgetFlag && | ||
482 | isVisible_Widget(child) && | ||
483 | cmp_String(id_Widget(child), "url")) { | ||
484 | avail -= width_Widget(child); | ||
485 | } | ||
486 | } | ||
487 | return avail; | ||
488 | } | ||
489 | |||
490 | iBool isNarrow_Window(const iWindow *d) { | ||
491 | return width_Rect(safeRootRect_Window(d)) / gap_UI < 140; | ||
492 | } | ||
493 | |||
494 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | ||
495 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) { | ||
496 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | ||
497 | const iBool isNarrow = !isPhone && isNarrow_Window(get_Window()); | ||
498 | /* Adjust navbar padding. */ { | ||
499 | int hPad = isPhone && isPortrait_App() ? 0 : (isPhone || isNarrow) ? gap_UI / 2 | ||
500 | : gap_UI * 3 / 2; | ||
501 | int vPad = gap_UI * 3 / 2; | ||
502 | int topPad = !findWidget_App("winbar") ? gap_UI / 2 : 0; | ||
503 | setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, vPad / 2); | ||
504 | } | ||
505 | /* Button sizing. */ | ||
506 | if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) { | ||
507 | setFlags_Widget(navBar, tight_WidgetFlag, isNarrow); | ||
508 | iForEach(ObjectList, i, navBar->children) { | ||
509 | iWidget *child = as_Widget(i.object); | ||
510 | setFlags_Widget(child, tight_WidgetFlag, isNarrow); | ||
511 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
512 | iLabelWidget *label = i.object; | ||
513 | updateSize_LabelWidget(label); | ||
514 | } | ||
515 | } | ||
516 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ | ||
517 | /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ | ||
518 | const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); | ||
519 | setContentPadding_InputWidget(findChild_Widget(navBar, "url"), | ||
520 | embedButtonWidth * 0.75f, | ||
521 | embedButtonWidth * 0.75f); | ||
522 | } | ||
523 | if (isPhone) { | ||
524 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", | ||
525 | "navbar.ident", "navbar.home", "navbar.menu" }; | ||
526 | iWidget *toolBar = findWidget_App("toolbar"); | ||
527 | setVisualOffset_Widget(toolBar, 0, 0, 0); | ||
528 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); | ||
529 | iForIndices(i, buttons) { | ||
530 | iLabelWidget *btn = findChild_Widget(navBar, buttons[i]); | ||
531 | setFlags_Widget(as_Widget(btn), hidden_WidgetFlag, isPortrait_App()); | ||
532 | if (isLandscape_App()) { | ||
533 | /* Collapsing sets size to zero and the label doesn't know when to update | ||
534 | its own size automatically. */ | ||
535 | updateSize_LabelWidget(btn); | ||
536 | } | ||
537 | } | ||
538 | arrange_Widget(get_Window()->root); | ||
539 | } | ||
540 | /* Resize the URL input field. */ { | ||
541 | iWidget *urlBar = findChild_Widget(navBar, "url"); | ||
542 | urlBar->rect.size.x = iMini(navBarAvailableSpace_(navBar), 167 * gap_UI); | ||
543 | arrange_Widget(navBar); | ||
544 | } | ||
545 | refresh_Widget(navBar); | ||
546 | postCommand_Widget(navBar, "layout.changed id:navbar"); | ||
547 | return iFalse; | ||
548 | } | ||
549 | else if (equal_Command(cmd, "window.reload.update")) { | ||
550 | checkLoadAnimation_Window_(get_Window()); | ||
551 | return iTrue; | ||
552 | } | ||
553 | else if (equal_Command(cmd, "navigate.focus")) { | ||
554 | iWidget *url = findChild_Widget(navBar, "url"); | ||
555 | if (focus_Widget() != url) { | ||
556 | setFocus_Widget(findChild_Widget(navBar, "url")); | ||
557 | } | ||
558 | else { | ||
559 | selectAll_InputWidget((iInputWidget *) url); | ||
560 | } | ||
561 | return iTrue; | ||
562 | } | ||
563 | else if (equal_Command(cmd, "input.edited")) { | ||
564 | iAnyObject * url = findChild_Widget(navBar, "url"); | ||
565 | const iString *text = text_InputWidget(url); | ||
566 | const iBool show = willPerformSearchQuery_(text); | ||
567 | showSearchQueryIndicator_(show); | ||
568 | if (pointer_Command(cmd) == url) { | ||
569 | submit_LookupWidget(findWidget_App("lookup"), text); | ||
570 | return iTrue; | ||
571 | } | ||
572 | } | ||
573 | else if (startsWith_CStr(cmd, "input.ended id:url ")) { | ||
574 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
575 | showSearchQueryIndicator_(iFalse); | ||
576 | if (isEmpty_String(text_InputWidget(url))) { | ||
577 | /* User entered nothing; restore the current URL. */ | ||
578 | setText_InputWidget(url, url_DocumentWidget(document_App())); | ||
579 | return iTrue; | ||
580 | } | ||
581 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && | ||
582 | !isFocused_Widget(findWidget_App("lookup"))) { | ||
583 | iString *newUrl = copy_String(text_InputWidget(url)); | ||
584 | trim_String(newUrl); | ||
585 | if (willPerformSearchQuery_(newUrl)) { | ||
586 | postCommandf_App("open url:%s", cstr_String(searchQueryUrl_App(newUrl))); | ||
587 | } | ||
588 | else { | ||
589 | postCommandf_App( | ||
590 | "open url:%s", | ||
591 | cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl)))); | ||
592 | } | ||
593 | return iTrue; | ||
594 | } | ||
595 | } | ||
596 | else if (startsWith_CStr(cmd, "document.")) { | ||
597 | /* React to the current document only. */ | ||
598 | if (document_Command(cmd) == document_App()) { | ||
599 | if (equal_Command(cmd, "document.changed")) { | ||
600 | iInputWidget *url = findWidget_App("url"); | ||
601 | const iString *urlStr = collect_String(suffix_Command(cmd, "url")); | ||
602 | trimCache_App(); | ||
603 | visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ | ||
604 | postCommand_App("visited.changed"); /* sidebar will update */ | ||
605 | setText_InputWidget(url, urlStr); | ||
606 | checkLoadAnimation_Window_(get_Window()); | ||
607 | dismissPortraitPhoneSidebars_Root(get_Root()); | ||
608 | updateNavBarIdentity_(navBar); | ||
609 | updateNavDirButtons_(navBar); | ||
610 | /* Icon updates should be limited to automatically chosen icons if the user | ||
611 | is allowed to pick their own in the future. */ | ||
612 | if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr, | ||
613 | siteIcon_GmDocument(document_DocumentWidget(document_App())))) { | ||
614 | postCommand_App("bookmarks.changed"); | ||
615 | } | ||
616 | return iFalse; | ||
617 | } | ||
618 | else if (equal_Command(cmd, "document.request.cancelled")) { | ||
619 | checkLoadAnimation_Window_(get_Window()); | ||
620 | return iFalse; | ||
621 | } | ||
622 | else if (equal_Command(cmd, "document.request.started")) { | ||
623 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
624 | setTextCStr_InputWidget(url, suffixPtr_Command(cmd, "url")); | ||
625 | checkLoadAnimation_Window_(get_Window()); | ||
626 | dismissPortraitPhoneSidebars_Root(get_Root()); | ||
627 | return iFalse; | ||
628 | } | ||
629 | } | ||
630 | } | ||
631 | else if (equal_Command(cmd, "tabs.changed")) { | ||
632 | /* Update navbar according to the current tab. */ | ||
633 | iDocumentWidget *doc = document_App(); | ||
634 | if (doc) { | ||
635 | setText_InputWidget(findChild_Widget(navBar, "url"), url_DocumentWidget(doc)); | ||
636 | checkLoadAnimation_Window_(get_Window()); | ||
637 | updateNavBarIdentity_(navBar); | ||
638 | } | ||
639 | setFocus_Widget(NULL); | ||
640 | } | ||
641 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | ||
642 | iWidget *widget = pointer_Command(cmd); | ||
643 | iWidget *menu = findWidget_App("doctabs.menu"); | ||
644 | if (isTabButton_Widget(widget)) { | ||
645 | if (!isVisible_Widget(menu)) { | ||
646 | iWidget *tabs = findWidget_App("doctabs"); | ||
647 | iWidget *page = tabPage_Widget(tabs, childIndex_Widget(widget->parent, widget)); | ||
648 | if (argLabel_Command(cmd, "button") == SDL_BUTTON_MIDDLE) { | ||
649 | postCommandf_App("tabs.close id:%s", cstr_String(id_Widget(page))); | ||
650 | return iTrue; | ||
651 | } | ||
652 | showTabPage_Widget(tabs, page); | ||
653 | openMenu_Widget(menu, coord_Command(cmd)); | ||
654 | } | ||
655 | } | ||
656 | } | ||
657 | else if (equal_Command(cmd, "navigate.reload")) { | ||
658 | iDocumentWidget *doc = document_Command(cmd); | ||
659 | if (isRequestOngoing_DocumentWidget(doc)) { | ||
660 | postCommand_App("document.stop"); | ||
661 | } | ||
662 | else { | ||
663 | postCommand_App("document.reload"); | ||
664 | } | ||
665 | return iTrue; | ||
666 | } | ||
667 | return iFalse; | ||
668 | } | ||
669 | |||
670 | static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | ||
671 | if (equal_Command(cmd, "input.ended") && | ||
672 | equal_Rangecc(range_Command(cmd, "id"), "find.input")) { | ||
673 | iInputWidget *input = findChild_Widget(searchBar, "find.input"); | ||
674 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && isVisible_Widget(input)) { | ||
675 | postCommand_App("find.next"); | ||
676 | /* Keep focus when pressing Enter. */ | ||
677 | if (!isEmpty_String(text_InputWidget(input))) { | ||
678 | postCommand_App("focus.set id:find.input"); | ||
679 | } | ||
680 | } | ||
681 | else { | ||
682 | postCommand_App("find.clearmark"); | ||
683 | } | ||
684 | return iTrue; | ||
685 | } | ||
686 | else if (equal_Command(cmd, "focus.gained")) { | ||
687 | if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { | ||
688 | if (!isVisible_Widget(searchBar)) { | ||
689 | setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); | ||
690 | arrange_Widget(get_Window()->root); | ||
691 | postRefresh_App(); | ||
692 | } | ||
693 | } | ||
694 | } | ||
695 | else if (equal_Command(cmd, "find.close")) { | ||
696 | if (isVisible_Widget(searchBar)) { | ||
697 | setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | ||
698 | arrange_Widget(searchBar->parent); | ||
699 | if (isFocused_Widget(findChild_Widget(searchBar, "find.input"))) { | ||
700 | setFocus_Widget(NULL); | ||
701 | } | ||
702 | refresh_Widget(searchBar->parent); | ||
703 | } | ||
704 | return iTrue; | ||
705 | } | ||
706 | return iFalse; | ||
707 | } | ||
708 | |||
709 | #if defined (iPlatformAppleMobile) | ||
710 | static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) { | ||
711 | if (isVisible_Widget(sidebar)) { | ||
712 | postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar))); | ||
713 | if (toolButtonId) { | ||
714 | // setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue); | ||
715 | } | ||
716 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
717 | } | ||
718 | } | ||
719 | |||
720 | static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | ||
721 | if (equalWidget_Command(cmd, toolBar, "mouse.clicked") && arg_Command(cmd) && | ||
722 | argLabel_Command(cmd, "button") == SDL_BUTTON_RIGHT) { | ||
723 | iWidget *menu = findChild_Widget(toolBar, "toolbar.menu"); | ||
724 | arrange_Widget(menu); | ||
725 | openMenu_Widget(menu, init_I2(0, -height_Widget(menu))); | ||
726 | return iTrue; | ||
727 | } | ||
728 | else if (equal_Command(cmd, "toolbar.showview")) { | ||
729 | /* TODO: Clean this up. */ | ||
730 | iWidget *sidebar = findWidget_App("sidebar"); | ||
731 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
732 | dismissSidebar_(sidebar2, "toolbar.ident"); | ||
733 | const iBool isVisible = isVisible_Widget(sidebar); | ||
734 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.view"), noBackground_WidgetFlag, | ||
735 | // isVisible); | ||
736 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
737 | const int viewHeight = rootSize_Window(get_Window()).y; | ||
738 | if (arg_Command(cmd) >= 0) { | ||
739 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); | ||
740 | if (!isVisible) { | ||
741 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
742 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
743 | } | ||
744 | } | ||
745 | else { | ||
746 | postCommandf_App("sidebar.toggle"); | ||
747 | if (isVisible) { | ||
748 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
749 | } | ||
750 | else { | ||
751 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
752 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
753 | } | ||
754 | } | ||
755 | return iTrue; | ||
756 | } | ||
757 | else if (equal_Command(cmd, "toolbar.showident")) { | ||
758 | /* TODO: Clean this up. */ | ||
759 | iWidget *sidebar = findWidget_App("sidebar"); | ||
760 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
761 | dismissSidebar_(sidebar, "toolbar.view"); | ||
762 | const iBool isVisible = isVisible_Widget(sidebar2); | ||
763 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, | ||
764 | // isVisible); | ||
765 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
766 | const int viewHeight = rootSize_Window(get_Window()).y; | ||
767 | if (isVisible) { | ||
768 | dismissSidebar_(sidebar2, NULL); | ||
769 | } | ||
770 | else { | ||
771 | postCommand_App("sidebar2.mode arg:3 show:1"); | ||
772 | int offset = height_Widget(sidebar2); | ||
773 | if (offset == 0) offset = rootSize_Window(get_Window()).y; | ||
774 | setVisualOffset_Widget(sidebar2, offset, 0, 0); | ||
775 | setVisualOffset_Widget(sidebar2, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
776 | } | ||
777 | return iTrue; | ||
778 | } | ||
779 | else if (equal_Command(cmd, "sidebar.mode.changed")) { | ||
780 | iLabelWidget *viewTool = findChild_Widget(toolBar, "toolbar.view"); | ||
781 | updateTextCStr_LabelWidget(viewTool, icon_SidebarMode(arg_Command(cmd))); | ||
782 | return iFalse; | ||
783 | } | ||
784 | return iFalse; | ||
785 | } | ||
786 | #endif /* defined (iPlatformAppleMobile) */ | ||
787 | |||
788 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { | ||
789 | iLabelWidget *lab = newIcon_LabelWidget(text, 0, 0, cmd); | ||
790 | setFont_LabelWidget(lab, uiLabelLarge_FontId); | ||
791 | return lab; | ||
792 | } | ||
793 | |||
794 | static int appIconSize_(void) { | ||
795 | return lineHeight_Text(uiContent_FontId); | ||
796 | } | ||
797 | |||
798 | void updateMetrics_Root(iWidget *d) { | ||
799 | /* Custom frame. */ | ||
800 | iWidget *winBar = findChild_Widget(d, "winbar"); | ||
801 | if (winBar) { | ||
802 | iWidget *appIcon = findChild_Widget(winBar, "winbar.icon"); | ||
803 | iWidget *appTitle = findChild_Widget(winBar, "winbar.title"); | ||
804 | iWidget *appMin = findChild_Widget(winBar, "winbar.min"); | ||
805 | iWidget *appMax = findChild_Widget(winBar, "winbar.max"); | ||
806 | iWidget *appClose = findChild_Widget(winBar, "winbar.close"); | ||
807 | setPadding_Widget(winBar, 0, gap_UI / 3, 0, 0); | ||
808 | setFixedSize_Widget(appMin, init_I2(gap_UI * 11.5f, height_Widget(appTitle))); | ||
809 | setFixedSize_Widget(appMax, appMin->rect.size); | ||
810 | setFixedSize_Widget(appClose, appMin->rect.size); | ||
811 | setFixedSize_Widget(appIcon, init_I2(appIconSize_(), appMin->rect.size.y)); | ||
812 | } | ||
813 | iWidget *navBar = findChild_Widget(d, "navbar"); | ||
814 | iWidget *lock = findChild_Widget(navBar, "navbar.lock"); | ||
815 | iWidget *url = findChild_Widget(d, "url"); | ||
816 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | ||
817 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | ||
818 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); | ||
819 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ | ||
820 | updateSize_LabelWidget((iLabelWidget *) lock); | ||
821 | setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); | ||
822 | setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | ||
823 | width_Widget(lock) * 0.75); | ||
824 | rightEmbed->rect.pos.y = gap_UI; | ||
825 | updatePadding_Root(d); | ||
826 | arrange_Widget(d); | ||
827 | postRefresh_App(); | ||
828 | } | ||
829 | |||
830 | iWidget *createUserInterface_Root(void) { | ||
831 | iWidget *root = new_Widget(); | ||
832 | setId_Widget(root, "root"); | ||
833 | /* Children of root cover the entire window. */ | ||
834 | setFlags_Widget( | ||
835 | root, resizeChildren_WidgetFlag | fixedSize_WidgetFlag | focusRoot_WidgetFlag, iTrue); | ||
836 | setCommandHandler_Widget(root, handleRootCommands_); | ||
837 | |||
838 | iWidget *div = makeVDiv_Widget(); | ||
839 | setId_Widget(div, "navdiv"); | ||
840 | addChild_Widget(root, iClob(div)); | ||
841 | |||
842 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | ||
843 | /* Window title bar. */ | ||
844 | if (prefs_App()->customFrame) { | ||
845 | setPadding1_Widget(div, 1); | ||
846 | iWidget *winBar = new_Widget(); | ||
847 | setId_Widget(winBar, "winbar"); | ||
848 | setFlags_Widget(winBar, | ||
849 | arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag | | ||
850 | arrangeHorizontal_WidgetFlag | collapse_WidgetFlag, | ||
851 | iTrue); | ||
852 | iWidget *appIcon; | ||
853 | setId_Widget( | ||
854 | addChild_Widget(winBar, iClob(appIcon = makePadding_Widget(0))), "winbar.icon"); | ||
855 | iLabelWidget *appButton = | ||
856 | addChildFlags_Widget(winBar, | ||
857 | iClob(new_LabelWidget("Lagrange", NULL)), | ||
858 | fixedHeight_WidgetFlag | frameless_WidgetFlag); | ||
859 | setTextColor_LabelWidget(appButton, uiTextAppTitle_ColorId); | ||
860 | setId_Widget(as_Widget(appButton), "winbar.app"); | ||
861 | iLabelWidget *appTitle; | ||
862 | setFont_LabelWidget(appButton, uiContentBold_FontId); | ||
863 | setId_Widget(addChildFlags_Widget(winBar, | ||
864 | iClob(appTitle = new_LabelWidget("", NULL)), | ||
865 | expand_WidgetFlag | fixedHeight_WidgetFlag | | ||
866 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | ||
867 | "winbar.title"); | ||
868 | setTextColor_LabelWidget(appTitle, uiTextStrong_ColorId); | ||
869 | iLabelWidget *appMin, *appMax, *appClose; | ||
870 | setId_Widget(addChildFlags_Widget( | ||
871 | winBar, | ||
872 | iClob(appMin = newLargeIcon_LabelWidget("\u2013", "window.minimize")), | ||
873 | frameless_WidgetFlag), | ||
874 | "winbar.min"); | ||
875 | addChildFlags_Widget( | ||
876 | winBar, | ||
877 | iClob(appMax = newLargeIcon_LabelWidget("\u25a1", "window.maximize toggle:1")), | ||
878 | frameless_WidgetFlag); | ||
879 | setId_Widget(as_Widget(appMax), "winbar.max"); | ||
880 | addChildFlags_Widget(winBar, | ||
881 | iClob(appClose = newLargeIcon_LabelWidget(close_Icon, "window.close")), | ||
882 | frameless_WidgetFlag); | ||
883 | setId_Widget(as_Widget(appClose), "winbar.close"); | ||
884 | setFont_LabelWidget(appClose, uiContent_FontId); | ||
885 | addChild_Widget(div, iClob(winBar)); | ||
886 | setBackgroundColor_Widget(winBar, uiBackground_ColorId); | ||
887 | } | ||
888 | #endif | ||
889 | /* Navigation bar. */ { | ||
890 | iWidget *navBar = new_Widget(); | ||
891 | setId_Widget(navBar, "navbar"); | ||
892 | setFlags_Widget(navBar, | ||
893 | hittable_WidgetFlag | /* context menu */ | ||
894 | arrangeHeight_WidgetFlag | | ||
895 | resizeChildren_WidgetFlag | | ||
896 | arrangeHorizontal_WidgetFlag | | ||
897 | drawBackgroundToHorizontalSafeArea_WidgetFlag | | ||
898 | drawBackgroundToVerticalSafeArea_WidgetFlag, | ||
899 | iTrue); | ||
900 | addChild_Widget(div, iClob(navBar)); | ||
901 | setBackgroundColor_Widget(navBar, uiBackground_ColorId); | ||
902 | setCommandHandler_Widget(navBar, handleNavBarCommands_); | ||
903 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.back"); | ||
904 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.forward"); | ||
905 | /* Mobile devices have a button for easier access to the left sidebar. */ | ||
906 | if (deviceType_App() != desktop_AppDeviceType) { | ||
907 | setId_Widget(addChildFlags_Widget( | ||
908 | navBar, | ||
909 | iClob(newIcon_LabelWidget(leftHalf_Icon, 0, 0, "sidebar.toggle")), | ||
910 | collapse_WidgetFlag), | ||
911 | "navbar.sidebar"); | ||
912 | } | ||
913 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | ||
914 | iLabelWidget *idMenu = makeMenuButton_LabelWidget( | ||
915 | "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_)); | ||
916 | setAlignVisually_LabelWidget(idMenu, iTrue); | ||
917 | setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident"); | ||
918 | iInputWidget *url; | ||
919 | /* URL input field. */ { | ||
920 | url = new_InputWidget(0); | ||
921 | setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue); | ||
922 | setSelectAllOnFocus_InputWidget(url, iTrue); | ||
923 | setId_Widget(as_Widget(url), "url"); | ||
924 | setUrlContent_InputWidget(url, iTrue); | ||
925 | setNotifyEdits_InputWidget(url, iTrue); | ||
926 | setTextCStr_InputWidget(url, "gemini://"); | ||
927 | addChildFlags_Widget(navBar, iClob(url), 0); | ||
928 | const int64_t embedFlags = | ||
929 | noBackground_WidgetFlag | frameless_WidgetFlag | unpadded_WidgetFlag | | ||
930 | (deviceType_App() == desktop_AppDeviceType ? tight_WidgetFlag : 0); | ||
931 | /* Page information/certificate warning. */ { | ||
932 | iLabelWidget *lock = addChildFlags_Widget( | ||
933 | as_Widget(url), | ||
934 | iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")), | ||
935 | embedFlags | moveToParentLeftEdge_WidgetFlag); | ||
936 | setId_Widget(as_Widget(lock), "navbar.lock"); | ||
937 | setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); | ||
938 | updateTextCStr_LabelWidget(lock, "\U0001f512"); | ||
939 | } | ||
940 | iWidget *rightEmbed = new_Widget(); | ||
941 | setId_Widget(rightEmbed, "url.rightembed"); | ||
942 | addChildFlags_Widget(as_Widget(url), | ||
943 | iClob(rightEmbed), | ||
944 | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag | | ||
945 | resizeHeightOfChildren_WidgetFlag | | ||
946 | moveToParentRightEdge_WidgetFlag); | ||
947 | /* Feeds refresh indicator is inside the input field. */ { | ||
948 | iLabelWidget *queryInd = | ||
949 | new_LabelWidget(uiTextAction_ColorEscape "\u21d2 ${status.query}", NULL); | ||
950 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); | ||
951 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); | ||
952 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); | ||
953 | setAlignVisually_LabelWidget(queryInd, iTrue); | ||
954 | addChildFlags_Widget(rightEmbed, | ||
955 | iClob(queryInd), | ||
956 | collapse_WidgetFlag | hidden_WidgetFlag); | ||
957 | } | ||
958 | /* Feeds refresh indicator is inside the input field. */ { | ||
959 | iLabelWidget *fprog = new_LabelWidget(uiTextCaution_ColorEscape | ||
960 | "\u2605 ${status.feeds}", NULL); | ||
961 | setId_Widget(as_Widget(fprog), "feeds.progress"); | ||
962 | setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); | ||
963 | setAlignVisually_LabelWidget(fprog, iTrue); | ||
964 | addChildFlags_Widget(rightEmbed, | ||
965 | iClob(fprog), | ||
966 | collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); | ||
967 | } | ||
968 | /* Download progress indicator is also inside the input field, but hidden normally. */ { | ||
969 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); | ||
970 | setId_Widget(as_Widget(progress), "document.progress"); | ||
971 | setBackgroundColor_Widget(as_Widget(progress), uiBackground_ColorId); | ||
972 | setAlignVisually_LabelWidget(progress, iTrue); | ||
973 | addChildFlags_Widget( | ||
974 | rightEmbed, iClob(progress), collapse_WidgetFlag); | ||
975 | } | ||
976 | /* Reload button. */ { | ||
977 | iLabelWidget *reload; | ||
978 | if (deviceType_App() == desktop_AppDeviceType) { | ||
979 | reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | ||
980 | } | ||
981 | else { | ||
982 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
983 | reload = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
984 | (iMenuItem[]){ | ||
985 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | ||
986 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
987 | { "---", 0, 0, NULL }, | ||
988 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
989 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
990 | { "---", 0, 0, NULL }, | ||
991 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
992 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
993 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
994 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
995 | { "---", 0, 0, NULL }, | ||
996 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
997 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
998 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
999 | 14); | ||
1000 | setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId); | ||
1001 | setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue); | ||
1002 | } | ||
1003 | setId_Widget(as_Widget(reload), "reload"); | ||
1004 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | ||
1005 | updateSize_LabelWidget(reload); | ||
1006 | } | ||
1007 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | ||
1008 | } | ||
1009 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1010 | /* On mobile, the Identities button is on the right side of the URL bar. */ | ||
1011 | iWidget *ident = removeChild_Widget(navBar, findChild_Widget(navBar, "navbar.ident")); | ||
1012 | addChild_Widget(navBar, iClob(ident)); | ||
1013 | } | ||
1014 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | ||
1015 | setId_Widget(addChildFlags_Widget(navBar, | ||
1016 | iClob(newIcon_LabelWidget( | ||
1017 | home_Icon, SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")), | ||
1018 | collapse_WidgetFlag), | ||
1019 | "navbar.home"); | ||
1020 | #if defined (iPlatformMobile) | ||
1021 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); | ||
1022 | #endif | ||
1023 | #if !defined (iHaveNativeMenus) | ||
1024 | # if defined (iPlatformAppleMobile) | ||
1025 | iLabelWidget *navMenu = | ||
1026 | makeMenuButton_LabelWidget("\U0001d362", isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, | ||
1027 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); | ||
1028 | # else | ||
1029 | iLabelWidget *navMenu = | ||
1030 | makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_)); | ||
1031 | # endif | ||
1032 | setAlignVisually_LabelWidget(navMenu, iTrue); | ||
1033 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); | ||
1034 | #else | ||
1035 | insertMacMenus_(); | ||
1036 | #endif | ||
1037 | } | ||
1038 | /* Tab bar. */ { | ||
1039 | iWidget *mainStack = new_Widget(); | ||
1040 | setId_Widget(mainStack, "stack"); | ||
1041 | addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag | | ||
1042 | unhittable_WidgetFlag); | ||
1043 | iWidget *tabBar = makeTabs_Widget(mainStack); | ||
1044 | setId_Widget(tabBar, "doctabs"); | ||
1045 | setBackgroundColor_Widget(tabBar, uiBackground_ColorId); | ||
1046 | appendTabPage_Widget(tabBar, iClob(new_DocumentWidget()), "Document", 0, 0); | ||
1047 | iWidget *buttons = findChild_Widget(tabBar, "tabs.buttons"); | ||
1048 | setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag | | ||
1049 | drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue); | ||
1050 | if (deviceType_App() == phone_AppDeviceType) { | ||
1051 | setBackgroundColor_Widget(buttons, uiBackground_ColorId); | ||
1052 | } | ||
1053 | setId_Widget( | ||
1054 | addChild_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new"))), | ||
1055 | "newtab"); | ||
1056 | } | ||
1057 | /* Sidebars. */ { | ||
1058 | iWidget *content = findChild_Widget(root, "tabs.content"); | ||
1059 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SideBarSide); | ||
1060 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | ||
1061 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SideBarSide); | ||
1062 | if (deviceType_App() != phone_AppDeviceType) { | ||
1063 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); | ||
1064 | } | ||
1065 | else { | ||
1066 | /* The identities sidebar is always in the main area. */ | ||
1067 | addChild_Widget(findChild_Widget(root, "stack"), iClob(sidebar2)); | ||
1068 | setFlags_Widget(as_Widget(sidebar2), hidden_WidgetFlag, iTrue); | ||
1069 | } | ||
1070 | } | ||
1071 | /* Lookup results. */ { | ||
1072 | iLookupWidget *lookup = new_LookupWidget(); | ||
1073 | addChildFlags_Widget(div, iClob(lookup), fixedPosition_WidgetFlag | hidden_WidgetFlag); | ||
1074 | } | ||
1075 | /* Search bar. */ { | ||
1076 | iWidget *searchBar = new_Widget(); | ||
1077 | setId_Widget(searchBar, "search"); | ||
1078 | setFlags_Widget(searchBar, | ||
1079 | hidden_WidgetFlag | disabled_WidgetFlag | collapse_WidgetFlag | | ||
1080 | arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag | | ||
1081 | arrangeHorizontal_WidgetFlag, | ||
1082 | iTrue); | ||
1083 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1084 | addChild_Widget(div, iClob(searchBar)); | ||
1085 | } | ||
1086 | else { | ||
1087 | /* The search bar appears at the top on mobile, because there is a virtual keyboard | ||
1088 | covering the bottom. */ | ||
1089 | insertChildAfter_Widget(div, iClob(searchBar), | ||
1090 | childIndex_Widget(div, findChild_Widget(div, "navbar"))); | ||
1091 | } | ||
1092 | setBackgroundColor_Widget(searchBar, uiBackground_ColorId); | ||
1093 | setCommandHandler_Widget(searchBar, handleSearchBarCommands_); | ||
1094 | addChildFlags_Widget( | ||
1095 | searchBar, iClob(new_LabelWidget(magnifyingGlass_Icon, NULL)), frameless_WidgetFlag); | ||
1096 | iInputWidget *input = new_InputWidget(0); | ||
1097 | setHint_InputWidget(input, "${hint.findtext}"); | ||
1098 | setSelectAllOnFocus_InputWidget(input, iTrue); | ||
1099 | setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ | ||
1100 | setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), | ||
1101 | "find.input"); | ||
1102 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); | ||
1103 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); | ||
1104 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(close_Icon, SDLK_ESCAPE, 0, "find.close"))); | ||
1105 | } | ||
1106 | #if defined (iPlatformAppleMobile) | ||
1107 | /* Bottom toolbar. */ | ||
1108 | if (isPhone_iOS()) { | ||
1109 | iWidget *toolBar = new_Widget(); | ||
1110 | addChild_Widget(root, iClob(toolBar)); | ||
1111 | setId_Widget(toolBar, "toolbar"); | ||
1112 | setCommandHandler_Widget(toolBar, handleToolBarCommands_); | ||
1113 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | | ||
1114 | parentCannotResizeHeight_WidgetFlag | | ||
1115 | resizeWidthOfChildren_WidgetFlag | | ||
1116 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | ||
1117 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); | ||
1118 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1119 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), | ||
1120 | frameless_WidgetFlag), | ||
1121 | "toolbar.back"); | ||
1122 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1123 | iClob(newLargeIcon_LabelWidget("\U0001f872", "navigate.forward")), | ||
1124 | frameless_WidgetFlag), | ||
1125 | "toolbar.forward"); | ||
1126 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1127 | iClob(newLargeIcon_LabelWidget("\U0001f464", "toolbar.showident")), | ||
1128 | frameless_WidgetFlag), | ||
1129 | "toolbar.ident"); | ||
1130 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1131 | iClob(newLargeIcon_LabelWidget("\U0001f588", "toolbar.showview arg:-1")), | ||
1132 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | ||
1133 | "toolbar.view"); | ||
1134 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, | ||
1135 | iElemCount(phoneNavMenuItems_)); | ||
1136 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); | ||
1137 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); | ||
1138 | addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag); | ||
1139 | iForEach(ObjectList, i, children_Widget(toolBar)) { | ||
1140 | iLabelWidget *btn = i.object; | ||
1141 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | ||
1142 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); | ||
1143 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); | ||
1144 | } | ||
1145 | const iMenuItem items[] = { | ||
1146 | { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | ||
1147 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | ||
1148 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, | ||
1149 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, | ||
1150 | }; | ||
1151 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), | ||
1152 | items, iElemCount(items)); | ||
1153 | setId_Widget(menu, "toolbar.menu"); /* view menu */ | ||
1154 | } | ||
1155 | #endif | ||
1156 | updatePadding_Root(root); | ||
1157 | /* Global context menus. */ { | ||
1158 | iWidget *tabsMenu = makeMenu_Widget( | ||
1159 | root, | ||
1160 | (iMenuItem[]){ | ||
1161 | { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" }, | ||
1162 | { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" }, | ||
1163 | { "---", 0, 0, NULL }, | ||
1164 | { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, | ||
1165 | { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, | ||
1166 | { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, | ||
1167 | }, | ||
1168 | 6); | ||
1169 | iWidget *barMenu = | ||
1170 | makeMenu_Widget(root, | ||
1171 | (iMenuItem[]){ | ||
1172 | { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, | ||
1173 | { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, | ||
1174 | }, | ||
1175 | deviceType_App() == phone_AppDeviceType ? 1 : 2); | ||
1176 | iWidget *clipMenu = makeMenu_Widget(root, | ||
1177 | (iMenuItem[]){ | ||
1178 | { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, | ||
1179 | { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, | ||
1180 | { "---", 0, 0, NULL }, | ||
1181 | { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, | ||
1182 | }, | ||
1183 | 4); | ||
1184 | setId_Widget(tabsMenu, "doctabs.menu"); | ||
1185 | setId_Widget(barMenu, "barmenu"); | ||
1186 | setId_Widget(clipMenu, "clipmenu"); | ||
1187 | } | ||
1188 | /* Global keyboard shortcuts. */ { | ||
1189 | addAction_Widget(root, 'l', KMOD_PRIMARY, "navigate.focus"); | ||
1190 | addAction_Widget(root, 'f', KMOD_PRIMARY, "focus.set id:find.input"); | ||
1191 | addAction_Widget(root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1"); | ||
1192 | addAction_Widget(root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1"); | ||
1193 | addAction_Widget(root, '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1"); | ||
1194 | addAction_Widget(root, '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1"); | ||
1195 | addAction_Widget(root, '5', KMOD_PRIMARY, "sidebar.mode arg:4 toggle:1"); | ||
1196 | addAction_Widget(root, '1', rightSidebar_KeyModifier, "sidebar2.mode arg:0 toggle:1"); | ||
1197 | addAction_Widget(root, '2', rightSidebar_KeyModifier, "sidebar2.mode arg:1 toggle:1"); | ||
1198 | addAction_Widget(root, '3', rightSidebar_KeyModifier, "sidebar2.mode arg:2 toggle:1"); | ||
1199 | addAction_Widget(root, '4', rightSidebar_KeyModifier, "sidebar2.mode arg:3 toggle:1"); | ||
1200 | addAction_Widget(root, '5', rightSidebar_KeyModifier, "sidebar2.mode arg:4 toggle:1"); | ||
1201 | } | ||
1202 | updateMetrics_Root(root); | ||
1203 | return root; | ||
1204 | } | ||
1205 | |||
1206 | void showToolbars_Root(iWidget *root, iBool show) { | ||
1207 | /* The toolbar is only used on phone portrait layout. */ | ||
1208 | if (isLandscape_App()) return; | ||
1209 | iWidget *toolBar = findChild_Widget(root, "toolbar"); | ||
1210 | if (!toolBar) return; | ||
1211 | const int height = rootSize_Window(get_Window()).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar)); | ||
1212 | if (show && !isVisible_Widget(toolBar)) { | ||
1213 | setFlags_Widget(toolBar, hidden_WidgetFlag, iFalse); | ||
1214 | setVisualOffset_Widget(toolBar, 0, 200, easeOut_AnimFlag); | ||
1215 | } | ||
1216 | else if (!show && isVisible_Widget(toolBar)) { | ||
1217 | /* Close any menus that open via the toolbar. */ | ||
1218 | closeMenu_Widget(findChild_Widget(findWidget_App("toolbar.navmenu"), "menu")); | ||
1219 | closeMenu_Widget(findChild_Widget(toolBar, "toolbar.menu")); | ||
1220 | setFlags_Widget(toolBar, hidden_WidgetFlag, iTrue); | ||
1221 | setVisualOffset_Widget(toolBar, height, 200, easeOut_AnimFlag); | ||
1222 | } | ||
1223 | } | ||
1224 | |||
diff --git a/src/ui/root.h b/src/ui/root.h new file mode 100644 index 00000000..a3e595ba --- /dev/null +++ b/src/ui/root.h | |||
@@ -0,0 +1,13 @@ | |||
1 | #pragma once | ||
2 | |||
3 | #include "widget.h" | ||
4 | |||
5 | iWidget * createUserInterface_Root (void); | ||
6 | |||
7 | void setCurrent_Root (iWidget *root); | ||
8 | iWidget * get_Root (void); | ||
9 | |||
10 | void updateMetrics_Root (iWidget *); | ||
11 | void updatePadding_Root (iWidget *); /* TODO: is part of metrics? */ | ||
12 | void dismissPortraitPhoneSidebars_Root (iWidget *); | ||
13 | void showToolbars_Root (iWidget *, iBool show); | ||
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 5aa17140..683f6436 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | #include "listwidget.h" | 36 | #include "listwidget.h" |
37 | #include "keys.h" | 37 | #include "keys.h" |
38 | #include "paint.h" | 38 | #include "paint.h" |
39 | #include "root.h" | ||
39 | #include "scrollwidget.h" | 40 | #include "scrollwidget.h" |
40 | #include "util.h" | 41 | #include "util.h" |
41 | #include "visited.h" | 42 | #include "visited.h" |
@@ -708,7 +709,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it | |||
708 | const iGmDocument *doc = document_DocumentWidget(document_App()); | 709 | const iGmDocument *doc = document_DocumentWidget(document_App()); |
709 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); | 710 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); |
710 | postCommandf_App("document.goto loc:%p", head->text.start); | 711 | postCommandf_App("document.goto loc:%p", head->text.start); |
711 | dismissPortraitPhoneSidebars_Window(get_Window()); | 712 | dismissPortraitPhoneSidebars_Root(get_Root()); |
712 | break; | 713 | break; |
713 | } | 714 | } |
714 | case feeds_SidebarMode: { | 715 | case feeds_SidebarMode: { |
diff --git a/src/ui/widget.c b/src/ui/widget.c index b398b7f6..b7458528 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
26 | #include "touch.h" | 26 | #include "touch.h" |
27 | #include "command.h" | 27 | #include "command.h" |
28 | #include "paint.h" | 28 | #include "paint.h" |
29 | #include "root.h" | ||
29 | #include "util.h" | 30 | #include "util.h" |
30 | #include "window.h" | 31 | #include "window.h" |
31 | 32 | ||
@@ -39,7 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
39 | #endif | 40 | #endif |
40 | 41 | ||
41 | iDeclareType(RootData) | 42 | iDeclareType(RootData) |
42 | 43 | ||
44 | /* TODO: Move to root.c, one instance per root widget. */ | ||
43 | struct Impl_RootData { | 45 | struct Impl_RootData { |
44 | iWidget *hover; | 46 | iWidget *hover; |
45 | iWidget *mouseGrab; | 47 | iWidget *mouseGrab; |
@@ -772,6 +774,7 @@ void unhover_Widget(void) { | |||
772 | 774 | ||
773 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | 775 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { |
774 | if (!d->parent) { | 776 | if (!d->parent) { |
777 | setCurrent_Root(d); | ||
775 | if (ev->type == SDL_MOUSEMOTION) { | 778 | if (ev->type == SDL_MOUSEMOTION) { |
776 | /* Hover widget may change. */ | 779 | /* Hover widget may change. */ |
777 | setHover_Widget(NULL); | 780 | setHover_Widget(NULL); |
diff --git a/src/ui/window.c b/src/ui/window.c index 10c7fc82..9732a4f9 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -22,25 +22,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "window.h" | 23 | #include "window.h" |
24 | 24 | ||
25 | #include "defs.h" | 25 | #include "../app.h" |
26 | #include "labelwidget.h" | ||
27 | #include "inputwidget.h" | ||
28 | #include "documentwidget.h" | ||
29 | #include "sidebarwidget.h" | ||
30 | #include "lookupwidget.h" | ||
31 | #include "bookmarks.h" | 26 | #include "bookmarks.h" |
32 | #include "embedded.h" | ||
33 | #include "command.h" | 27 | #include "command.h" |
34 | #include "paint.h" | 28 | #include "defs.h" |
35 | #include "util.h" | 29 | #include "embedded.h" |
36 | #include "keys.h" | 30 | #include "keys.h" |
31 | #include "labelwidget.h" | ||
32 | #include "paint.h" | ||
33 | #include "root.h" | ||
37 | #include "touch.h" | 34 | #include "touch.h" |
38 | #include "../app.h" | 35 | #include "util.h" |
39 | #include "../visited.h" | 36 | |
40 | #include "../history.h" | ||
41 | #include "../gmcerts.h" | ||
42 | #include "../gmutil.h" | ||
43 | #include "../visited.h" | ||
44 | #if defined (iPlatformMsys) | 37 | #if defined (iPlatformMsys) |
45 | # include "../win32.h" | 38 | # include "../win32.h" |
46 | #endif | 39 | #endif |
@@ -73,186 +66,12 @@ static float initialUiScale_ = 1.1f; | |||
73 | 66 | ||
74 | iDefineTypeConstructionArgs(Window, (iRect rect), rect) | 67 | iDefineTypeConstructionArgs(Window, (iRect rect), rect) |
75 | 68 | ||
76 | static iBool handleRootCommands_(iWidget *root, const char *cmd) { | ||
77 | iUnused(root); | ||
78 | if (equal_Command(cmd, "menu.open")) { | ||
79 | iWidget *button = pointer_Command(cmd); | ||
80 | iWidget *menu = findChild_Widget(button, "menu"); | ||
81 | iAssert(menu); | ||
82 | if (!isVisible_Widget(menu)) { | ||
83 | openMenu_Widget(menu, init_I2(0, button->rect.size.y)); | ||
84 | } | ||
85 | else { | ||
86 | closeMenu_Widget(menu); | ||
87 | } | ||
88 | return iTrue; | ||
89 | } | ||
90 | else if (equal_Command(cmd, "contextclick")) { | ||
91 | iBool showBarMenu = iFalse; | ||
92 | if (equal_Rangecc(range_Command(cmd, "id"), "buttons")) { | ||
93 | const iWidget *sidebar = findWidget_App("sidebar"); | ||
94 | const iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
95 | const iWidget *buttons = pointer_Command(cmd); | ||
96 | if (hasParent_Widget(buttons, sidebar) || | ||
97 | hasParent_Widget(buttons, sidebar2)) { | ||
98 | showBarMenu = iTrue; | ||
99 | } | ||
100 | } | ||
101 | if (equal_Rangecc(range_Command(cmd, "id"), "navbar")) { | ||
102 | showBarMenu = iTrue; | ||
103 | } | ||
104 | if (showBarMenu) { | ||
105 | openMenu_Widget(findWidget_App("barmenu"), coord_Command(cmd)); | ||
106 | return iTrue; | ||
107 | } | ||
108 | return iFalse; | ||
109 | } | ||
110 | else if (equal_Command(cmd, "focus.set")) { | ||
111 | setFocus_Widget(findWidget_App(cstr_Rangecc(range_Command(cmd, "id")))); | ||
112 | return iTrue; | ||
113 | } | ||
114 | else if (equal_Command(cmd, "window.focus.lost")) { | ||
115 | #if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ | ||
116 | setFocus_Widget(NULL); | ||
117 | #endif | ||
118 | setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId); | ||
119 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId); | ||
120 | return iFalse; | ||
121 | } | ||
122 | else if (equal_Command(cmd, "window.focus.gained")) { | ||
123 | setTextColor_LabelWidget(findWidget_App("winbar.app"), uiTextAppTitle_ColorId); | ||
124 | setTextColor_LabelWidget(findWidget_App("winbar.title"), uiTextStrong_ColorId); | ||
125 | return iFalse; | ||
126 | } | ||
127 | else if (equal_Command(cmd, "window.setrect")) { | ||
128 | const int snap = argLabel_Command(cmd, "snap"); | ||
129 | if (snap) { | ||
130 | iWindow *window = get_Window(); | ||
131 | iInt2 coord = coord_Command(cmd); | ||
132 | iInt2 size = init_I2(argLabel_Command(cmd, "width"), | ||
133 | argLabel_Command(cmd, "height")); | ||
134 | SDL_SetWindowPosition(window->win, coord.x, coord.y); | ||
135 | SDL_SetWindowSize(window->win, size.x, size.y); | ||
136 | window->place.snap = snap; | ||
137 | return iTrue; | ||
138 | } | ||
139 | } | ||
140 | else if (equal_Command(cmd, "window.restore")) { | ||
141 | setSnap_Window(get_Window(), none_WindowSnap); | ||
142 | return iTrue; | ||
143 | } | ||
144 | else if (equal_Command(cmd, "window.minimize")) { | ||
145 | SDL_MinimizeWindow(get_Window()->win); | ||
146 | return iTrue; | ||
147 | } | ||
148 | else if (equal_Command(cmd, "window.close")) { | ||
149 | SDL_PushEvent(&(SDL_Event){ .type = SDL_QUIT }); | ||
150 | return iTrue; | ||
151 | } | ||
152 | else if (deviceType_App() == phone_AppDeviceType && equal_Command(cmd, "window.resized")) { | ||
153 | /* Place the sidebar next to or under doctabs depending on orientation. */ | ||
154 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | ||
155 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | ||
156 | removeChild_Widget(parent_Widget(sidebar), sidebar); | ||
157 | setButtonFont_SidebarWidget(sidebar, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
158 | setButtonFont_SidebarWidget(sidebar2, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
159 | // setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | ||
160 | // isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId | ||
161 | // : uiBackgroundSidebar_ColorId); | ||
162 | setFlags_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | ||
163 | borderTop_WidgetFlag, | ||
164 | isPortrait_App()); | ||
165 | if (isLandscape_App()) { | ||
166 | addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos); | ||
167 | setWidth_SidebarWidget(sidebar, 73.0f); | ||
168 | if (isVisible_Widget(findWidget_App("sidebar2"))) { | ||
169 | postCommand_App("sidebar2.toggle"); | ||
170 | } | ||
171 | } | ||
172 | else { | ||
173 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); | ||
174 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | ||
175 | } | ||
176 | return iFalse; | ||
177 | } | ||
178 | else if (handleCommand_App(cmd)) { | ||
179 | return iTrue; | ||
180 | } | ||
181 | return iFalse; | ||
182 | } | ||
183 | |||
184 | /* TODO: Define menus per platform. */ | 69 | /* TODO: Define menus per platform. */ |
185 | 70 | ||
186 | #if defined (iPlatformAppleDesktop) | 71 | #if defined (iPlatformAppleDesktop) |
187 | # define iHaveNativeMenus | 72 | # define iHaveNativeMenus |
188 | #endif | 73 | #endif |
189 | 74 | ||
190 | #if !defined (iHaveNativeMenus) | ||
191 | #if !defined (iPlatformAppleMobile) | ||
192 | /* TODO: Submenus wouldn't hurt here. */ | ||
193 | static const iMenuItem navMenuItems_[] = { | ||
194 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
195 | { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, | ||
196 | { "---", 0, 0, NULL }, | ||
197 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, | ||
198 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
199 | { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" }, | ||
200 | { "---", 0, 0, NULL }, | ||
201 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
202 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | ||
203 | { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, | ||
204 | { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, | ||
205 | { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, | ||
206 | { "---", 0, 0, NULL }, | ||
207 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
208 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | ||
209 | { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" }, | ||
210 | { "---", 0, 0, NULL }, | ||
211 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
212 | { "---", 0, 0, NULL }, | ||
213 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
214 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | ||
215 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | ||
216 | { "---", 0, 0, NULL }, | ||
217 | { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } | ||
218 | }; | ||
219 | #else | ||
220 | /* Tablet menu. */ | ||
221 | static const iMenuItem tabletNavMenuItems_[] = { | ||
222 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
223 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | ||
224 | { "---", 0, 0, NULL }, | ||
225 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | ||
226 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
227 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | ||
228 | { "---", 0, 0, NULL }, | ||
229 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
230 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | ||
231 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
232 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
233 | { "---", 0, 0, NULL }, | ||
234 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
235 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | ||
236 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | ||
237 | }; | ||
238 | |||
239 | /* Phone menu. */ | ||
240 | static const iMenuItem phoneNavMenuItems_[] = { | ||
241 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | ||
242 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | ||
243 | { "---", 0, 0, NULL }, | ||
244 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | ||
245 | { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | ||
246 | { "---", 0, 0, NULL }, | ||
247 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | ||
248 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | ||
249 | { "${menu.downloads}", 0, 0, "downloads.open" }, | ||
250 | { "---", 0, 0, NULL }, | ||
251 | { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | ||
252 | }; | ||
253 | #endif /* AppleMobile */ | ||
254 | #endif | ||
255 | |||
256 | #if defined (iHaveNativeMenus) | 75 | #if defined (iHaveNativeMenus) |
257 | /* Using native menus. */ | 76 | /* Using native menus. */ |
258 | static const iMenuItem fileMenuItems_[] = { | 77 | static const iMenuItem fileMenuItems_[] = { |
@@ -342,980 +161,9 @@ static void removeMacMenus_(void) { | |||
342 | } | 161 | } |
343 | #endif | 162 | #endif |
344 | 163 | ||
345 | #if defined (iPlatformAppleMobile) | ||
346 | static const iMenuItem identityButtonMenuItems_[] = { | ||
347 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | ||
348 | { "---", 0, 0, NULL }, | ||
349 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | ||
350 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | ||
351 | { "---", 0, 0, NULL }, | ||
352 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, | ||
353 | }; | ||
354 | #else /* desktop */ | ||
355 | static const iMenuItem identityButtonMenuItems_[] = { | ||
356 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | ||
357 | { "---", 0, 0, NULL }, | ||
358 | # if !defined (iHaveNativeMenus) | ||
359 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | ||
360 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | ||
361 | { "---", 0, 0, NULL }, | ||
362 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | ||
363 | # else | ||
364 | { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" }, | ||
365 | { "---", 0, 0, NULL }, | ||
366 | { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" }, | ||
367 | #endif | ||
368 | }; | ||
369 | #endif | ||
370 | |||
371 | static const char *reloadCStr_ = reload_Icon; | ||
372 | static const char *pageMenuCStr_ = midEllipsis_Icon; | ||
373 | |||
374 | /* TODO: A preference for these, maybe? */ | ||
375 | static const char *stopSeqCStr_[] = { | ||
376 | /* Corners */ | ||
377 | uiTextCaution_ColorEscape "\U0000230c", | ||
378 | uiTextCaution_ColorEscape "\U0000230d", | ||
379 | uiTextCaution_ColorEscape "\U0000230f", | ||
380 | uiTextCaution_ColorEscape "\U0000230e", | ||
381 | #if 0 | ||
382 | /* Rotating arrow */ | ||
383 | uiTextCaution_ColorEscape "\U00002b62", | ||
384 | uiTextCaution_ColorEscape "\U00002b68", | ||
385 | uiTextCaution_ColorEscape "\U00002b63", | ||
386 | uiTextCaution_ColorEscape "\U00002b69", | ||
387 | uiTextCaution_ColorEscape "\U00002b60", | ||
388 | uiTextCaution_ColorEscape "\U00002b66", | ||
389 | uiTextCaution_ColorEscape "\U00002b61", | ||
390 | uiTextCaution_ColorEscape "\U00002b67", | ||
391 | #endif | ||
392 | #if 0 | ||
393 | /* Star */ | ||
394 | uiTextCaution_ColorEscape "\u2bcc", | ||
395 | uiTextCaution_ColorEscape "\u2bcd", | ||
396 | uiTextCaution_ColorEscape "\u2bcc", | ||
397 | uiTextCaution_ColorEscape "\u2bcd", | ||
398 | uiTextCaution_ColorEscape "\u2bcc", | ||
399 | uiTextCaution_ColorEscape "\u2bcd", | ||
400 | uiTextCaution_ColorEscape "\u2bce", | ||
401 | uiTextCaution_ColorEscape "\u2bcf", | ||
402 | uiTextCaution_ColorEscape "\u2bce", | ||
403 | uiTextCaution_ColorEscape "\u2bcf", | ||
404 | uiTextCaution_ColorEscape "\u2bce", | ||
405 | uiTextCaution_ColorEscape "\u2bcf", | ||
406 | #endif | ||
407 | #if 0 | ||
408 | /* Pulsing circle */ | ||
409 | uiTextCaution_ColorEscape "\U0001f785", | ||
410 | uiTextCaution_ColorEscape "\U0001f786", | ||
411 | uiTextCaution_ColorEscape "\U0001f787", | ||
412 | uiTextCaution_ColorEscape "\U0001f788", | ||
413 | uiTextCaution_ColorEscape "\U0001f789", | ||
414 | uiTextCaution_ColorEscape "\U0001f789", | ||
415 | uiTextCaution_ColorEscape "\U0001f788", | ||
416 | uiTextCaution_ColorEscape "\U0001f787", | ||
417 | uiTextCaution_ColorEscape "\U0001f786", | ||
418 | #endif | ||
419 | #if 0 | ||
420 | /* Dancing dots */ | ||
421 | uiTextCaution_ColorEscape "\U0001fb00", | ||
422 | uiTextCaution_ColorEscape "\U0001fb01", | ||
423 | uiTextCaution_ColorEscape "\U0001fb07", | ||
424 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
425 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
426 | uiTextCaution_ColorEscape "\U0001fb03", | ||
427 | uiTextCaution_ColorEscape "\U0001fb00", | ||
428 | uiTextCaution_ColorEscape "\U0001fb01", | ||
429 | uiTextCaution_ColorEscape "\U0001fb07", | ||
430 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
431 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
432 | uiTextCaution_ColorEscape "\U0001fb03", | ||
433 | |||
434 | uiTextCaution_ColorEscape "\U0001fb7d", | ||
435 | uiTextCaution_ColorEscape "\U0001fb7e", | ||
436 | uiTextCaution_ColorEscape "\U0001fb7f", | ||
437 | uiTextCaution_ColorEscape "\U0001fb7c", | ||
438 | uiTextCaution_ColorEscape "\U0001fb7d", | ||
439 | uiTextCaution_ColorEscape "\U0001fb7e", | ||
440 | uiTextCaution_ColorEscape "\U0001fb7f", | ||
441 | uiTextCaution_ColorEscape "\U0001fb7c", | ||
442 | |||
443 | uiTextCaution_ColorEscape "\U0001fb00", | ||
444 | uiTextCaution_ColorEscape "\U0001fb01", | ||
445 | uiTextCaution_ColorEscape "\U0001fb07", | ||
446 | uiTextCaution_ColorEscape "\U0001fb03", | ||
447 | uiTextCaution_ColorEscape "\U0001fb0f", | ||
448 | uiTextCaution_ColorEscape "\U0001fb1e", | ||
449 | uiTextCaution_ColorEscape "\U0001fb07", | ||
450 | uiTextCaution_ColorEscape "\U0001fb03", | ||
451 | #endif | ||
452 | }; | ||
453 | |||
454 | static void updateNavBarIdentity_(iWidget *navBar) { | ||
455 | const iGmIdentity *ident = | ||
456 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); | ||
457 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); | ||
458 | iWidget *tool = findWidget_App("toolbar.ident"); | ||
459 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); | ||
460 | setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); | ||
461 | /* Update menu. */ | ||
462 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); | ||
463 | setTextCStr_LabelWidget( | ||
464 | idItem, | ||
465 | ident ? format_CStr(uiTextAction_ColorEscape "%s", | ||
466 | cstrCollect_String(subject_TlsCertificate(ident->cert))) | ||
467 | : "${menu.identity.notactive}"); | ||
468 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); | ||
469 | } | ||
470 | |||
471 | static void updateNavDirButtons_(iWidget *navBar) { | ||
472 | const iHistory *history = history_DocumentWidget(document_App()); | ||
473 | setFlags_Widget(findChild_Widget(navBar, "navbar.back"), disabled_WidgetFlag, | ||
474 | atOldest_History(history)); | ||
475 | setFlags_Widget(findChild_Widget(navBar, "navbar.forward"), disabled_WidgetFlag, | ||
476 | atLatest_History(history)); | ||
477 | setFlags_Widget(findWidget_App("toolbar.back"), disabled_WidgetFlag, | ||
478 | atOldest_History(history)); | ||
479 | setFlags_Widget(findWidget_App("toolbar.forward"), disabled_WidgetFlag, | ||
480 | atLatest_History(history)); | ||
481 | } | ||
482 | |||
483 | static const int loadAnimIntervalMs_ = 133; | ||
484 | static int loadAnimIndex_ = 0; | ||
485 | |||
486 | static const char *loadAnimationCStr_(void) { | ||
487 | return stopSeqCStr_[loadAnimIndex_ % iElemCount(stopSeqCStr_)]; | ||
488 | } | ||
489 | |||
490 | static uint32_t updateReloadAnimation_Window_(uint32_t interval, void *window) { | ||
491 | iUnused(window); | ||
492 | loadAnimIndex_++; | ||
493 | postCommand_App("window.reload.update"); | ||
494 | return interval; | ||
495 | } | ||
496 | |||
497 | static void setReloadLabel_Window_(iWindow *d, iBool animating) { | ||
498 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; | ||
499 | iLabelWidget *label = findChild_Widget(d->root, "reload"); | ||
500 | updateTextCStr_LabelWidget(label, animating ? loadAnimationCStr_() : | ||
501 | (isMobile ? pageMenuCStr_ : reloadCStr_)); | ||
502 | if (isMobile) { | ||
503 | setCommand_LabelWidget(label, | ||
504 | collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); | ||
505 | } | ||
506 | } | ||
507 | |||
508 | static void checkLoadAnimation_Window_(iWindow *d) { | ||
509 | const iBool isOngoing = isRequestOngoing_DocumentWidget(document_App()); | ||
510 | if (isOngoing && !d->loadAnimTimer) { | ||
511 | d->loadAnimTimer = SDL_AddTimer(loadAnimIntervalMs_, updateReloadAnimation_Window_, d); | ||
512 | } | ||
513 | else if (!isOngoing && d->loadAnimTimer) { | ||
514 | SDL_RemoveTimer(d->loadAnimTimer); | ||
515 | d->loadAnimTimer = 0; | ||
516 | } | ||
517 | setReloadLabel_Window_(d, isOngoing); | ||
518 | } | ||
519 | |||
520 | static void updatePadding_Window_(iWindow *d) { | ||
521 | #if defined (iPlatformAppleMobile) | ||
522 | iWidget *toolBar = findChild_Widget(d->root, "toolbar"); | ||
523 | float left, top, right, bottom; | ||
524 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
525 | /* Respect the safe area insets. */ { | ||
526 | setPadding_Widget(findChild_Widget(d->root, "navdiv"), left, top, right, 0); | ||
527 | if (toolBar) { | ||
528 | setPadding_Widget(toolBar, left, 0, right, bottom); | ||
529 | } | ||
530 | } | ||
531 | if (toolBar) { | ||
532 | /* TODO: get this from toolBar height, but it's buggy for some reason */ | ||
533 | const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; | ||
534 | setPadding_Widget(findChild_Widget(d->root, "sidebar"), 0, 0, 0, sidebarBottomPad); | ||
535 | setPadding_Widget(findChild_Widget(d->root, "sidebar2"), 0, 0, 0, sidebarBottomPad); | ||
536 | /* TODO: There seems to be unrelated layout glitch in the sidebar where its children | ||
537 | are not arranged correctly until it's hidden and reshown. */ | ||
538 | } | ||
539 | /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ | ||
540 | #endif | ||
541 | } | ||
542 | |||
543 | void dismissPortraitPhoneSidebars_Window(iWindow *d) { | ||
544 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { | ||
545 | iWidget *sidebar = findWidget_App("sidebar"); | ||
546 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
547 | if (isVisible_Widget(sidebar)) { | ||
548 | postCommand_App("sidebar.toggle"); | ||
549 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
550 | } | ||
551 | if (isVisible_Widget(sidebar2)) { | ||
552 | postCommand_App("sidebar2.toggle"); | ||
553 | setVisualOffset_Widget(sidebar2, height_Widget(sidebar2), 250, easeIn_AnimFlag); | ||
554 | } | ||
555 | // setFlags_Widget(findWidget_App("toolbar.ident"), noBackground_WidgetFlag, iTrue); | ||
556 | // setFlags_Widget(findWidget_App("toolbar.view"), noBackground_WidgetFlag, iTrue); | ||
557 | } | ||
558 | } | ||
559 | |||
560 | static iBool willPerformSearchQuery_(const iString *userInput) { | ||
561 | const iString *clean = collect_String(trimmed_String(userInput)); | ||
562 | if (isEmpty_String(clean)) { | ||
563 | return iFalse; | ||
564 | } | ||
565 | return !isEmpty_String(&prefs_App()->searchUrl) && !isLikelyUrl_String(userInput); | ||
566 | } | ||
567 | |||
568 | static void showSearchQueryIndicator_(iBool show) { | ||
569 | iWidget *indicator = findWidget_App("input.indicator.search"); | ||
570 | showCollapsed_Widget(indicator, show); | ||
571 | iAssert(isInstance_Object(parent_Widget(parent_Widget(indicator)), &Class_InputWidget)); | ||
572 | iInputWidget *url = (iInputWidget *) parent_Widget(parent_Widget(indicator)); | ||
573 | setContentPadding_InputWidget(url, -1, contentPadding_InputWidget(url).left + | ||
574 | (show ? width_Widget(indicator) : 0)); | ||
575 | } | ||
576 | |||
577 | static int navBarAvailableSpace_(iWidget *navBar) { | ||
578 | int avail = width_Rect(innerBounds_Widget(navBar)); | ||
579 | iConstForEach(ObjectList, i, children_Widget(navBar)) { | ||
580 | const iWidget *child = i.object; | ||
581 | if (~flags_Widget(child) & expand_WidgetFlag && | ||
582 | isVisible_Widget(child) && | ||
583 | cmp_String(id_Widget(child), "url")) { | ||
584 | avail -= width_Widget(child); | ||
585 | } | ||
586 | } | ||
587 | return avail; | ||
588 | } | ||
589 | |||
590 | iBool isNarrow_Window(const iWindow *d) { | ||
591 | return width_Rect(safeRootRect_Window(d)) / gap_UI < 140; | ||
592 | } | ||
593 | |||
594 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | ||
595 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "metrics.changed")) { | ||
596 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | ||
597 | const iBool isNarrow = !isPhone && isNarrow_Window(get_Window()); | ||
598 | /* Adjust navbar padding. */ { | ||
599 | int hPad = isPhone && isPortrait_App() ? 0 : (isPhone || isNarrow) ? gap_UI / 2 | ||
600 | : gap_UI * 3 / 2; | ||
601 | int vPad = gap_UI * 3 / 2; | ||
602 | int topPad = !findWidget_App("winbar") ? gap_UI / 2 : 0; | ||
603 | setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, vPad / 2); | ||
604 | } | ||
605 | /* Button sizing. */ | ||
606 | if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) { | ||
607 | setFlags_Widget(navBar, tight_WidgetFlag, isNarrow); | ||
608 | iForEach(ObjectList, i, navBar->children) { | ||
609 | iWidget *child = as_Widget(i.object); | ||
610 | setFlags_Widget(child, tight_WidgetFlag, isNarrow); | ||
611 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
612 | iLabelWidget *label = i.object; | ||
613 | updateSize_LabelWidget(label); | ||
614 | } | ||
615 | } | ||
616 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ | ||
617 | /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ | ||
618 | const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); | ||
619 | setContentPadding_InputWidget(findChild_Widget(navBar, "url"), | ||
620 | embedButtonWidth * 0.75f, | ||
621 | embedButtonWidth * 0.75f); | ||
622 | } | ||
623 | if (isPhone) { | ||
624 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", | ||
625 | "navbar.ident", "navbar.home", "navbar.menu" }; | ||
626 | iWidget *toolBar = findWidget_App("toolbar"); | ||
627 | setVisualOffset_Widget(toolBar, 0, 0, 0); | ||
628 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); | ||
629 | iForIndices(i, buttons) { | ||
630 | iLabelWidget *btn = findChild_Widget(navBar, buttons[i]); | ||
631 | setFlags_Widget(as_Widget(btn), hidden_WidgetFlag, isPortrait_App()); | ||
632 | if (isLandscape_App()) { | ||
633 | /* Collapsing sets size to zero and the label doesn't know when to update | ||
634 | its own size automatically. */ | ||
635 | updateSize_LabelWidget(btn); | ||
636 | } | ||
637 | } | ||
638 | arrange_Widget(get_Window()->root); | ||
639 | } | ||
640 | /* Resize the URL input field. */ { | ||
641 | iWidget *urlBar = findChild_Widget(navBar, "url"); | ||
642 | urlBar->rect.size.x = iMini(navBarAvailableSpace_(navBar), 167 * gap_UI); | ||
643 | arrange_Widget(navBar); | ||
644 | } | ||
645 | refresh_Widget(navBar); | ||
646 | postCommand_Widget(navBar, "layout.changed id:navbar"); | ||
647 | return iFalse; | ||
648 | } | ||
649 | else if (equal_Command(cmd, "window.reload.update")) { | ||
650 | checkLoadAnimation_Window_(get_Window()); | ||
651 | return iTrue; | ||
652 | } | ||
653 | else if (equal_Command(cmd, "navigate.focus")) { | ||
654 | iWidget *url = findChild_Widget(navBar, "url"); | ||
655 | if (focus_Widget() != url) { | ||
656 | setFocus_Widget(findChild_Widget(navBar, "url")); | ||
657 | } | ||
658 | else { | ||
659 | selectAll_InputWidget((iInputWidget *) url); | ||
660 | } | ||
661 | return iTrue; | ||
662 | } | ||
663 | else if (equal_Command(cmd, "input.edited")) { | ||
664 | iAnyObject * url = findChild_Widget(navBar, "url"); | ||
665 | const iString *text = text_InputWidget(url); | ||
666 | const iBool show = willPerformSearchQuery_(text); | ||
667 | showSearchQueryIndicator_(show); | ||
668 | if (pointer_Command(cmd) == url) { | ||
669 | submit_LookupWidget(findWidget_App("lookup"), text); | ||
670 | return iTrue; | ||
671 | } | ||
672 | } | ||
673 | else if (startsWith_CStr(cmd, "input.ended id:url ")) { | ||
674 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
675 | showSearchQueryIndicator_(iFalse); | ||
676 | if (isEmpty_String(text_InputWidget(url))) { | ||
677 | /* User entered nothing; restore the current URL. */ | ||
678 | setText_InputWidget(url, url_DocumentWidget(document_App())); | ||
679 | return iTrue; | ||
680 | } | ||
681 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && | ||
682 | !isFocused_Widget(findWidget_App("lookup"))) { | ||
683 | iString *newUrl = copy_String(text_InputWidget(url)); | ||
684 | trim_String(newUrl); | ||
685 | if (willPerformSearchQuery_(newUrl)) { | ||
686 | postCommandf_App("open url:%s", cstr_String(searchQueryUrl_App(newUrl))); | ||
687 | } | ||
688 | else { | ||
689 | postCommandf_App( | ||
690 | "open url:%s", | ||
691 | cstr_String(absoluteUrl_String(&iStringLiteral(""), collect_String(newUrl)))); | ||
692 | } | ||
693 | return iTrue; | ||
694 | } | ||
695 | } | ||
696 | else if (startsWith_CStr(cmd, "document.")) { | ||
697 | /* React to the current document only. */ | ||
698 | if (document_Command(cmd) == document_App()) { | ||
699 | if (equal_Command(cmd, "document.changed")) { | ||
700 | iInputWidget *url = findWidget_App("url"); | ||
701 | const iString *urlStr = collect_String(suffix_Command(cmd, "url")); | ||
702 | trimCache_App(); | ||
703 | visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ | ||
704 | postCommand_App("visited.changed"); /* sidebar will update */ | ||
705 | setText_InputWidget(url, urlStr); | ||
706 | checkLoadAnimation_Window_(get_Window()); | ||
707 | dismissPortraitPhoneSidebars_Window(get_Window()); | ||
708 | updateNavBarIdentity_(navBar); | ||
709 | updateNavDirButtons_(navBar); | ||
710 | /* Icon updates should be limited to automatically chosen icons if the user | ||
711 | is allowed to pick their own in the future. */ | ||
712 | if (updateBookmarkIcon_Bookmarks(bookmarks_App(), urlStr, | ||
713 | siteIcon_GmDocument(document_DocumentWidget(document_App())))) { | ||
714 | postCommand_App("bookmarks.changed"); | ||
715 | } | ||
716 | return iFalse; | ||
717 | } | ||
718 | else if (equal_Command(cmd, "document.request.cancelled")) { | ||
719 | checkLoadAnimation_Window_(get_Window()); | ||
720 | return iFalse; | ||
721 | } | ||
722 | else if (equal_Command(cmd, "document.request.started")) { | ||
723 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
724 | setTextCStr_InputWidget(url, suffixPtr_Command(cmd, "url")); | ||
725 | checkLoadAnimation_Window_(get_Window()); | ||
726 | dismissPortraitPhoneSidebars_Window(get_Window()); | ||
727 | return iFalse; | ||
728 | } | ||
729 | } | ||
730 | } | ||
731 | else if (equal_Command(cmd, "tabs.changed")) { | ||
732 | /* Update navbar according to the current tab. */ | ||
733 | iDocumentWidget *doc = document_App(); | ||
734 | if (doc) { | ||
735 | setText_InputWidget(findChild_Widget(navBar, "url"), url_DocumentWidget(doc)); | ||
736 | checkLoadAnimation_Window_(get_Window()); | ||
737 | updateNavBarIdentity_(navBar); | ||
738 | } | ||
739 | setFocus_Widget(NULL); | ||
740 | } | ||
741 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | ||
742 | iWidget *widget = pointer_Command(cmd); | ||
743 | iWidget *menu = findWidget_App("doctabs.menu"); | ||
744 | if (isTabButton_Widget(widget)) { | ||
745 | if (!isVisible_Widget(menu)) { | ||
746 | iWidget *tabs = findWidget_App("doctabs"); | ||
747 | iWidget *page = tabPage_Widget(tabs, childIndex_Widget(widget->parent, widget)); | ||
748 | if (argLabel_Command(cmd, "button") == SDL_BUTTON_MIDDLE) { | ||
749 | postCommandf_App("tabs.close id:%s", cstr_String(id_Widget(page))); | ||
750 | return iTrue; | ||
751 | } | ||
752 | showTabPage_Widget(tabs, page); | ||
753 | openMenu_Widget(menu, coord_Command(cmd)); | ||
754 | } | ||
755 | } | ||
756 | } | ||
757 | else if (equal_Command(cmd, "navigate.reload")) { | ||
758 | iDocumentWidget *doc = document_Command(cmd); | ||
759 | if (isRequestOngoing_DocumentWidget(doc)) { | ||
760 | postCommand_App("document.stop"); | ||
761 | } | ||
762 | else { | ||
763 | postCommand_App("document.reload"); | ||
764 | } | ||
765 | return iTrue; | ||
766 | } | ||
767 | return iFalse; | ||
768 | } | ||
769 | |||
770 | static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | ||
771 | if (equal_Command(cmd, "input.ended") && | ||
772 | equal_Rangecc(range_Command(cmd, "id"), "find.input")) { | ||
773 | iInputWidget *input = findChild_Widget(searchBar, "find.input"); | ||
774 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && isVisible_Widget(input)) { | ||
775 | postCommand_App("find.next"); | ||
776 | /* Keep focus when pressing Enter. */ | ||
777 | if (!isEmpty_String(text_InputWidget(input))) { | ||
778 | postCommand_App("focus.set id:find.input"); | ||
779 | } | ||
780 | } | ||
781 | else { | ||
782 | postCommand_App("find.clearmark"); | ||
783 | } | ||
784 | return iTrue; | ||
785 | } | ||
786 | else if (equal_Command(cmd, "focus.gained")) { | ||
787 | if (pointer_Command(cmd) == findChild_Widget(searchBar, "find.input")) { | ||
788 | if (!isVisible_Widget(searchBar)) { | ||
789 | setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iFalse); | ||
790 | arrange_Widget(get_Window()->root); | ||
791 | postRefresh_App(); | ||
792 | } | ||
793 | } | ||
794 | } | ||
795 | else if (equal_Command(cmd, "find.close")) { | ||
796 | if (isVisible_Widget(searchBar)) { | ||
797 | setFlags_Widget(searchBar, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | ||
798 | arrange_Widget(searchBar->parent); | ||
799 | if (isFocused_Widget(findChild_Widget(searchBar, "find.input"))) { | ||
800 | setFocus_Widget(NULL); | ||
801 | } | ||
802 | refresh_Widget(searchBar->parent); | ||
803 | } | ||
804 | return iTrue; | ||
805 | } | ||
806 | return iFalse; | ||
807 | } | ||
808 | |||
809 | #if defined (iPlatformAppleMobile) | ||
810 | static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) { | ||
811 | if (isVisible_Widget(sidebar)) { | ||
812 | postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar))); | ||
813 | if (toolButtonId) { | ||
814 | // setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue); | ||
815 | } | ||
816 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
817 | } | ||
818 | } | ||
819 | |||
820 | static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | ||
821 | if (equalWidget_Command(cmd, toolBar, "mouse.clicked") && arg_Command(cmd) && | ||
822 | argLabel_Command(cmd, "button") == SDL_BUTTON_RIGHT) { | ||
823 | iWidget *menu = findChild_Widget(toolBar, "toolbar.menu"); | ||
824 | arrange_Widget(menu); | ||
825 | openMenu_Widget(menu, init_I2(0, -height_Widget(menu))); | ||
826 | return iTrue; | ||
827 | } | ||
828 | else if (equal_Command(cmd, "toolbar.showview")) { | ||
829 | /* TODO: Clean this up. */ | ||
830 | iWidget *sidebar = findWidget_App("sidebar"); | ||
831 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
832 | dismissSidebar_(sidebar2, "toolbar.ident"); | ||
833 | const iBool isVisible = isVisible_Widget(sidebar); | ||
834 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.view"), noBackground_WidgetFlag, | ||
835 | // isVisible); | ||
836 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
837 | const int viewHeight = rootSize_Window(get_Window()).y; | ||
838 | if (arg_Command(cmd) >= 0) { | ||
839 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); | ||
840 | if (!isVisible) { | ||
841 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
842 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
843 | } | ||
844 | } | ||
845 | else { | ||
846 | postCommandf_App("sidebar.toggle"); | ||
847 | if (isVisible) { | ||
848 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | ||
849 | } | ||
850 | else { | ||
851 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | ||
852 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
853 | } | ||
854 | } | ||
855 | return iTrue; | ||
856 | } | ||
857 | else if (equal_Command(cmd, "toolbar.showident")) { | ||
858 | /* TODO: Clean this up. */ | ||
859 | iWidget *sidebar = findWidget_App("sidebar"); | ||
860 | iWidget *sidebar2 = findWidget_App("sidebar2"); | ||
861 | dismissSidebar_(sidebar, "toolbar.view"); | ||
862 | const iBool isVisible = isVisible_Widget(sidebar2); | ||
863 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, | ||
864 | // isVisible); | ||
865 | /* If a sidebar hasn't been shown yet, it's height is zero. */ | ||
866 | const int viewHeight = rootSize_Window(get_Window()).y; | ||
867 | if (isVisible) { | ||
868 | dismissSidebar_(sidebar2, NULL); | ||
869 | } | ||
870 | else { | ||
871 | postCommand_App("sidebar2.mode arg:3 show:1"); | ||
872 | int offset = height_Widget(sidebar2); | ||
873 | if (offset == 0) offset = rootSize_Window(get_Window()).y; | ||
874 | setVisualOffset_Widget(sidebar2, offset, 0, 0); | ||
875 | setVisualOffset_Widget(sidebar2, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | ||
876 | } | ||
877 | return iTrue; | ||
878 | } | ||
879 | else if (equal_Command(cmd, "sidebar.mode.changed")) { | ||
880 | iLabelWidget *viewTool = findChild_Widget(toolBar, "toolbar.view"); | ||
881 | updateTextCStr_LabelWidget(viewTool, icon_SidebarMode(arg_Command(cmd))); | ||
882 | return iFalse; | ||
883 | } | ||
884 | return iFalse; | ||
885 | } | ||
886 | #endif /* defined (iPlatformAppleMobile) */ | ||
887 | |||
888 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { | ||
889 | iLabelWidget *lab = newIcon_LabelWidget(text, 0, 0, cmd); | ||
890 | setFont_LabelWidget(lab, uiLabelLarge_FontId); | ||
891 | return lab; | ||
892 | } | ||
893 | |||
894 | static int appIconSize_(void) { | ||
895 | return lineHeight_Text(uiContent_FontId); | ||
896 | } | ||
897 | |||
898 | static void updateMetrics_Window_(iWindow *d) { | ||
899 | /* Custom frame. */ | ||
900 | iWidget *winBar = findChild_Widget(d->root, "winbar"); | ||
901 | if (winBar) { | ||
902 | iWidget *appIcon = findChild_Widget(winBar, "winbar.icon"); | ||
903 | iWidget *appTitle = findChild_Widget(winBar, "winbar.title"); | ||
904 | iWidget *appMin = findChild_Widget(winBar, "winbar.min"); | ||
905 | iWidget *appMax = findChild_Widget(winBar, "winbar.max"); | ||
906 | iWidget *appClose = findChild_Widget(winBar, "winbar.close"); | ||
907 | setPadding_Widget(winBar, 0, gap_UI / 3, 0, 0); | ||
908 | setFixedSize_Widget(appMin, init_I2(gap_UI * 11.5f, height_Widget(appTitle))); | ||
909 | setFixedSize_Widget(appMax, appMin->rect.size); | ||
910 | setFixedSize_Widget(appClose, appMin->rect.size); | ||
911 | setFixedSize_Widget(appIcon, init_I2(appIconSize_(), appMin->rect.size.y)); | ||
912 | } | ||
913 | iWidget *navBar = findChild_Widget(d->root, "navbar"); | ||
914 | iWidget *lock = findChild_Widget(navBar, "navbar.lock"); | ||
915 | iWidget *url = findChild_Widget(d->root, "url"); | ||
916 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | ||
917 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | ||
918 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); | ||
919 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ | ||
920 | updateSize_LabelWidget((iLabelWidget *) lock); | ||
921 | setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); | ||
922 | setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | ||
923 | width_Widget(lock) * 0.75); | ||
924 | rightEmbed->rect.pos.y = gap_UI; | ||
925 | updatePadding_Window_(d); | ||
926 | arrange_Widget(d->root); | ||
927 | postRefresh_App(); | ||
928 | } | ||
929 | |||
930 | static void setupUserInterface_Window(iWindow *d) { | 164 | static void setupUserInterface_Window(iWindow *d) { |
931 | #if defined (iPlatformMobile) | 165 | d->root = createUserInterface_Root(); |
932 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); | 166 | setCurrent_Root(d->root); |
933 | #endif | ||
934 | /* Children of root cover the entire window. */ | ||
935 | setFlags_Widget(d->root, resizeChildren_WidgetFlag, iTrue); | ||
936 | setCommandHandler_Widget(d->root, handleRootCommands_); | ||
937 | |||
938 | iWidget *div = makeVDiv_Widget(); | ||
939 | setId_Widget(div, "navdiv"); | ||
940 | addChild_Widget(d->root, iClob(div)); | ||
941 | |||
942 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | ||
943 | /* Window title bar. */ | ||
944 | if (prefs_App()->customFrame) { | ||
945 | setPadding1_Widget(div, 1); | ||
946 | iWidget *winBar = new_Widget(); | ||
947 | setId_Widget(winBar, "winbar"); | ||
948 | setFlags_Widget(winBar, | ||
949 | arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag | | ||
950 | arrangeHorizontal_WidgetFlag | collapse_WidgetFlag, | ||
951 | iTrue); | ||
952 | iWidget *appIcon; | ||
953 | setId_Widget( | ||
954 | addChild_Widget(winBar, iClob(appIcon = makePadding_Widget(0))), "winbar.icon"); | ||
955 | iLabelWidget *appButton = | ||
956 | addChildFlags_Widget(winBar, | ||
957 | iClob(new_LabelWidget("Lagrange", NULL)), | ||
958 | fixedHeight_WidgetFlag | frameless_WidgetFlag); | ||
959 | setTextColor_LabelWidget(appButton, uiTextAppTitle_ColorId); | ||
960 | setId_Widget(as_Widget(appButton), "winbar.app"); | ||
961 | iLabelWidget *appTitle; | ||
962 | setFont_LabelWidget(appButton, uiContentBold_FontId); | ||
963 | setId_Widget(addChildFlags_Widget(winBar, | ||
964 | iClob(appTitle = new_LabelWidget("", NULL)), | ||
965 | expand_WidgetFlag | fixedHeight_WidgetFlag | | ||
966 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | ||
967 | "winbar.title"); | ||
968 | setTextColor_LabelWidget(appTitle, uiTextStrong_ColorId); | ||
969 | iLabelWidget *appMin, *appMax, *appClose; | ||
970 | setId_Widget(addChildFlags_Widget( | ||
971 | winBar, | ||
972 | iClob(appMin = newLargeIcon_LabelWidget("\u2013", "window.minimize")), | ||
973 | frameless_WidgetFlag), | ||
974 | "winbar.min"); | ||
975 | addChildFlags_Widget( | ||
976 | winBar, | ||
977 | iClob(appMax = newLargeIcon_LabelWidget("\u25a1", "window.maximize toggle:1")), | ||
978 | frameless_WidgetFlag); | ||
979 | setId_Widget(as_Widget(appMax), "winbar.max"); | ||
980 | addChildFlags_Widget(winBar, | ||
981 | iClob(appClose = newLargeIcon_LabelWidget(close_Icon, "window.close")), | ||
982 | frameless_WidgetFlag); | ||
983 | setId_Widget(as_Widget(appClose), "winbar.close"); | ||
984 | setFont_LabelWidget(appClose, uiContent_FontId); | ||
985 | addChild_Widget(div, iClob(winBar)); | ||
986 | setBackgroundColor_Widget(winBar, uiBackground_ColorId); | ||
987 | } | ||
988 | #endif | ||
989 | /* Navigation bar. */ { | ||
990 | iWidget *navBar = new_Widget(); | ||
991 | setId_Widget(navBar, "navbar"); | ||
992 | setFlags_Widget(navBar, | ||
993 | hittable_WidgetFlag | /* context menu */ | ||
994 | arrangeHeight_WidgetFlag | | ||
995 | resizeChildren_WidgetFlag | | ||
996 | arrangeHorizontal_WidgetFlag | | ||
997 | drawBackgroundToHorizontalSafeArea_WidgetFlag | | ||
998 | drawBackgroundToVerticalSafeArea_WidgetFlag, | ||
999 | iTrue); | ||
1000 | addChild_Widget(div, iClob(navBar)); | ||
1001 | setBackgroundColor_Widget(navBar, uiBackground_ColorId); | ||
1002 | setCommandHandler_Widget(navBar, handleNavBarCommands_); | ||
1003 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.back"); | ||
1004 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.forward"); | ||
1005 | /* Mobile devices have a button for easier access to the left sidebar. */ | ||
1006 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1007 | setId_Widget(addChildFlags_Widget( | ||
1008 | navBar, | ||
1009 | iClob(newIcon_LabelWidget(leftHalf_Icon, 0, 0, "sidebar.toggle")), | ||
1010 | collapse_WidgetFlag), | ||
1011 | "navbar.sidebar"); | ||
1012 | } | ||
1013 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | ||
1014 | iLabelWidget *idMenu = makeMenuButton_LabelWidget( | ||
1015 | "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_)); | ||
1016 | setAlignVisually_LabelWidget(idMenu, iTrue); | ||
1017 | setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident"); | ||
1018 | iInputWidget *url; | ||
1019 | /* URL input field. */ { | ||
1020 | url = new_InputWidget(0); | ||
1021 | setFlags_Widget(as_Widget(url), resizeHeightOfChildren_WidgetFlag, iTrue); | ||
1022 | setSelectAllOnFocus_InputWidget(url, iTrue); | ||
1023 | setId_Widget(as_Widget(url), "url"); | ||
1024 | setUrlContent_InputWidget(url, iTrue); | ||
1025 | setNotifyEdits_InputWidget(url, iTrue); | ||
1026 | setTextCStr_InputWidget(url, "gemini://"); | ||
1027 | addChildFlags_Widget(navBar, iClob(url), 0); | ||
1028 | const int64_t embedFlags = | ||
1029 | noBackground_WidgetFlag | frameless_WidgetFlag | unpadded_WidgetFlag | | ||
1030 | (deviceType_App() == desktop_AppDeviceType ? tight_WidgetFlag : 0); | ||
1031 | /* Page information/certificate warning. */ { | ||
1032 | iLabelWidget *lock = addChildFlags_Widget( | ||
1033 | as_Widget(url), | ||
1034 | iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")), | ||
1035 | embedFlags | moveToParentLeftEdge_WidgetFlag); | ||
1036 | setId_Widget(as_Widget(lock), "navbar.lock"); | ||
1037 | setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); | ||
1038 | updateTextCStr_LabelWidget(lock, "\U0001f512"); | ||
1039 | } | ||
1040 | iWidget *rightEmbed = new_Widget(); | ||
1041 | setId_Widget(rightEmbed, "url.rightembed"); | ||
1042 | addChildFlags_Widget(as_Widget(url), | ||
1043 | iClob(rightEmbed), | ||
1044 | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag | | ||
1045 | resizeHeightOfChildren_WidgetFlag | | ||
1046 | moveToParentRightEdge_WidgetFlag); | ||
1047 | /* Feeds refresh indicator is inside the input field. */ { | ||
1048 | iLabelWidget *queryInd = | ||
1049 | new_LabelWidget(uiTextAction_ColorEscape "\u21d2 ${status.query}", NULL); | ||
1050 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); | ||
1051 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); | ||
1052 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); | ||
1053 | setAlignVisually_LabelWidget(queryInd, iTrue); | ||
1054 | addChildFlags_Widget(rightEmbed, | ||
1055 | iClob(queryInd), | ||
1056 | collapse_WidgetFlag | hidden_WidgetFlag); | ||
1057 | } | ||
1058 | /* Feeds refresh indicator is inside the input field. */ { | ||
1059 | iLabelWidget *fprog = new_LabelWidget(uiTextCaution_ColorEscape | ||
1060 | "\u2605 ${status.feeds}", NULL); | ||
1061 | setId_Widget(as_Widget(fprog), "feeds.progress"); | ||
1062 | setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); | ||
1063 | setAlignVisually_LabelWidget(fprog, iTrue); | ||
1064 | addChildFlags_Widget(rightEmbed, | ||
1065 | iClob(fprog), | ||
1066 | collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); | ||
1067 | } | ||
1068 | /* Download progress indicator is also inside the input field, but hidden normally. */ { | ||
1069 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); | ||
1070 | setId_Widget(as_Widget(progress), "document.progress"); | ||
1071 | setBackgroundColor_Widget(as_Widget(progress), uiBackground_ColorId); | ||
1072 | setAlignVisually_LabelWidget(progress, iTrue); | ||
1073 | addChildFlags_Widget( | ||
1074 | rightEmbed, iClob(progress), collapse_WidgetFlag); | ||
1075 | } | ||
1076 | /* Reload button. */ { | ||
1077 | iLabelWidget *reload; | ||
1078 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1079 | reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | ||
1080 | } | ||
1081 | else { | ||
1082 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1083 | reload = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1084 | (iMenuItem[]){ | ||
1085 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | ||
1086 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1087 | { "---", 0, 0, NULL }, | ||
1088 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1089 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1090 | { "---", 0, 0, NULL }, | ||
1091 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1092 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1093 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1094 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1095 | { "---", 0, 0, NULL }, | ||
1096 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1097 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1098 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1099 | 14); | ||
1100 | setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId); | ||
1101 | setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue); | ||
1102 | } | ||
1103 | setId_Widget(as_Widget(reload), "reload"); | ||
1104 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | ||
1105 | updateSize_LabelWidget(reload); | ||
1106 | } | ||
1107 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | ||
1108 | } | ||
1109 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1110 | /* On mobile, the Identities button is on the right side of the URL bar. */ | ||
1111 | iWidget *ident = removeChild_Widget(navBar, findChild_Widget(navBar, "navbar.ident")); | ||
1112 | addChild_Widget(navBar, iClob(ident)); | ||
1113 | } | ||
1114 | addChildFlags_Widget(navBar, iClob(new_Widget()), expand_WidgetFlag); | ||
1115 | setId_Widget(addChildFlags_Widget(navBar, | ||
1116 | iClob(newIcon_LabelWidget( | ||
1117 | home_Icon, SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")), | ||
1118 | collapse_WidgetFlag), | ||
1119 | "navbar.home"); | ||
1120 | #if !defined (iHaveNativeMenus) | ||
1121 | # if defined (iPlatformAppleMobile) | ||
1122 | iLabelWidget *navMenu = | ||
1123 | makeMenuButton_LabelWidget("\U0001d362", isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, | ||
1124 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); | ||
1125 | # else | ||
1126 | iLabelWidget *navMenu = | ||
1127 | makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_)); | ||
1128 | # endif | ||
1129 | setAlignVisually_LabelWidget(navMenu, iTrue); | ||
1130 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); | ||
1131 | #else | ||
1132 | insertMacMenus_(); | ||
1133 | #endif | ||
1134 | } | ||
1135 | /* Tab bar. */ { | ||
1136 | iWidget *mainStack = new_Widget(); | ||
1137 | setId_Widget(mainStack, "stack"); | ||
1138 | addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag | | ||
1139 | unhittable_WidgetFlag); | ||
1140 | iWidget *tabBar = makeTabs_Widget(mainStack); | ||
1141 | setId_Widget(tabBar, "doctabs"); | ||
1142 | setBackgroundColor_Widget(tabBar, uiBackground_ColorId); | ||
1143 | appendTabPage_Widget(tabBar, iClob(new_DocumentWidget()), "Document", 0, 0); | ||
1144 | iWidget *buttons = findChild_Widget(tabBar, "tabs.buttons"); | ||
1145 | setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag | | ||
1146 | drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue); | ||
1147 | if (deviceType_App() == phone_AppDeviceType) { | ||
1148 | setBackgroundColor_Widget(buttons, uiBackground_ColorId); | ||
1149 | } | ||
1150 | setId_Widget( | ||
1151 | addChild_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new"))), | ||
1152 | "newtab"); | ||
1153 | } | ||
1154 | /* Sidebars. */ { | ||
1155 | iWidget *content = findChild_Widget(d->root, "tabs.content"); | ||
1156 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SideBarSide); | ||
1157 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | ||
1158 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SideBarSide); | ||
1159 | if (deviceType_App() != phone_AppDeviceType) { | ||
1160 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); | ||
1161 | } | ||
1162 | else { | ||
1163 | /* The identities sidebar is always in the main area. */ | ||
1164 | addChild_Widget(findChild_Widget(d->root, "stack"), iClob(sidebar2)); | ||
1165 | setFlags_Widget(as_Widget(sidebar2), hidden_WidgetFlag, iTrue); | ||
1166 | } | ||
1167 | } | ||
1168 | /* Lookup results. */ { | ||
1169 | iLookupWidget *lookup = new_LookupWidget(); | ||
1170 | addChildFlags_Widget(div, iClob(lookup), fixedPosition_WidgetFlag | hidden_WidgetFlag); | ||
1171 | } | ||
1172 | /* Search bar. */ { | ||
1173 | iWidget *searchBar = new_Widget(); | ||
1174 | setId_Widget(searchBar, "search"); | ||
1175 | setFlags_Widget(searchBar, | ||
1176 | hidden_WidgetFlag | disabled_WidgetFlag | collapse_WidgetFlag | | ||
1177 | arrangeHeight_WidgetFlag | resizeChildren_WidgetFlag | | ||
1178 | arrangeHorizontal_WidgetFlag, | ||
1179 | iTrue); | ||
1180 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1181 | addChild_Widget(div, iClob(searchBar)); | ||
1182 | } | ||
1183 | else { | ||
1184 | /* The search bar appears at the top on mobile, because there is a virtual keyboard | ||
1185 | covering the bottom. */ | ||
1186 | insertChildAfter_Widget(div, iClob(searchBar), | ||
1187 | childIndex_Widget(div, findChild_Widget(div, "navbar"))); | ||
1188 | } | ||
1189 | setBackgroundColor_Widget(searchBar, uiBackground_ColorId); | ||
1190 | setCommandHandler_Widget(searchBar, handleSearchBarCommands_); | ||
1191 | addChildFlags_Widget( | ||
1192 | searchBar, iClob(new_LabelWidget(magnifyingGlass_Icon, NULL)), frameless_WidgetFlag); | ||
1193 | iInputWidget *input = new_InputWidget(0); | ||
1194 | setHint_InputWidget(input, "${hint.findtext}"); | ||
1195 | setSelectAllOnFocus_InputWidget(input, iTrue); | ||
1196 | setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ | ||
1197 | setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), | ||
1198 | "find.input"); | ||
1199 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); | ||
1200 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); | ||
1201 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(close_Icon, SDLK_ESCAPE, 0, "find.close"))); | ||
1202 | } | ||
1203 | #if defined (iPlatformAppleMobile) | ||
1204 | /* Bottom toolbar. */ | ||
1205 | if (isPhone_iOS()) { | ||
1206 | iWidget *toolBar = new_Widget(); | ||
1207 | addChild_Widget(d->root, iClob(toolBar)); | ||
1208 | setId_Widget(toolBar, "toolbar"); | ||
1209 | setCommandHandler_Widget(toolBar, handleToolBarCommands_); | ||
1210 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | | ||
1211 | parentCannotResizeHeight_WidgetFlag | | ||
1212 | resizeWidthOfChildren_WidgetFlag | | ||
1213 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | ||
1214 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); | ||
1215 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1216 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), | ||
1217 | frameless_WidgetFlag), | ||
1218 | "toolbar.back"); | ||
1219 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1220 | iClob(newLargeIcon_LabelWidget("\U0001f872", "navigate.forward")), | ||
1221 | frameless_WidgetFlag), | ||
1222 | "toolbar.forward"); | ||
1223 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1224 | iClob(newLargeIcon_LabelWidget("\U0001f464", "toolbar.showident")), | ||
1225 | frameless_WidgetFlag), | ||
1226 | "toolbar.ident"); | ||
1227 | setId_Widget(addChildFlags_Widget(toolBar, | ||
1228 | iClob(newLargeIcon_LabelWidget("\U0001f588", "toolbar.showview arg:-1")), | ||
1229 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | ||
1230 | "toolbar.view"); | ||
1231 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, | ||
1232 | iElemCount(phoneNavMenuItems_)); | ||
1233 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); | ||
1234 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); | ||
1235 | addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag); | ||
1236 | iForEach(ObjectList, i, children_Widget(toolBar)) { | ||
1237 | iLabelWidget *btn = i.object; | ||
1238 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | ||
1239 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); | ||
1240 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); | ||
1241 | } | ||
1242 | const iMenuItem items[] = { | ||
1243 | { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | ||
1244 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | ||
1245 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, | ||
1246 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, | ||
1247 | }; | ||
1248 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), | ||
1249 | items, iElemCount(items)); | ||
1250 | setId_Widget(menu, "toolbar.menu"); /* view menu */ | ||
1251 | } | ||
1252 | #endif | ||
1253 | updatePadding_Window_(d); | ||
1254 | /* Global context menus. */ { | ||
1255 | iWidget *tabsMenu = makeMenu_Widget( | ||
1256 | d->root, | ||
1257 | (iMenuItem[]){ | ||
1258 | { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" }, | ||
1259 | { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" }, | ||
1260 | { "---", 0, 0, NULL }, | ||
1261 | { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, | ||
1262 | { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, | ||
1263 | { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, | ||
1264 | }, | ||
1265 | 6); | ||
1266 | iWidget *barMenu = | ||
1267 | makeMenu_Widget(d->root, | ||
1268 | (iMenuItem[]){ | ||
1269 | { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, | ||
1270 | { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, | ||
1271 | }, | ||
1272 | deviceType_App() == phone_AppDeviceType ? 1 : 2); | ||
1273 | iWidget *clipMenu = makeMenu_Widget(d->root, | ||
1274 | (iMenuItem[]){ | ||
1275 | { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, | ||
1276 | { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, | ||
1277 | { "---", 0, 0, NULL }, | ||
1278 | { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, | ||
1279 | }, | ||
1280 | 4); | ||
1281 | setId_Widget(tabsMenu, "doctabs.menu"); | ||
1282 | setId_Widget(barMenu, "barmenu"); | ||
1283 | setId_Widget(clipMenu, "clipmenu"); | ||
1284 | } | ||
1285 | /* Global keyboard shortcuts. */ { | ||
1286 | addAction_Widget(d->root, 'l', KMOD_PRIMARY, "navigate.focus"); | ||
1287 | addAction_Widget(d->root, 'f', KMOD_PRIMARY, "focus.set id:find.input"); | ||
1288 | addAction_Widget(d->root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1"); | ||
1289 | addAction_Widget(d->root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1"); | ||
1290 | addAction_Widget(d->root, '3', KMOD_PRIMARY, "sidebar.mode arg:2 toggle:1"); | ||
1291 | addAction_Widget(d->root, '4', KMOD_PRIMARY, "sidebar.mode arg:3 toggle:1"); | ||
1292 | addAction_Widget(d->root, '5', KMOD_PRIMARY, "sidebar.mode arg:4 toggle:1"); | ||
1293 | addAction_Widget(d->root, '1', rightSidebar_KeyModifier, "sidebar2.mode arg:0 toggle:1"); | ||
1294 | addAction_Widget(d->root, '2', rightSidebar_KeyModifier, "sidebar2.mode arg:1 toggle:1"); | ||
1295 | addAction_Widget(d->root, '3', rightSidebar_KeyModifier, "sidebar2.mode arg:2 toggle:1"); | ||
1296 | addAction_Widget(d->root, '4', rightSidebar_KeyModifier, "sidebar2.mode arg:3 toggle:1"); | ||
1297 | addAction_Widget(d->root, '5', rightSidebar_KeyModifier, "sidebar2.mode arg:4 toggle:1"); | ||
1298 | } | ||
1299 | updateMetrics_Window_(d); | ||
1300 | } | ||
1301 | |||
1302 | void showToolbars_Window(iWindow *d, iBool show) { | ||
1303 | /* The toolbar is only used on phone portrait layout. */ | ||
1304 | if (isLandscape_App()) return; | ||
1305 | iWidget *toolBar = findChild_Widget(d->root, "toolbar"); | ||
1306 | if (!toolBar) return; | ||
1307 | const int height = rootSize_Window(d).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar)); | ||
1308 | if (show && !isVisible_Widget(toolBar)) { | ||
1309 | setFlags_Widget(toolBar, hidden_WidgetFlag, iFalse); | ||
1310 | setVisualOffset_Widget(toolBar, 0, 200, easeOut_AnimFlag); | ||
1311 | } | ||
1312 | else if (!show && isVisible_Widget(toolBar)) { | ||
1313 | /* Close any menus that open via the toolbar. */ | ||
1314 | closeMenu_Widget(findChild_Widget(findWidget_App("toolbar.navmenu"), "menu")); | ||
1315 | closeMenu_Widget(findChild_Widget(toolBar, "toolbar.menu")); | ||
1316 | setFlags_Widget(toolBar, hidden_WidgetFlag, iTrue); | ||
1317 | setVisualOffset_Widget(toolBar, height, 200, easeOut_AnimFlag); | ||
1318 | } | ||
1319 | } | 167 | } |
1320 | 168 | ||
1321 | static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { | 169 | static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { |
@@ -1325,7 +173,7 @@ static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { | |||
1325 | size->y -= d->keyboardHeight; | 173 | size->y -= d->keyboardHeight; |
1326 | d->root->minSize = *size; | 174 | d->root->minSize = *size; |
1327 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { | 175 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { |
1328 | updatePadding_Window_(d); | 176 | updatePadding_Root(d->root); |
1329 | const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); | 177 | const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); |
1330 | const iBool isVert = (d->place.lastNotifiedSize.y != size->y); | 178 | const iBool isVert = (d->place.lastNotifiedSize.y != size->y); |
1331 | arrange_Widget(d->root); | 179 | arrange_Widget(d->root); |
@@ -1494,6 +342,7 @@ static SDL_Surface *loadImage_(const iBlock *data, int resized) { | |||
1494 | void init_Window(iWindow *d, iRect rect) { | 342 | void init_Window(iWindow *d, iRect rect) { |
1495 | theWindow_ = d; | 343 | theWindow_ = d; |
1496 | d->win = NULL; | 344 | d->win = NULL; |
345 | d->root = NULL; | ||
1497 | iZap(d->cursors); | 346 | iZap(d->cursors); |
1498 | d->place.initialPos = rect.pos; | 347 | d->place.initialPos = rect.pos; |
1499 | d->place.normalRect = rect; | 348 | d->place.normalRect = rect; |
@@ -1567,12 +416,9 @@ void init_Window(iWindow *d, iRect rect) { | |||
1567 | #if defined (iPlatformAppleMobile) | 416 | #if defined (iPlatformAppleMobile) |
1568 | setupWindow_iOS(d); | 417 | setupWindow_iOS(d); |
1569 | #endif | 418 | #endif |
1570 | d->root = new_Widget(); | ||
1571 | setFlags_Widget(d->root, fixedSize_WidgetFlag | focusRoot_WidgetFlag, iTrue); | ||
1572 | d->presentTime = 0.0; | 419 | d->presentTime = 0.0; |
1573 | d->frameTime = SDL_GetTicks(); | 420 | d->frameTime = SDL_GetTicks(); |
1574 | d->loadAnimTimer = 0; | 421 | d->loadAnimTimer = 0; |
1575 | setId_Widget(d->root, "root"); | ||
1576 | init_Text(d->render); | 422 | init_Text(d->render); |
1577 | setupUserInterface_Window(d); | 423 | setupUserInterface_Window(d); |
1578 | postCommand_App("~bindings.changed"); /* update from bindings */ | 424 | postCommand_App("~bindings.changed"); /* update from bindings */ |
@@ -1785,7 +631,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
1785 | return iTrue; | 631 | return iTrue; |
1786 | } | 632 | } |
1787 | case SDL_WINDOWEVENT_RESIZED: | 633 | case SDL_WINDOWEVENT_RESIZED: |
1788 | updatePadding_Window_(d); | 634 | updatePadding_Root(d->root); |
1789 | if (d->isMinimized) { | 635 | if (d->isMinimized) { |
1790 | updateRootSize_Window_(d, iTrue); | 636 | updateRootSize_Window_(d, iTrue); |
1791 | return iTrue; | 637 | return iTrue; |
@@ -1945,7 +791,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
1945 | } | 791 | } |
1946 | } | 792 | } |
1947 | if (isMetricsChange_UserEvent(&event)) { | 793 | if (isMetricsChange_UserEvent(&event)) { |
1948 | updateMetrics_Window_(d); | 794 | updateMetrics_Root(d->root); |
1949 | } | 795 | } |
1950 | if (isCommand_UserEvent(&event, "lang.changed")) { | 796 | if (isCommand_UserEvent(&event, "lang.changed")) { |
1951 | #if defined (iPlatformAppleDesktop) | 797 | #if defined (iPlatformAppleDesktop) |
@@ -2091,11 +937,11 @@ uint32_t id_Window(const iWindow *d) { | |||
2091 | } | 937 | } |
2092 | 938 | ||
2093 | iInt2 rootSize_Window(const iWindow *d) { | 939 | iInt2 rootSize_Window(const iWindow *d) { |
2094 | return d ? d->root->rect.size : zero_I2(); | 940 | return d && d->root ? d->root->rect.size : zero_I2(); |
2095 | } | 941 | } |
2096 | 942 | ||
2097 | iRect safeRootRect_Window(const iWindow *d) { | 943 | iRect safeRootRect_Window(const iWindow *d) { |
2098 | iRect rect = { zero_I2(), d->root->rect.size }; | 944 | iRect rect = { zero_I2(), rootSize_Window(d) }; |
2099 | #if defined (iPlatformAppleMobile) | 945 | #if defined (iPlatformAppleMobile) |
2100 | float left, top, right, bottom; | 946 | float left, top, right, bottom; |
2101 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 947 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
diff --git a/src/ui/window.h b/src/ui/window.h index 052f8828..9d9a6aeb 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -91,7 +91,6 @@ void setFreezeDraw_Window (iWindow *, iBool freezeDraw); | |||
91 | void setCursor_Window (iWindow *, int cursor); | 91 | void setCursor_Window (iWindow *, int cursor); |
92 | void setSnap_Window (iWindow *, int snapMode); | 92 | void setSnap_Window (iWindow *, int snapMode); |
93 | void setKeyboardHeight_Window(iWindow *, int height); | 93 | void setKeyboardHeight_Window(iWindow *, int height); |
94 | void dismissPortraitPhoneSidebars_Window (iWindow *); | ||
95 | void showToolbars_Window (iWindow *, iBool show); | 94 | void showToolbars_Window (iWindow *, iBool show); |
96 | iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); | 95 | iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); |
97 | 96 | ||