summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-03-21 14:29:51 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-03-21 14:29:51 +0200
commit2f9e203058df442921fc0151ddc5fe9b68b87935 (patch)
tree5441025fcf4e974e82128f65376650bec7a941bb
parentb93ba1bfb860c2a4b5bf46c77f3f0d11b4eb1282 (diff)
iOS: Save to Files; hide toolbar on scroll
There is no downloads directory on mobile. Instead, the downloaded file is temporarily cached and given to the iOS document picker to export. Added an option to hide the bottom toolbar while scrolling down.
-rw-r--r--src/app.c23
-rw-r--r--src/defs.h8
-rw-r--r--src/ios.h6
-rw-r--r--src/ios.m41
-rw-r--r--src/prefs.c1
-rw-r--r--src/prefs.h1
-rw-r--r--src/ui/documentwidget.c63
-rw-r--r--src/ui/util.c5
-rw-r--r--src/ui/widget.c33
-rw-r--r--src/ui/widget.h3
-rw-r--r--src/ui/window.c33
-rw-r--r--src/ui/window.h1
12 files changed, 175 insertions, 43 deletions
diff --git a/src/app.c b/src/app.c
index 0611b6dd..2bf2ad4a 100644
--- a/src/app.c
+++ b/src/app.c
@@ -276,6 +276,14 @@ static const char *downloadDir_App_(void) {
276 } 276 }
277 } 277 }
278#endif 278#endif
279#if defined (iPlatformAppleMobile)
280 /* Save to a local cache directory from where the user can export to the cloud. */
281 const iString *dlDir = cleanedCStr_Path("~/Library/Caches/Downloads");
282 if (!fileExists_FileInfo(dlDir)) {
283 makeDirs_Path(dlDir);
284 }
285 return cstr_String(dlDir);
286#endif
279 return defaultDownloadDir_App_; 287 return defaultDownloadDir_App_;
280} 288}
281 289
@@ -313,6 +321,11 @@ static void loadPrefs_App_(iApp *d) {
313 d->initialWindowRect = init_Rect( 321 d->initialWindowRect = init_Rect(
314 pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); 322 pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height"));
315 } 323 }
324#if !defined (LAGRANGE_DOWNLOAD_EDIT)
325 else if (equal_Command(cmd, "downloads")) {
326 continue; /* can't change downloads directory */
327 }
328#endif
316 else { 329 else {
317 postCommandString_App(&cmdStr); 330 postCommandString_App(&cmdStr);
318 } 331 }
@@ -1311,6 +1324,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
1311 isSelected_Widget(findChild_Widget(d, "prefs.smoothscroll"))); 1324 isSelected_Widget(findChild_Widget(d, "prefs.smoothscroll")));
1312 postCommandf_App("imageloadscroll arg:%d", 1325 postCommandf_App("imageloadscroll arg:%d",
1313 isSelected_Widget(findChild_Widget(d, "prefs.imageloadscroll"))); 1326 isSelected_Widget(findChild_Widget(d, "prefs.imageloadscroll")));
1327 postCommandf_App("hidetoolbarscroll arg:%d",
1328 isSelected_Widget(findChild_Widget(d, "prefs.hidetoolbarscroll")));
1314 postCommandf_App("ostheme arg:%d", 1329 postCommandf_App("ostheme arg:%d",
1315 isSelected_Widget(findChild_Widget(d, "prefs.ostheme"))); 1330 isSelected_Widget(findChild_Widget(d, "prefs.ostheme")));
1316 postCommandf_App("decodeurls arg:%d", 1331 postCommandf_App("decodeurls arg:%d",
@@ -1592,6 +1607,13 @@ iBool handleCommand_App(const char *cmd) {
1592 d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd); 1607 d->prefs.loadImageInsteadOfScrolling = arg_Command(cmd);
1593 return iTrue; 1608 return iTrue;
1594 } 1609 }
1610 else if (equal_Command(cmd, "hidetoolbarscroll")) {
1611 d->prefs.hideToolbarOnScroll = arg_Command(cmd);
1612 if (!d->prefs.hideToolbarOnScroll) {
1613 showToolbars_Window(d->window, iTrue);
1614 }
1615 return iTrue;
1616 }
1595 else if (equal_Command(cmd, "theme.set")) { 1617 else if (equal_Command(cmd, "theme.set")) {
1596 const int isAuto = argLabel_Command(cmd, "auto"); 1618 const int isAuto = argLabel_Command(cmd, "auto");
1597 d->prefs.theme = arg_Command(cmd); 1619 d->prefs.theme = arg_Command(cmd);
@@ -1864,6 +1886,7 @@ iBool handleCommand_App(const char *cmd) {
1864 setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink); 1886 setToggle_Widget(findChild_Widget(dlg, "prefs.hoverlink"), d->prefs.hoverLink);
1865 setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling); 1887 setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling);
1866 setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling); 1888 setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling);
1889 setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll);
1867 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); 1890 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);
1868 setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); 1891 setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame);
1869 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); 1892 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize);
diff --git a/src/defs.h b/src/defs.h
index eb8affec..27eacd3b 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -82,3 +82,11 @@ enum iFileVersion {
82#define unhappy_Icon "\U0001f641" 82#define unhappy_Icon "\U0001f641"
83#define globe_Icon "\U0001f310" 83#define globe_Icon "\U0001f310"
84#define magnifyingGlass_Icon "\U0001f50d" 84#define magnifyingGlass_Icon "\U0001f50d"
85
86/* UI labels that depend on the platform */
87
88#if defined (iPlatformMobile)
89# define saveToDownloads_Label "Save to Files"
90#else
91# define saveToDownloads_Label "Save to Downloads"
92#endif
diff --git a/src/ios.h b/src/ios.h
index 9490491f..29669a26 100644
--- a/src/ios.h
+++ b/src/ios.h
@@ -32,7 +32,9 @@ enum iHapticEffect {
32 32
33void setupApplication_iOS (void); 33void setupApplication_iOS (void);
34void setupWindow_iOS (iWindow *window); 34void setupWindow_iOS (iWindow *window);
35iBool isPhone_iOS (void);
36void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom);
37iBool processEvent_iOS (const SDL_Event *); 35iBool processEvent_iOS (const SDL_Event *);
38void playHapticEffect_iOS (enum iHapticEffect effect); 36void playHapticEffect_iOS (enum iHapticEffect effect);
37void exportDownloadedFile_iOS(const iString *path);
38
39iBool isPhone_iOS (void);
40void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom);
diff --git a/src/ios.m b/src/ios.m
index dbe78fd6..9aca5a19 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -120,7 +120,9 @@ API_AVAILABLE(ios(13.0))
120 120
121/*----------------------------------------------------------------------------------------------*/ 121/*----------------------------------------------------------------------------------------------*/
122 122
123@interface AppState : NSObject 123@interface AppState : NSObject<UIDocumentPickerDelegate> {
124 iString *fileBeingSaved;
125}
124@property (nonatomic, assign) BOOL isHapticsAvailable; 126@property (nonatomic, assign) BOOL isHapticsAvailable;
125@property (nonatomic, strong) NSObject *haptic; 127@property (nonatomic, strong) NSObject *haptic;
126@end 128@end
@@ -129,6 +131,23 @@ static AppState *appState_;
129 131
130@implementation AppState 132@implementation AppState
131 133
134-(instancetype)init {
135 self = [super init];
136 fileBeingSaved = NULL;
137 return self;
138}
139
140-(void)setFileBeingSaved:(const iString *)path {
141 fileBeingSaved = copy_String(path);
142}
143
144-(void)removeSavedFile {
145 /* The file was copied to an external location, so the cached copy is not needed. */
146 remove(cstr_String(fileBeingSaved));
147 delete_String(fileBeingSaved);
148 fileBeingSaved = NULL;
149}
150
132-(void)setupHaptics { 151-(void)setupHaptics {
133 if (@available(iOS 13.0, *)) { 152 if (@available(iOS 13.0, *)) {
134 self.isHapticsAvailable = CHHapticEngine.capabilitiesForHardware.supportsHaptics; 153 self.isHapticsAvailable = CHHapticEngine.capabilitiesForHardware.supportsHaptics;
@@ -145,6 +164,15 @@ static AppState *appState_;
145 } 164 }
146} 165}
147 166
167- (void)documentPicker:(UIDocumentPickerViewController *)controller
168didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
169 [self removeSavedFile];
170}
171
172- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
173 [self removeSavedFile];
174}
175
148-(void)keyboardOnScreen:(NSNotification *)notification { 176-(void)keyboardOnScreen:(NSNotification *)notification {
149 NSDictionary *info = notification.userInfo; 177 NSDictionary *info = notification.userInfo;
150 NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; 178 NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
@@ -257,3 +285,14 @@ iBool processEvent_iOS(const SDL_Event *ev) {
257 } 285 }
258 return iFalse; /* allow normal processing */ 286 return iFalse; /* allow normal processing */
259} 287}
288
289void exportDownloadedFile_iOS(const iString *path) {
290 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path)
291 encoding:NSUTF8StringEncoding]];
292 UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc]
293 initWithURL:url
294 inMode:UIDocumentPickerModeExportToService];
295 picker.delegate = appState_;
296 [appState_ setFileBeingSaved:path];
297 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil];
298}
diff --git a/src/prefs.c b/src/prefs.c
index b9b59836..a4f12e20 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -36,6 +36,7 @@ void init_Prefs(iPrefs *d) {
36 d->uiScale = 1.0f; /* default set elsewhere */ 36 d->uiScale = 1.0f; /* default set elsewhere */
37 d->zoomPercent = 100; 37 d->zoomPercent = 100;
38 d->sideIcon = iTrue; 38 d->sideIcon = iTrue;
39 d->hideToolbarOnScroll = iTrue;
39 d->hoverLink = iTrue; 40 d->hoverLink = iTrue;
40 d->smoothScrolling = iTrue; 41 d->smoothScrolling = iTrue;
41 d->loadImageInsteadOfScrolling = iFalse; 42 d->loadImageInsteadOfScrolling = iFalse;
diff --git a/src/prefs.h b/src/prefs.h
index 13b91568..130f11e2 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -46,6 +46,7 @@ struct Impl_Prefs {
46 float uiScale; 46 float uiScale;
47 int zoomPercent; 47 int zoomPercent;
48 iBool sideIcon; 48 iBool sideIcon;
49 iBool hideToolbarOnScroll;
49 /* Behavior */ 50 /* Behavior */
50 iString downloadDir; 51 iString downloadDir;
51 iBool hoverLink; 52 iBool hoverLink;
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index d9b78a4d..0b9757d4 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -48,6 +48,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
48#include "visbuf.h" 48#include "visbuf.h"
49#include "visited.h" 49#include "visited.h"
50 50
51#if defined (iPlatformAppleMobile)
52# include "ios.h"
53#endif
54
51#include <the_Foundation/file.h> 55#include <the_Foundation/file.h>
52#include <the_Foundation/fileinfo.h> 56#include <the_Foundation/fileinfo.h>
53#include <the_Foundation/objectlist.h> 57#include <the_Foundation/objectlist.h>
@@ -220,6 +224,7 @@ struct Impl_DocumentWidget {
220 SDL_Texture * sideIconBuf; 224 SDL_Texture * sideIconBuf;
221 iTextBuf * timestampBuf; 225 iTextBuf * timestampBuf;
222 iTranslation * translation; 226 iTranslation * translation;
227 iWidget * phoneToolbar;
223}; 228};
224 229
225iDefineObjectConstruction(DocumentWidget) 230iDefineObjectConstruction(DocumentWidget)
@@ -231,6 +236,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
231 setFlags_Widget(w, hover_WidgetFlag, iTrue); 236 setFlags_Widget(w, hover_WidgetFlag, iTrue);
232 init_PersistentDocumentState(&d->mod); 237 init_PersistentDocumentState(&d->mod);
233 d->flags = 0; 238 d->flags = 0;
239 d->phoneToolbar = NULL;
234 iZap(d->certExpiry); 240 iZap(d->certExpiry);
235 d->certFingerprint = new_Block(0); 241 d->certFingerprint = new_Block(0);
236 d->certFlags = 0; 242 d->certFlags = 0;
@@ -440,8 +446,13 @@ static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) {
440} 446}
441 447
442static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { 448static int scrollMax_DocumentWidget_(const iDocumentWidget *d) {
443 return size_GmDocument(d->doc).y - height_Rect(bounds_Widget(constAs_Widget(d))) + 449 int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(constAs_Widget(d))) +
444 (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI; 450 (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI;
451 if (d->phoneToolbar) {
452 sm += rootSize_Window(get_Window()).y -
453 top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar));
454 }
455 return sm;
445} 456}
446 457
447static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) { 458static void invalidateLink_DocumentWidget_(iDocumentWidget *d, iGmLinkId id) {
@@ -815,9 +826,10 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
815 appendFormat_String(src, 826 appendFormat_String(src,
816 "\n```\n%s\n```\n" 827 "\n```\n%s\n```\n"
817 "You can save it as a file to your Downloads folder, though. " 828 "You can save it as a file to your Downloads folder, though. "
818 "Press %s or select \"Save to Downloads\" from the menu.", 829 "Press %s or select \"%s\" from the menu.",
819 cstr_String(meta), 830 cstr_String(meta),
820 cstr_String(key)); 831 cstr_String(key),
832 saveToDownloads_Label);
821 break; 833 break;
822 } 834 }
823 case slowDown_GmStatusCode: 835 case slowDown_GmStatusCode:
@@ -1085,6 +1097,14 @@ static void smoothScroll_DocumentWidget_(iDocumentWidget *d, int offset, int dur
1085 const int scrollMax = scrollMax_DocumentWidget_(d); 1097 const int scrollMax = scrollMax_DocumentWidget_(d);
1086 if (scrollMax > 0) { 1098 if (scrollMax > 0) {
1087 destY = iMin(destY, scrollMax); 1099 destY = iMin(destY, scrollMax);
1100 if (deviceType_App() == phone_AppDeviceType) {
1101 if (destY == scrollMax) {
1102 showToolbars_Window(get_Window(), iTrue);
1103 }
1104 else if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) {
1105 showToolbars_Window(get_Window(), offset < 0);
1106 }
1107 }
1088 } 1108 }
1089 else { 1109 else {
1090 destY = 0; 1110 destY = 0;
@@ -1443,12 +1463,17 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo
1443 iFile *f = new_File(savePath); 1463 iFile *f = new_File(savePath);
1444 if (open_File(f, writeOnly_FileMode)) { 1464 if (open_File(f, writeOnly_FileMode)) {
1445 write_File(f, content); 1465 write_File(f, content);
1466 close_File(f);
1446 const size_t size = size_Block(content); 1467 const size_t size = size_Block(content);
1447 const iBool isMega = size >= 1000000; 1468 const iBool isMega = size >= 1000000;
1469#if defined (iPlatformAppleMobile)
1470 exportDownloadedFile_iOS(savePath);
1471#else
1448 makeMessage_Widget(uiHeading_ColorEscape "FILE SAVED", 1472 makeMessage_Widget(uiHeading_ColorEscape "FILE SAVED",
1449 format_CStr("%s\nSize: %.3f %s", cstr_String(path_File(f)), 1473 format_CStr("%s\nSize: %.3f %s", cstr_String(path_File(f)),
1450 isMega ? size / 1.0e6f : (size / 1.0e3f), 1474 isMega ? size / 1.0e6f : (size / 1.0e3f),
1451 isMega ? "MB" : "KB")); 1475 isMega ? "MB" : "KB"));
1476#endif
1452 } 1477 }
1453 else { 1478 else {
1454 makeMessage_Widget(uiTextCaution_ColorEscape "ERROR SAVING FILE", 1479 makeMessage_Widget(uiTextCaution_ColorEscape "ERROR SAVING FILE",
@@ -1496,8 +1521,8 @@ static const int homeRowKeys_[] = {
1496static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d, 1521static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumentWidget *d,
1497 iBool keepCenter) { 1522 iBool keepCenter) {
1498 const int newWidth = documentWidth_DocumentWidget_(d); 1523 const int newWidth = documentWidth_DocumentWidget_(d);
1499 if (newWidth == size_GmDocument(d->doc).x) { 1524 if (newWidth == size_GmDocument(d->doc).x && !keepCenter /* not a font change */) {
1500 return iFalse; /* hasn't changed */ 1525 return iFalse;
1501 } 1526 }
1502 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top 1527 /* Font changes (i.e., zooming) will keep the view centered, otherwise keep the top
1503 of the visible area fixed. */ 1528 of the visible area fixed. */
@@ -1526,6 +1551,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen
1526 scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue); 1551 scrollTo_DocumentWidget_(d, mid_Rect(run->bounds).y, iTrue);
1527 } 1552 }
1528 } 1553 }
1554 return iTrue;
1529} 1555}
1530 1556
1531static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 1557static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -1533,6 +1559,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1533 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) { 1559 if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "font.changed")) {
1534 /* Alt/Option key may be involved in window size changes. */ 1560 /* Alt/Option key may be involved in window size changes. */
1535 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse); 1561 iChangeFlags(d->flags, showLinkNumbers_DocumentWidgetFlag, iFalse);
1562 d->phoneToolbar = findWidget_App("toolbar");
1536 const iBool keepCenter = equal_Command(cmd, "font.changed"); 1563 const iBool keepCenter = equal_Command(cmd, "font.changed");
1537 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); 1564 updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter);
1538 updateSideIconBuf_DocumentWidget_(d); 1565 updateSideIconBuf_DocumentWidget_(d);
@@ -2361,36 +2388,16 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2361 } 2388 }
2362 } 2389 }
2363 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 2390 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
2364 /* TODO: Maybe clean this up a bit? Wheel events are used for scrolling
2365 but they are calculated differently based on device/mouse/trackpad. */
2366 const iInt2 mouseCoord = mouseCoord_Window(get_Window()); 2391 const iInt2 mouseCoord = mouseCoord_Window(get_Window());
2367 if (isPerPixel_MouseWheelEvent(&ev->wheel)) { 2392 if (isPerPixel_MouseWheelEvent(&ev->wheel)) {
2368 stop_Anim(&d->scrollY); 2393 stop_Anim(&d->scrollY);
2369 iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y); 2394 iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
2370//# if defined (iPlatformAppleMobile)
2371// wheel.x = -wheel.x;
2372//# else
2373// /* Wheel mounts are in points. */
2374// mulfv_I2(&wheel, get_Window()->pixelRatio);
2375// /* Only scroll on one axis at a time. */
2376// if (iAbs(wheel.x) > iAbs(wheel.y)) {
2377// wheel.y = 0;
2378// }
2379// else {
2380// wheel.x = 0;
2381// }
2382//# endif
2383 scroll_DocumentWidget_(d, -wheel.y); 2395 scroll_DocumentWidget_(d, -wheel.y);
2384 scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0); 2396 scrollWideBlock_DocumentWidget_(d, mouseCoord, -wheel.x, 0);
2385 } 2397 }
2386 else { 2398 else {
2387 /* Traditional mouse wheel. */ 2399 /* Traditional mouse wheel. */
2388//#if defined (iPlatformApple)
2389// /* Disregard wheel acceleration applied by the OS. */
2390// const int amount = iSign(ev->wheel.y);
2391//#else
2392 const int amount = ev->wheel.y; 2400 const int amount = ev->wheel.y;
2393//#endif
2394 if (keyMods_Sym(modState_Keys()) == KMOD_PRIMARY) { 2401 if (keyMods_Sym(modState_Keys()) == KMOD_PRIMARY) {
2395 postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10); 2402 postCommandf_App("zoom.delta arg:%d", amount > 0 ? 10 : -10);
2396 return iTrue; 2403 return iTrue;
@@ -2513,7 +2520,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2513 d->contextLink->mediaType != download_GmRunMediaType) { 2520 d->contextLink->mediaType != download_GmRunMediaType) {
2514 if (isFinished_GmRequest(mediaReq->req)) { 2521 if (isFinished_GmRequest(mediaReq->req)) {
2515 pushBack_Array(&items, 2522 pushBack_Array(&items,
2516 &(iMenuItem){ download_Icon " Save to Downloads", 2523 &(iMenuItem){ download_Icon " " saveToDownloads_Label,
2517 0, 2524 0,
2518 0, 2525 0,
2519 format_CStr("document.media.save link:%u", 2526 format_CStr("document.media.save link:%u",
@@ -2558,7 +2565,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2558 &items, 2565 &items,
2559 (iMenuItem[]){ 2566 (iMenuItem[]){
2560 { "Copy Page Source", 'c', KMOD_PRIMARY, "copy" }, 2567 { "Copy Page Source", 'c', KMOD_PRIMARY, "copy" },
2561 { download_Icon " Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" } }, 2568 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } },
2562 2); 2569 2);
2563 } 2570 }
2564 } 2571 }
diff --git a/src/ui/util.c b/src/ui/util.c
index 8fbe5d41..d68ae738 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -986,7 +986,6 @@ static iBool isTwoColumnPage_(iWidget *d) {
986 986
987static iBool isOmittedPref_(const iString *id) { 987static iBool isOmittedPref_(const iString *id) {
988 static const char *omittedPrefs[] = { 988 static const char *omittedPrefs[] = {
989 "prefs.downloads",
990 "prefs.smoothscroll", 989 "prefs.smoothscroll",
991 "prefs.imageloadscroll", 990 "prefs.imageloadscroll",
992 "prefs.retainwindow", 991 "prefs.retainwindow",
@@ -1812,6 +1811,10 @@ iWidget *makePreferences_Widget(void) {
1812 addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll"))); 1811 addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll")));
1813 addChild_Widget(headings, iClob(makeHeading_Widget("Load image on scroll:"))); 1812 addChild_Widget(headings, iClob(makeHeading_Widget("Load image on scroll:")));
1814 addChild_Widget(values, iClob(makeToggle_Widget("prefs.imageloadscroll"))); 1813 addChild_Widget(values, iClob(makeToggle_Widget("prefs.imageloadscroll")));
1814 if (deviceType_App() == phone_AppDeviceType) {
1815 addChild_Widget(headings, iClob(makeHeading_Widget("Hide toolbar on scroll:")));
1816 addChild_Widget(values, iClob(makeToggle_Widget("prefs.hidetoolbarscroll")));
1817 }
1815 } 1818 }
1816 /* Window. */ { 1819 /* Window. */ {
1817 appendTwoColumnPage_(tabs, "Interface", '2', &headings, &values); 1820 appendTwoColumnPage_(tabs, "Interface", '2', &headings, &values);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 81853c2a..23891999 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -223,6 +223,9 @@ void setVisualOffset_Widget(iWidget *d, int value, uint32_t span, int animFlags)
223 setFlags_Widget(d, visualOffset_WidgetFlag, iTrue); 223 setFlags_Widget(d, visualOffset_WidgetFlag, iTrue);
224 if (span == 0) { 224 if (span == 0) {
225 init_Anim(&d->visualOffset, value); 225 init_Anim(&d->visualOffset, value);
226 if (value == 0) {
227 setFlags_Widget(d, visualOffset_WidgetFlag, iFalse); /* offset is being reset */
228 }
226 } 229 }
227 else { 230 else {
228 setValue_Anim(&d->visualOffset, value, span); 231 setValue_Anim(&d->visualOffset, value, span);
@@ -346,6 +349,9 @@ void arrange_Widget(iWidget *d) {
346 else if (d->flags & moveToParentRightEdge_WidgetFlag) { 349 else if (d->flags & moveToParentRightEdge_WidgetFlag) {
347 d->rect.pos.x = width_Rect(innerRect_Widget_(d->parent)) - width_Rect(d->rect); 350 d->rect.pos.x = width_Rect(innerRect_Widget_(d->parent)) - width_Rect(d->rect);
348 } 351 }
352 else if (d->flags & moveToParentBottomEdge_WidgetFlag) {
353 d->rect.pos.y = height_Rect(innerRect_Widget_(d->parent)) - height_Rect(d->rect);
354 }
349 else if (d->flags & centerHorizontal_WidgetFlag) { 355 else if (d->flags & centerHorizontal_WidgetFlag) {
350 centerHorizontal_Widget_(d); 356 centerHorizontal_Widget_(d);
351 } 357 }
@@ -455,8 +461,12 @@ void arrange_Widget(iWidget *d) {
455 iForEach(ObjectList, i, d->children) { 461 iForEach(ObjectList, i, d->children) {
456 iWidget *child = as_Widget(i.object); 462 iWidget *child = as_Widget(i.object);
457 if (isArranged_Widget_(child) && ~child->flags & parentCannotResize_WidgetFlag) { 463 if (isArranged_Widget_(child) && ~child->flags & parentCannotResize_WidgetFlag) {
458 if (dirs.x) setWidth_Widget_(child, child->flags & unpadded_WidgetFlag ? unpaddedChildSize.x : childSize.x); 464 if (dirs.x) {
459 if (dirs.y) setHeight_Widget_(child, child->flags & unpadded_WidgetFlag ? unpaddedChildSize.y : childSize.y); 465 setWidth_Widget_(child, child->flags & unpadded_WidgetFlag ? unpaddedChildSize.x : childSize.x);
466 }
467 if (dirs.y && ~child->flags & parentCannotResizeHeight_WidgetFlag) {
468 setHeight_Widget_(child, child->flags & unpadded_WidgetFlag ? unpaddedChildSize.y : childSize.y);
469 }
460 } 470 }
461 } 471 }
462 } 472 }
@@ -493,7 +503,8 @@ void arrange_Widget(iWidget *d) {
493 pos.y += child->rect.size.y; 503 pos.y += child->rect.size.y;
494 } 504 }
495 } 505 }
496 else if ((d->flags & resizeChildren_WidgetFlag) == resizeChildren_WidgetFlag) { 506 else if ((d->flags & resizeChildren_WidgetFlag) == resizeChildren_WidgetFlag &&
507 ~child->flags & moveToParentBottomEdge_WidgetFlag) {
497 child->rect.pos = pos; 508 child->rect.pos = pos;
498 } 509 }
499 else if (d->flags & resizeWidthOfChildren_WidgetFlag) { 510 else if (d->flags & resizeWidthOfChildren_WidgetFlag) {
@@ -522,7 +533,9 @@ void arrange_Widget(iWidget *d) {
522 iForEach(ObjectList, j, d->children) { 533 iForEach(ObjectList, j, d->children) {
523 iWidget *child = as_Widget(j.object); 534 iWidget *child = as_Widget(j.object);
524 if (child->flags & 535 if (child->flags &
525 (resizeToParentWidth_WidgetFlag | moveToParentLeftEdge_WidgetFlag | 536 (resizeToParentWidth_WidgetFlag |
537 moveToParentLeftEdge_WidgetFlag |
538 moveToParentBottomEdge_WidgetFlag |
526 moveToParentRightEdge_WidgetFlag)) { 539 moveToParentRightEdge_WidgetFlag)) {
527 arrange_Widget(child); 540 arrange_Widget(child);
528 } 541 }
@@ -542,6 +555,10 @@ void arrange_Widget(iWidget *d) {
542 } 555 }
543 } 556 }
544 } 557 }
558// if (d->flags & moveToParentBottomEdge_WidgetFlag) {
559// /* TODO: Fix this: not DRY. See beginning of method. */
560// d->rect.pos.y = height_Rect(innerRect_Widget_(d->parent)) - height_Rect(d->rect);
561// }
545 if (d->flags & centerHorizontal_WidgetFlag) { 562 if (d->flags & centerHorizontal_WidgetFlag) {
546 centerHorizontal_Widget_(d); 563 centerHorizontal_Widget_(d);
547 } 564 }
@@ -574,6 +591,14 @@ iRect bounds_Widget(const iWidget *d) {
574 return bounds; 591 return bounds;
575} 592}
576 593
594iRect boundsWithoutVisualOffset_Widget(const iWidget *d) {
595 iRect bounds = d->rect;
596 for (const iWidget *w = d->parent; w; w = w->parent) {
597 addv_I2(&bounds.pos, w->rect.pos);
598 }
599 return bounds;
600}
601
577iInt2 localCoord_Widget(const iWidget *d, iInt2 coord) { 602iInt2 localCoord_Widget(const iWidget *d, iInt2 coord) {
578 for (const iWidget *w = d; w; w = w->parent) { 603 for (const iWidget *w = d; w; w = w->parent) {
579 subv_I2(&coord, w->rect.pos); 604 subv_I2(&coord, w->rect.pos);
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 90ccac8e..ad7ce168 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -110,6 +110,8 @@ enum iWidgetFlag {
110#define dragged_WidgetFlag iBit64(54) 110#define dragged_WidgetFlag iBit64(54)
111#define hittable_WidgetFlag iBit64(55) 111#define hittable_WidgetFlag iBit64(55)
112#define safePadding_WidgetFlag iBit64(56) /* padded using safe area insets */ 112#define safePadding_WidgetFlag iBit64(56) /* padded using safe area insets */
113#define moveToParentBottomEdge_WidgetFlag iBit64(57)
114#define parentCannotResizeHeight_WidgetFlag iBit64(58)
113 115
114enum iWidgetAddPos { 116enum iWidgetAddPos {
115 back_WidgetAddPos, 117 back_WidgetAddPos,
@@ -165,6 +167,7 @@ const iString *id_Widget (const iWidget *);
165int64_t flags_Widget (const iWidget *); 167int64_t flags_Widget (const iWidget *);
166iRect bounds_Widget (const iWidget *); /* outer bounds */ 168iRect bounds_Widget (const iWidget *); /* outer bounds */
167iRect innerBounds_Widget (const iWidget *); 169iRect innerBounds_Widget (const iWidget *);
170iRect boundsWithoutVisualOffset_Widget(const iWidget *);
168iInt2 localCoord_Widget (const iWidget *, iInt2 coord); 171iInt2 localCoord_Widget (const iWidget *, iInt2 coord);
169iBool contains_Widget (const iWidget *, iInt2 coord); 172iBool contains_Widget (const iWidget *, iInt2 coord);
170iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand); 173iBool containsExpanded_Widget (const iWidget *, iInt2 coord, int expand);
diff --git a/src/ui/window.c b/src/ui/window.c
index 1249a0b4..bd2b1493 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -191,7 +191,7 @@ static const iMenuItem navMenuItems_[] = {
191 { add_Icon " New Tab", 't', KMOD_PRIMARY, "tabs.new" }, 191 { add_Icon " New Tab", 't', KMOD_PRIMARY, "tabs.new" },
192 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, 192 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
193 { "---", 0, 0, NULL }, 193 { "---", 0, 0, NULL },
194 { download_Icon " Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, 194 { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" },
195 { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" }, 195 { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" },
196 { "---", 0, 0, NULL }, 196 { "---", 0, 0, NULL },
197 { leftHalf_Icon " Toggle Left Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, 197 { leftHalf_Icon " Toggle Left Sidebar", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" },
@@ -259,7 +259,7 @@ static const iMenuItem fileMenuItems_[] = {
259 { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" }, 259 { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" },
260 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, 260 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
261 { "---", 0, 0, NULL }, 261 { "---", 0, 0, NULL },
262 { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, 262 { saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" },
263}; 263};
264 264
265static const iMenuItem editMenuItems_[] = { 265static const iMenuItem editMenuItems_[] = {
@@ -576,7 +576,9 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
576 if (isPhone) { 576 if (isPhone) {
577 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", 577 static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar",
578 "navbar.ident", "navbar.home", "navbar.menu" }; 578 "navbar.ident", "navbar.home", "navbar.menu" };
579 setFlags_Widget(findWidget_App("toolbar"), hidden_WidgetFlag, isLandscape_App()); 579 iWidget *toolBar = findWidget_App("toolbar");
580 setVisualOffset_Widget(toolBar, 0, 0, 0);
581 setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App());
580 iForIndices(i, buttons) { 582 iForIndices(i, buttons) {
581 iLabelWidget *btn = findChild_Widget(navBar, buttons[i]); 583 iLabelWidget *btn = findChild_Widget(navBar, buttons[i]);
582 setFlags_Widget(as_Widget(btn), hidden_WidgetFlag, isPortrait_App()); 584 setFlags_Widget(as_Widget(btn), hidden_WidgetFlag, isPortrait_App());
@@ -936,7 +938,6 @@ static void setupUserInterface_Window(iWindow *d) {
936 setBackgroundColor_Widget(winBar, uiBackground_ColorId); 938 setBackgroundColor_Widget(winBar, uiBackground_ColorId);
937 } 939 }
938#endif 940#endif
939
940 /* Navigation bar. */ { 941 /* Navigation bar. */ {
941 iWidget *navBar = new_Widget(); 942 iWidget *navBar = new_Widget();
942 setId_Widget(navBar, "navbar"); 943 setId_Widget(navBar, "navbar");
@@ -1119,8 +1120,9 @@ static void setupUserInterface_Window(iWindow *d) {
1119 setBackgroundColor_Widget(searchBar, uiBackground_ColorId); 1120 setBackgroundColor_Widget(searchBar, uiBackground_ColorId);
1120 setCommandHandler_Widget(searchBar, handleSearchBarCommands_); 1121 setCommandHandler_Widget(searchBar, handleSearchBarCommands_);
1121 addChildFlags_Widget( 1122 addChildFlags_Widget(
1122 searchBar, iClob(new_LabelWidget(magnifyingGlass_Icon " Text", NULL)), frameless_WidgetFlag); 1123 searchBar, iClob(new_LabelWidget(magnifyingGlass_Icon, NULL)), frameless_WidgetFlag);
1123 iInputWidget *input = new_InputWidget(0); 1124 iInputWidget *input = new_InputWidget(0);
1125 setHint_InputWidget(input, "Find text on page");
1124 setSelectAllOnFocus_InputWidget(input, iTrue); 1126 setSelectAllOnFocus_InputWidget(input, iTrue);
1125 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ 1127 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */
1126 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), 1128 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag),
@@ -1133,10 +1135,12 @@ static void setupUserInterface_Window(iWindow *d) {
1133 /* Bottom toolbar. */ 1135 /* Bottom toolbar. */
1134 if (isPhone_iOS()) { 1136 if (isPhone_iOS()) {
1135 iWidget *toolBar = new_Widget(); 1137 iWidget *toolBar = new_Widget();
1136 addChild_Widget(div, iClob(toolBar)); 1138 addChild_Widget(d->root, iClob(toolBar));
1137 setId_Widget(toolBar, "toolbar"); 1139 setId_Widget(toolBar, "toolbar");
1138 setCommandHandler_Widget(toolBar, handleToolBarCommands_); 1140 setCommandHandler_Widget(toolBar, handleToolBarCommands_);
1139 setFlags_Widget(toolBar, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag | 1141 setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag |
1142 parentCannotResizeHeight_WidgetFlag |
1143 resizeWidthOfChildren_WidgetFlag |
1140 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); 1144 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
1141 setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); 1145 setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId);
1142 addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), frameless_WidgetFlag); 1146 addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), frameless_WidgetFlag);
@@ -1214,6 +1218,21 @@ static void setupUserInterface_Window(iWindow *d) {
1214 updateMetrics_Window_(d); 1218 updateMetrics_Window_(d);
1215} 1219}
1216 1220
1221void showToolbars_Window(iWindow *d, iBool show) {
1222 if (isLandscape_App()) return;
1223 iWidget *toolBar = findChild_Widget(d->root, "toolbar");
1224 if (!toolBar) return;
1225 const int height = rootSize_Window(d).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar));
1226 if (show && !isVisible_Widget(toolBar)) {
1227 setFlags_Widget(toolBar, hidden_WidgetFlag, iFalse);
1228 setVisualOffset_Widget(toolBar, 0, 200, easeOut_AnimFlag);
1229 }
1230 else if (!show && isVisible_Widget(toolBar)) {
1231 setFlags_Widget(toolBar, hidden_WidgetFlag, iTrue);
1232 setVisualOffset_Widget(toolBar, height, 200, easeOut_AnimFlag);
1233 }
1234}
1235
1217static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) { 1236static void updateRootSize_Window_(iWindow *d, iBool notifyAlways) {
1218 iInt2 *size = &d->root->rect.size; 1237 iInt2 *size = &d->root->rect.size;
1219 const iInt2 oldSize = *size; 1238 const iInt2 oldSize = *size;
diff --git a/src/ui/window.h b/src/ui/window.h
index cd2e3814..7303acb2 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -91,6 +91,7 @@ void setCursor_Window (iWindow *, int cursor);
91void setSnap_Window (iWindow *, int snapMode); 91void setSnap_Window (iWindow *, int snapMode);
92void setKeyboardHeight_Window(iWindow *, int height); 92void setKeyboardHeight_Window(iWindow *, int height);
93void dismissPortraitPhoneSidebars_Window (iWindow *); 93void dismissPortraitPhoneSidebars_Window (iWindow *);
94void showToolbars_Window (iWindow *, iBool show);
94iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); 95iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);
95 96
96uint32_t id_Window (const iWindow *); 97uint32_t id_Window (const iWindow *);