summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 *);