summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-02-20 08:33:15 +0200
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-02-20 08:33:15 +0200
commit0c4fc5d9189510ff88369709f018afc550aa0b54 (patch)
tree88f743ca8e6ab22e8eea781d116cdfc34c8b91d0
parentc1cc59ee6a6d8b88d6f49eb59ffa871d75c75866 (diff)
iOS: Tweaks and changes for phone mode
Phone mode uses a modified user interface. Work in progress...
-rw-r--r--res/LaunchScreen.storyboard7
-rw-r--r--src/app.c14
-rw-r--r--src/app.h9
-rw-r--r--src/ios.h2
-rw-r--r--src/ios.m25
-rw-r--r--src/prefs.c3
-rw-r--r--src/ui/documentwidget.c14
-rw-r--r--src/ui/listwidget.c10
-rw-r--r--src/ui/lookupwidget.c17
-rw-r--r--src/ui/touch.c85
-rw-r--r--src/ui/util.c20
-rw-r--r--src/ui/widget.c8
-rw-r--r--src/ui/window.c63
13 files changed, 197 insertions, 80 deletions
diff --git a/res/LaunchScreen.storyboard b/res/LaunchScreen.storyboard
new file mode 100644
index 00000000..f9a048ed
--- /dev/null
+++ b/res/LaunchScreen.storyboard
@@ -0,0 +1,7 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3 <dependencies>
4 <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
5 </dependencies>
6 <scenes/>
7</document>
diff --git a/src/app.c b/src/app.c
index bb8ebb42..38367210 100644
--- a/src/app.c
+++ b/src/app.c
@@ -941,6 +941,20 @@ iMimeHooks *mimeHooks_App(void) {
941 return app_.mimehooks; 941 return app_.mimehooks;
942} 942}
943 943
944iBool isLandscape_App(void) {
945 const iApp *d = &app_;
946 const iInt2 size = rootSize_Window(d->window);
947 return size.x > size.y;
948}
949
950enum iAppDeviceType deviceType_App(void) {
951#if defined (iPlatformAppleMobile)
952 return isPhone_iOS() ? phone_AppDeviceType : tablet_AppDeviceType;
953#else
954 return desktop_AppDeviceType;
955#endif
956}
957
944iGmCerts *certs_App(void) { 958iGmCerts *certs_App(void) {
945 return app_.certs; 959 return app_.certs;
946} 960}
diff --git a/src/app.h b/src/app.h
index 54c60d10..e29745c3 100644
--- a/src/app.h
+++ b/src/app.h
@@ -38,6 +38,12 @@ iDeclareType(MimeHooks)
38iDeclareType(Visited) 38iDeclareType(Visited)
39iDeclareType(Window) 39iDeclareType(Window)
40 40
41enum iAppDeviceType {
42 desktop_AppDeviceType,
43 tablet_AppDeviceType,
44 phone_AppDeviceType,
45};
46
41enum iAppEventMode { 47enum iAppEventMode {
42 waitForNewEvents_AppEventMode, 48 waitForNewEvents_AppEventMode,
43 postedEventsOnly_AppEventMode, 49 postedEventsOnly_AppEventMode,
@@ -61,6 +67,9 @@ void refresh_App (void);
61iBool isRefreshPending_App (void); 67iBool isRefreshPending_App (void);
62uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ 68uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */
63 69
70iBool isLandscape_App (void);
71iLocalDef iBool isPortrait_App (void) { return !isLandscape_App(); }
72enum iAppDeviceType deviceType_App (void);
64iGmCerts * certs_App (void); 73iGmCerts * certs_App (void);
65iVisited * visited_App (void); 74iVisited * visited_App (void);
66iBookmarks * bookmarks_App (void); 75iBookmarks * bookmarks_App (void);
diff --git a/src/ios.h b/src/ios.h
index 90570e20..60841aee 100644
--- a/src/ios.h
+++ b/src/ios.h
@@ -28,4 +28,6 @@ iDeclareType(Window)
28 28
29void setupApplication_iOS (void); 29void setupApplication_iOS (void);
30void setupWindow_iOS (iWindow *window); 30void setupWindow_iOS (iWindow *window);
31iBool isPhone_iOS (void);
32void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom);
31iBool processEvent_iOS (const SDL_Event *); 33iBool processEvent_iOS (const SDL_Event *);
diff --git a/src/ios.m b/src/ios.m
index aca3bf0a..5abf87df 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
29#import <UIKit/UIKit.h> 29#import <UIKit/UIKit.h>
30 30
31static iBool isSystemDarkMode_ = iFalse; 31static iBool isSystemDarkMode_ = iFalse;
32static iBool isPhone_ = iFalse;
32 33
33static void enableMouse_(iBool yes) { 34static void enableMouse_(iBool yes) {
34 SDL_EventState(SDL_MOUSEBUTTONDOWN, yes); 35 SDL_EventState(SDL_MOUSEBUTTONDOWN, yes);
@@ -38,6 +39,10 @@ static void enableMouse_(iBool yes) {
38 39
39void setupApplication_iOS(void) { 40void setupApplication_iOS(void) {
40 enableMouse_(iFalse); 41 enableMouse_(iFalse);
42 NSString *deviceModel = [[UIDevice currentDevice] model];
43 if ([deviceModel isEqualToString:@"iPhone"]) {
44 isPhone_ = iTrue;
45 }
41} 46}
42 47
43static UIViewController *viewController_(iWindow *window) { 48static UIViewController *viewController_(iWindow *window) {
@@ -61,7 +66,27 @@ static iBool isDarkMode_(iWindow *window) {
61 return iFalse; 66 return iFalse;
62} 67}
63 68
69void safeAreaInsets_iOS(float *left, float *top, float *right, float *bottom) {
70 iWindow *window = get_Window();
71 UIViewController *ctl = viewController_(window);
72 if (@available(iOS 11.0, *)) {
73 const UIEdgeInsets safe = ctl.view.safeAreaInsets;
74 *left = safe.left * window->pixelRatio;
75 *top = safe.top * window->pixelRatio;
76 *right = safe.right * window->pixelRatio;
77 *bottom = safe.bottom * window->pixelRatio;
78 } else {
79 // Fallback on earlier versions
80 *left = *top = *right = *bottom = 0.0f;
81 }
82}
83
84iBool isPhone_iOS(void) {
85 return isPhone_;
86}
87
64void setupWindow_iOS(iWindow *window) { 88void setupWindow_iOS(iWindow *window) {
89 UIViewController *ctl = viewController_(window);
65 isSystemDarkMode_ = isDarkMode_(window); 90 isSystemDarkMode_ = isDarkMode_(window);
66 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); 91 postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0);
67} 92}
diff --git a/src/prefs.c b/src/prefs.c
index 9d89f2f5..056ae267 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -52,6 +52,9 @@ void init_Prefs(iPrefs *d) {
52 init_String(&d->httpProxy); 52 init_String(&d->httpProxy);
53 init_String(&d->downloadDir); 53 init_String(&d->downloadDir);
54 init_String(&d->searchUrl); 54 init_String(&d->searchUrl);
55#if defined (iPlatformAppleMobile)
56 d->hoverLink = iFalse;
57#endif
55} 58}
56 59
57void deinit_Prefs(iPrefs *d) { 60void deinit_Prefs(iPrefs *d) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 40d83cec..cb075c49 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1701,6 +1701,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1701 iWidget *sizer = new_Widget(); 1701 iWidget *sizer = new_Widget();
1702 setSize_Widget(sizer, init_I2(gap_UI * 90, 1)); 1702 setSize_Widget(sizer, init_I2(gap_UI * 90, 1));
1703 addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag); 1703 addChildFlags_Widget(dlg, iClob(sizer), frameless_WidgetFlag);
1704 setFlags_Widget(dlg, centerHorizontal_WidgetFlag, iFalse);
1705 setPos_Widget(dlg, bottomLeft_Rect(bounds_Widget(findWidget_App("navbar.lock"))));
1704 arrange_Widget(dlg); 1706 arrange_Widget(dlg);
1705 addAction_Widget(dlg, SDLK_ESCAPE, 0, "message.ok"); 1707 addAction_Widget(dlg, SDLK_ESCAPE, 0, "message.ok");
1706 addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok"); 1708 addAction_Widget(dlg, SDLK_SPACE, 0, "message.ok");
@@ -2350,16 +2352,20 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2350 } 2352 }
2351 } 2353 }
2352 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 2354 else if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
2355 /* TODO: Maybe clean this up a bit? Wheel events are used for scrolling
2356 but they are calculated differently based on device/mouse/trackpad. */
2353 const iInt2 mouseCoord = mouseCoord_Window(get_Window()); 2357 const iInt2 mouseCoord = mouseCoord_Window(get_Window());
2354#if defined (iPlatformApple) 2358#if defined (iPlatformApple)
2355 /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify 2359 /* On macOS, we handle both trackpad and mouse events. We expect SDL to identify
2356 which device is sending the event. */ 2360 which device is sending the event. */
2357 if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */ 2361 if (ev->wheel.which == 0) { /* Trackpad with precise scrolling w/inertia. */
2358 stop_Anim(&d->scrollY); 2362 stop_Anim(&d->scrollY);
2359 iInt2 wheel = mulf_I2(init_I2(ev->wheel.x, ev->wheel.y), get_Window()->pixelRatio); 2363 iInt2 wheel = init_I2(ev->wheel.x, ev->wheel.y);
2360#if defined (iPlatformAppleMobile) 2364# if defined (iPlatformAppleMobile)
2361 wheel.x = -wheel.x; 2365 wheel.x = -wheel.x;
2362#else 2366# else
2367 /* Wheel mounts are in points. */
2368 mulfv_I2(&wheel, get_Window()->pixelRatio);
2363 /* Only scroll on one axis at a time. */ 2369 /* Only scroll on one axis at a time. */
2364 if (iAbs(wheel.x) > iAbs(wheel.y)) { 2370 if (iAbs(wheel.x) > iAbs(wheel.y)) {
2365 wheel.y = 0; 2371 wheel.y = 0;
@@ -2367,7 +2373,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
2367 else { 2373 else {
2368 wheel.x = 0; 2374 wheel.x = 0;
2369 } 2375 }
2370#endif 2376# endif
2371 scroll_DocumentWidget_(d, -wheel.y); 2377 scroll_DocumentWidget_(d, -wheel.y);
2372 scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x, 0); 2378 scrollWideBlock_DocumentWidget_(d, mouseCoord, wheel.x, 0);
2373 } 2379 }
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index b27107df..2130400c 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -292,12 +292,16 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
292 setHoverItem_ListWidget_(d, hover); 292 setHoverItem_ListWidget_(d, hover);
293 } 293 }
294 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { 294 if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) {
295 int amount = -ev->wheel.y;
295#if defined (iPlatformApple) 296#if defined (iPlatformApple)
296 /* Momentum scrolling. */ 297# if defined (iPlatformAppleDesktop)
297 scrollOffset_ListWidget(d, -ev->wheel.y * get_Window()->pixelRatio); 298 /* Momentum scrolling (in points). */
299 amount *= get_Window()->pixelRatio;
300# endif
298#else 301#else
299 scrollOffset_ListWidget(d, -ev->wheel.y * 3 * d->itemHeight); 302 amount *= 3 * d->itemHeight;
300#endif 303#endif
304 scrollOffset_ListWidget(d, amount);
301 return iTrue; 305 return iTrue;
302 } 306 }
303 switch (processEvent_Click(&d->click, ev)) { 307 switch (processEvent_Click(&d->click, ev)) {
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index ab0a009a..7d6052e2 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -629,9 +629,6 @@ static iBool moveCursor_LookupWidget_(iLookupWidget *d, int delta) {
629static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { 629static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
630 iWidget *w = as_Widget(d); 630 iWidget *w = as_Widget(d);
631 const char *cmd = command_UserEvent(ev); 631 const char *cmd = command_UserEvent(ev);
632// if (ev->type == SDL_MOUSEMOTION && contains_Widget(w, init_I2(ev->motion.x, ev->motion.y))) {
633// setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW);
634// }
635 if (isCommand_Widget(w, ev, "lookup.ready")) { 632 if (isCommand_Widget(w, ev, "lookup.ready")) {
636 /* Take the results and present them in the list. */ 633 /* Take the results and present them in the list. */
637 presentResults_LookupWidget_(d); 634 presentResults_LookupWidget_(d);
@@ -640,17 +637,23 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
640 if (isResize_UserEvent(ev) || (equal_Command(cmd, "layout.changed") && 637 if (isResize_UserEvent(ev) || (equal_Command(cmd, "layout.changed") &&
641 equal_Rangecc(range_Command(cmd, "id"), "navbar"))) { 638 equal_Rangecc(range_Command(cmd, "id"), "navbar"))) {
642 /* Position the lookup popup under the URL bar. */ { 639 /* Position the lookup popup under the URL bar. */ {
640 const iWindow *window = get_Window();
641 const iInt2 rootSize = rootSize_Window(window);
642 const iRect navBarBounds = bounds_Widget(findWidget_App("navbar"));
643 setSize_Widget(w, init_I2(width_Widget(findWidget_App("url")), 643 setSize_Widget(w, init_I2(width_Widget(findWidget_App("url")),
644 get_Window()->root->rect.size.y / 2)); 644 (rootSize.y - bottom_Rect(navBarBounds)) / 2));
645 setPos_Widget(w, bottomLeft_Rect(bounds_Widget(findWidget_App("url"))));
645#if defined (iPlatformAppleMobile) 646#if defined (iPlatformAppleMobile)
646 /* TODO: Ask the system how tall the keyboard is. */ { 647 /* TODO: Ask the system how tall the keyboard is. */ {
647 const iInt2 rootSize = rootSize_Window(get_Window()); 648 if (isLandscape_App()) {
648 if (rootSize.x > rootSize.y) {
649 w->rect.size.y = rootSize.y * 4 / 10; 649 w->rect.size.y = rootSize.y * 4 / 10;
650 } 650 }
651 else if (deviceType_App() == phone_AppDeviceType) {
652 w->rect.size.x = rootSize.x;
653 w->rect.pos.x = 0;
654 }
651 } 655 }
652#endif 656#endif
653 setPos_Widget(w, bottomLeft_Rect(bounds_Widget(findWidget_App("url"))));
654 arrange_Widget(w); 657 arrange_Widget(w);
655 } 658 }
656 updateVisible_ListWidget(d->list); 659 updateVisible_ListWidget(d->list);
diff --git a/src/ui/touch.c b/src/ui/touch.c
index d7d2bca1..8c39793e 100644
--- a/src/ui/touch.c
+++ b/src/ui/touch.c
@@ -50,7 +50,7 @@ struct Impl_Touch {
50 iFloat3 startPos; 50 iFloat3 startPos;
51 uint32_t posTime[numHistory_Touch_]; 51 uint32_t posTime[numHistory_Touch_];
52 iFloat3 pos[numHistory_Touch_]; 52 iFloat3 pos[numHistory_Touch_];
53 iFloat3 remainder; 53 iFloat3 accum;
54}; 54};
55 55
56iLocalDef void pushPos_Touch_(iTouch *d, const iFloat3 pos, uint32_t time) { 56iLocalDef void pushPos_Touch_(iTouch *d, const iFloat3 pos, uint32_t time) {
@@ -103,7 +103,7 @@ static iBool isStationary_Touch_(const iTouch *d) {
103} 103}
104 104
105static void dispatchClick_Touch_(const iTouch *d, int button) { 105static void dispatchClick_Touch_(const iTouch *d, int button) {
106 const iFloat3 tapPos = d->pos[0]; //divf_F3(add_F3(d->pos[0], d->startPos), 2); 106 const iFloat3 tapPos = d->pos[0];
107 SDL_MouseButtonEvent btn = { 107 SDL_MouseButtonEvent btn = {
108 .type = SDL_MOUSEBUTTONDOWN, 108 .type = SDL_MOUSEBUTTONDOWN,
109 .button = button, 109 .button = button,
@@ -151,13 +151,14 @@ static void update_TouchState_(void *ptr) {
151 } 151 }
152 /* Update/cancel momentum scrolling. */ { 152 /* Update/cancel momentum scrolling. */ {
153 const float minSpeed = 15.0f; 153 const float minSpeed = 15.0f;
154 const float momFriction = 0.98f; /* per step */ 154 const float momFriction = 0.985f; /* per step */
155 const float stepDurationMs = 1000.0f / 120.0f; 155 const float stepDurationMs = 1000.0f / 120.0f;
156 double momAvailMs = nowTime - d->lastMomTime; 156 double momAvailMs = nowTime - d->lastMomTime;
157 int numSteps = (int) (momAvailMs / stepDurationMs); 157 int numSteps = (int) (momAvailMs / stepDurationMs);
158 d->lastMomTime += numSteps * stepDurationMs; 158 d->lastMomTime += numSteps * stepDurationMs;
159 numSteps = iMin(numSteps, 10); /* don't spend too much time here */ 159 numSteps = iMin(numSteps, 10); /* don't spend too much time here */
160// printf("mom steps:%d\n", numSteps); 160// printf("mom steps:%d\n", numSteps);
161 iWindow *window = get_Window();
161 iForEach(Array, m, d->moms) { 162 iForEach(Array, m, d->moms) {
162 if (numSteps == 0) break; 163 if (numSteps == 0) break;
163 iMomentum *mom = m.value; 164 iMomentum *mom = m.value;
@@ -165,15 +166,15 @@ static void update_TouchState_(void *ptr) {
165 mulvf_F3(&mom->velocity, momFriction); 166 mulvf_F3(&mom->velocity, momFriction);
166 addv_F3(&mom->accum, mulf_F3(mom->velocity, stepDurationMs / 1000.0f)); 167 addv_F3(&mom->accum, mulf_F3(mom->velocity, stepDurationMs / 1000.0f));
167 } 168 }
168 const iInt2 pixels = initF3_I2(mom->accum); 169 const iInt2 points = initF3_I2(mom->accum);
169 if (pixels.x || pixels.y) { 170 if (points.x || points.y) {
170 subv_F3(&mom->accum, initI2_F3(pixels)); 171 subv_F3(&mom->accum, initI2_F3(points));
171 dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ 172 dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){
172 .type = SDL_MOUSEWHEEL, 173 .type = SDL_MOUSEWHEEL,
173 .timestamp = nowTime, 174 .timestamp = nowTime,
174 .which = 0, /* means "precise scrolling" in DocumentWidget */ 175 .which = 0, /* means "precise scrolling" in DocumentWidget */
175 .x = pixels.x, 176 .x = points.x,
176 .y = pixels.y 177 .y = points.y
177 }); 178 });
178 } 179 }
179 if (length_F3(mom->velocity) < minSpeed) { 180 if (length_F3(mom->velocity) < minSpeed) {
@@ -187,6 +188,17 @@ static void update_TouchState_(void *ptr) {
187 } 188 }
188} 189}
189 190
191static void dispatchMotion_Touch_(iFloat3 pos, int buttonState) {
192 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseMotionEvent){
193 .type = SDL_MOUSEMOTION,
194 .timestamp = SDL_GetTicks(),
195 .which = SDL_TOUCH_MOUSEID,
196 .state = buttonState,
197 .x = x_F3(pos),
198 .y = y_F3(pos)
199 });
200}
201
190static void dispatchButtonUp_Touch_(iFloat3 pos) { 202static void dispatchButtonUp_Touch_(iFloat3 pos) {
191 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){ 203 dispatchEvent_Widget(get_Window()->root, (SDL_Event *) &(SDL_MouseButtonEvent){
192 .type = SDL_MOUSEBUTTONUP, 204 .type = SDL_MOUSEBUTTONUP,
@@ -206,12 +218,10 @@ iBool processEvent_Touch(const SDL_Event *ev) {
206 return iFalse; 218 return iFalse;
207 } 219 }
208 iTouchState *d = touchState_(); 220 iTouchState *d = touchState_();
209 const SDL_TouchFingerEvent *fing = &ev->tfinger;
210 iWindow *window = get_Window(); 221 iWindow *window = get_Window();
211 const iInt2 rootSize = rootSize_Window(window); 222 const iInt2 rootSize = rootSize_Window(window);
212 const iFloat3 pos = init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0); 223 const SDL_TouchFingerEvent *fing = &ev->tfinger;
213 //printf("%2d: %f: touch %f, %f\n", ev->type, z_F3(pos), x_F3(pos), y_F3(pos)); 224 const iFloat3 pos = init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0); /* pixels */
214 //fflush(stdout);
215 const uint32_t nowTime = SDL_GetTicks(); 225 const uint32_t nowTime = SDL_GetTicks();
216 if (ev->type == SDL_FINGERDOWN) { 226 if (ev->type == SDL_FINGERDOWN) {
217 /* Register the new touch. */ 227 /* Register the new touch. */
@@ -225,6 +235,9 @@ iBool processEvent_Touch(const SDL_Event *ev) {
225 edge = right_TouchEdge; 235 edge = right_TouchEdge;
226 } 236 }
227 iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos)))); 237 iWidget *aff = hitChild_Widget(window->root, init_I2(iRound(x), iRound(y_F3(pos))));
238 /* TODO: We must retain a reference to the affinity widget, or otherwise it might
239 be destroyed during the gesture. */
240// printf("aff:%p (%s)\n", aff, aff ? class_Widget(aff)->name : "-");
228 if (flags_Widget(aff) & touchDrag_WidgetFlag) { 241 if (flags_Widget(aff) & touchDrag_WidgetFlag) {
229 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){ 242 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseButtonEvent){
230 .type = SDL_MOUSEBUTTONDOWN, 243 .type = SDL_MOUSEBUTTONDOWN,
@@ -248,46 +261,24 @@ iBool processEvent_Touch(const SDL_Event *ev) {
248 .pos = pos 261 .pos = pos
249 }); 262 });
250 /* Some widgets rely on hover state. */ 263 /* Some widgets rely on hover state. */
251 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseMotionEvent){ 264 dispatchMotion_Touch_(pos, 0);
252 .type = SDL_MOUSEMOTION,
253 .timestamp = fing->timestamp,
254 .which = SDL_TOUCH_MOUSEID,
255 .x = x_F3(pos),
256 .y = y_F3(pos)
257 });
258 addTicker_App(update_TouchState_, d); 265 addTicker_App(update_TouchState_, d);
259 } 266 }
260 else if (ev->type == SDL_FINGERMOTION) { 267 else if (ev->type == SDL_FINGERMOTION) {
261 iTouch *touch = find_TouchState_(d, fing->fingerId); 268 iTouch *touch = find_TouchState_(d, fing->fingerId);
262 if (touch && touch->affinity) { 269 if (touch && touch->affinity) {
263 if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { 270 if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) {
264 dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseMotionEvent){ 271 dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK);
265 .type = SDL_MOUSEMOTION,
266 .timestamp = fing->timestamp,
267 .which = SDL_TOUCH_MOUSEID,
268 .state = SDL_BUTTON_LMASK,
269 .x = x_F3(pos),
270 .y = y_F3(pos)
271 });
272 return iTrue; 272 return iTrue;
273 } 273 }
274 /*dispatchEvent_Widget(window->root, (SDL_Event *) &(SDL_MouseMotionEvent){
275 .type = SDL_MOUSEMOTION,
276 .timestamp = fing->timestamp,
277 .which = SDL_TOUCH_MOUSEID,
278 .x = x_F3(pos),
279 .y = y_F3(pos)
280 });*/
281 /* Update touch position. */ 274 /* Update touch position. */
282 pushPos_Touch_(touch, pos, nowTime); 275 pushPos_Touch_(touch, pos, nowTime);
283 const iFloat3 amount = add_F3(touch->remainder, 276 const iFloat3 amount = mul_F3(init_F3(fing->dx, fing->dy, 0),
284 divf_F3(mul_F3(init_F3(fing->dx, fing->dy, 0), 277 init_F3(rootSize.x, rootSize.y, 0));
285 init_F3(rootSize.x, rootSize.y, 0)), 278 addv_F3(&touch->accum, amount);
286 window->pixelRatio)); 279 iInt2 pixels = initF3_I2(touch->accum);
287 iInt2 pixels = init_I2(iRound(x_F3(amount)), iRound(y_F3(amount)));
288 /* We're reporting scrolling as full points, so keep track of the precise distance. */ 280 /* We're reporting scrolling as full points, so keep track of the precise distance. */
289 iFloat3 remainder = sub_F3(amount, initI2_F3(pixels)); 281 subv_F3(&touch->accum, initI2_F3(pixels));
290 touch->remainder = remainder;
291 if (!touch->hasMoved && !isStationary_Touch_(touch)) { 282 if (!touch->hasMoved && !isStationary_Touch_(touch)) {
292 touch->hasMoved = iTrue; 283 touch->hasMoved = iTrue;
293 } 284 }
@@ -301,15 +292,17 @@ iBool processEvent_Touch(const SDL_Event *ev) {
301 if (touch->edge) { 292 if (touch->edge) {
302 pixels.y = 0; 293 pixels.y = 0;
303 } 294 }
295// printf("%p (%s) py: %i wy: %f acc: %f\n",
296// touch->affinity,
297// class_Widget(touch->affinity)->name,
298// pixels.y, y_F3(amount), y_F3(touch->accum));
304 if (pixels.x || pixels.y) { 299 if (pixels.x || pixels.y) {
305// printf("%p (%s) wy: %f\n", touch->affinity, class_Widget(touch->affinity)->name,
306// fing->dy * rootSize.y / window->pixelRatio);
307 dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ 300 dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){
308 .type = SDL_MOUSEWHEEL, 301 .type = SDL_MOUSEWHEEL,
309 .timestamp = SDL_GetTicks(), 302 .timestamp = SDL_GetTicks(),
310 .which = 0, /* means "precise scrolling" in DocumentWidget */ 303 .which = 0, /* means "precise scrolling" in DocumentWidget */
311 .x = pixels.x, 304 .x = pixels.x,
312 .y = pixels.y 305 .y = pixels.y,
313 }); 306 });
314 /* TODO: Keep increasing movement if the direction is the same. */ 307 /* TODO: Keep increasing movement if the direction is the same. */
315 clearWidgetMomentum_TouchState_(d, touch->affinity); 308 clearWidgetMomentum_TouchState_(d, touch->affinity);
@@ -340,7 +333,7 @@ iBool processEvent_Touch(const SDL_Event *ev) {
340 else { 333 else {
341 const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex_Touch_]; 334 const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex_Touch_];
342 const float minVelocity = 400.0f; 335 const float minVelocity = 400.0f;
343 if (elapsed < 40) { 336 if (elapsed < 75) {
344 velocity = divf_F3(sub_F3(pos, touch->pos[lastIndex_Touch_]), 337 velocity = divf_F3(sub_F3(pos, touch->pos[lastIndex_Touch_]),
345 (float) elapsed / 1000.0f); 338 (float) elapsed / 1000.0f);
346 if (fabsf(x_F3(velocity)) < minVelocity) { 339 if (fabsf(x_F3(velocity)) < minVelocity) {
@@ -350,9 +343,11 @@ iBool processEvent_Touch(const SDL_Event *ev) {
350 setY_F3(&velocity, 0.0f); 343 setY_F3(&velocity, 0.0f);
351 } 344 }
352 } 345 }
346// printf("elap:%ums vel:%f\n", elapsed, length_F3(velocity));
353 pushPos_Touch_(touch, pos, nowTime); 347 pushPos_Touch_(touch, pos, nowTime);
354 /* If short and didn't move far, do a tap (left click). */ 348 /* If short and didn't move far, do a tap (left click). */
355 if (duration < longPressSpanMs_ && isStationary_Touch_(touch)) { 349 if (duration < longPressSpanMs_ && isStationary_Touch_(touch)) {
350 dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK);
356 dispatchClick_Touch_(touch, SDL_BUTTON_LEFT); 351 dispatchClick_Touch_(touch, SDL_BUTTON_LEFT);
357 } 352 }
358 else if (length_F3(velocity) > 0.0f) { 353 else if (length_F3(velocity) > 0.0f) {
diff --git a/src/ui/util.c b/src/ui/util.c
index 850ca818..1ff1b7f7 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -37,6 +37,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
37#include "text.h" 37#include "text.h"
38#include "window.h" 38#include "window.h"
39 39
40#if defined (iPlatformAppleMobile)
41# include "../ios.h"
42#endif
43
40#include <the_Foundation/math.h> 44#include <the_Foundation/math.h>
41#include <the_Foundation/path.h> 45#include <the_Foundation/path.h>
42#include <SDL_timer.h> 46#include <SDL_timer.h>
@@ -472,13 +476,19 @@ void openMenu_Widget(iWidget *d, iInt2 coord) {
472 } 476 }
473#endif 477#endif
474 const iRect bounds = bounds_Widget(d); 478 const iRect bounds = bounds_Widget(d);
475 const int leftExcess = -left_Rect(bounds); 479 int leftExcess = -left_Rect(bounds);
476 const int rightExcess = right_Rect(bounds) - rootSize.x; 480 int rightExcess = right_Rect(bounds) - rootSize.x;
477 int topExcess = -top_Rect(bounds); 481 int topExcess = -top_Rect(bounds);
478 const int bottomExcess = bottom_Rect(bounds) - rootSize.y; 482 int bottomExcess = bottom_Rect(bounds) - rootSize.y;
479#if defined (iPlatformAppleMobile) 483#if defined (iPlatformAppleMobile)
480 /* Reserve space for the system status bar. */ 484 /* Reserve space for the system status bar. */ {
481 topExcess += 4.5 * gap_UI; 485 float l, t, r, b;
486 safeAreaInsets_iOS(&l, &t, &r, &b);
487 topExcess += t;
488 bottomExcess += b;
489 leftExcess += l;
490 rightExcess += r;
491 }
482#endif 492#endif
483 if (bottomExcess > 0) { 493 if (bottomExcess > 0) {
484 d->rect.pos.y -= bottomExcess; 494 d->rect.pos.y -= bottomExcess;
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 331192f9..128aa39c 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -126,11 +126,15 @@ const iString *id_Widget(const iWidget *d) {
126} 126}
127 127
128int64_t flags_Widget(const iWidget *d) { 128int64_t flags_Widget(const iWidget *d) {
129 return d->flags; 129 return d ? d->flags : 0;
130} 130}
131 131
132void setFlags_Widget(iWidget *d, int64_t flags, iBool set) { 132void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
133 if (d) { 133 if (d) {
134 if (deviceType_App() == phone_AppDeviceType) {
135 /* Phones rarely have keyboards attached so don't bother with the shortcuts. */
136 flags &= ~drawKey_WidgetFlag;
137 }
134 iChangeFlags(d->flags, flags, set); 138 iChangeFlags(d->flags, flags, set);
135 if (flags & keepOnTop_WidgetFlag) { 139 if (flags & keepOnTop_WidgetFlag) {
136 if (set) { 140 if (set) {
@@ -731,7 +735,7 @@ size_t childIndex_Widget(const iWidget *d, const iAnyObject *child) {
731} 735}
732 736
733iAny *hitChild_Widget(const iWidget *d, iInt2 coord) { 737iAny *hitChild_Widget(const iWidget *d, iInt2 coord) {
734 if (d->flags & unhittable_WidgetFlag) { 738 if (d->flags & (unhittable_WidgetFlag | hidden_WidgetFlag)) {
735 return NULL; 739 return NULL;
736 } 740 }
737 /* Check for on-top widgets first. */ 741 /* Check for on-top widgets first. */
diff --git a/src/ui/window.c b/src/ui/window.c
index 38df8682..251eb5c4 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -398,9 +398,21 @@ static void checkLoadAnimation_Window_(iWindow *d) {
398 setReloadLabel_Window_(d, isOngoing); 398 setReloadLabel_Window_(d, isOngoing);
399} 399}
400 400
401static void updatePadding_Window_(iWindow *d) {
402#if defined (iPlatformAppleMobile)
403 /* Respect the safe area insets. */ {
404 float left, top, right, bottom;
405 safeAreaInsets_iOS(&left, &top, &right, &bottom);
406 setPadding_Widget(findChild_Widget(d->root, "navdiv"), left, top, right, 0);
407 setPadding_Widget(findChild_Widget(d->root, "toolbar"), left, 0, right, bottom);
408 }
409#endif
410}
411
401static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { 412static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
402 if (equal_Command(cmd, "window.resized")) { 413 if (equal_Command(cmd, "window.resized")) {
403 const iBool isNarrow = width_Rect(bounds_Widget(navBar)) / gap_UI < 140; 414 const iBool isPhone = deviceType_App() == phone_AppDeviceType;
415 const iBool isNarrow = !isPhone && width_Rect(bounds_Widget(navBar)) / gap_UI < 140;
404 if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) { 416 if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) {
405 setFlags_Widget(navBar, tight_WidgetFlag, isNarrow); 417 setFlags_Widget(navBar, tight_WidgetFlag, isNarrow);
406 iForEach(ObjectList, i, navBar->children) { 418 iForEach(ObjectList, i, navBar->children) {
@@ -413,6 +425,14 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
413 } 425 }
414 } 426 }
415 } 427 }
428 if (isPhone) {
429 setFlags_Widget(findWidget_App("toolbar"), hidden_WidgetFlag, isLandscape_App());
430 setFlags_Widget(findWidget_App("navbar.back"), hidden_WidgetFlag, isPortrait_App());
431 setFlags_Widget(findWidget_App("navbar.forward"), hidden_WidgetFlag, isPortrait_App());
432 setFlags_Widget(findWidget_App("navbar.ident"), hidden_WidgetFlag, isPortrait_App());
433 setFlags_Widget(findWidget_App("navbar.home"), hidden_WidgetFlag, isPortrait_App());
434 setFlags_Widget(findWidget_App("navbar.menu"), hidden_WidgetFlag, isPortrait_App());
435 }
416 arrange_Widget(navBar); 436 arrange_Widget(navBar);
417 refresh_Widget(navBar); 437 refresh_Widget(navBar);
418 postCommand_Widget(navBar, "layout.changed id:navbar"); 438 postCommand_Widget(navBar, "layout.changed id:navbar");
@@ -592,11 +612,6 @@ static void setupUserInterface_Window(iWindow *d) {
592 setId_Widget(div, "navdiv"); 612 setId_Widget(div, "navdiv");
593 addChild_Widget(d->root, iClob(div)); 613 addChild_Widget(d->root, iClob(div));
594 614
595#if defined (iPlatformAppleMobile)
596 /* System status bar needs space. */
597 setPadding_Widget(div, 0, 4 * gap_UI, 0, 0);
598#endif
599
600#if defined (LAGRANGE_CUSTOM_FRAME) 615#if defined (LAGRANGE_CUSTOM_FRAME)
601 /* Window title bar. */ 616 /* Window title bar. */
602 if (prefs_App()->customFrame) { 617 if (prefs_App()->customFrame) {
@@ -662,13 +677,12 @@ static void setupUserInterface_Window(iWindow *d) {
662 addChild_Widget(div, iClob(navBar)); 677 addChild_Widget(div, iClob(navBar));
663 setBackgroundColor_Widget(navBar, uiBackground_ColorId); 678 setBackgroundColor_Widget(navBar, uiBackground_ColorId);
664 setCommandHandler_Widget(navBar, handleNavBarCommands_); 679 setCommandHandler_Widget(navBar, handleNavBarCommands_);
665 addChild_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f870", 0, 0, "navigate.back"))); 680 setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f870", 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.back");
666 addChild_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f872", 0, 0, "navigate.forward"))); 681 setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget("\U0001f872", 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.forward");
667 iLabelWidget *idMenu = makeMenuButton_LabelWidget( 682 iLabelWidget *idMenu = makeMenuButton_LabelWidget(
668 "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_)); 683 "\U0001f464", identityButtonMenuItems_, iElemCount(identityButtonMenuItems_));
669 setAlignVisually_LabelWidget(idMenu, iTrue); 684 setAlignVisually_LabelWidget(idMenu, iTrue);
670 addChild_Widget(navBar, iClob(idMenu)); 685 setId_Widget(addChildFlags_Widget(navBar, iClob(idMenu), collapse_WidgetFlag), "navbar.ident");
671 setId_Widget(as_Widget(idMenu), "navbar.ident");
672 iLabelWidget *lock = 686 iLabelWidget *lock =
673 addChildFlags_Widget(navBar, 687 addChildFlags_Widget(navBar,
674 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")), 688 iClob(newIcon_LabelWidget("\U0001f513", SDLK_i, KMOD_PRIMARY, "document.info")),
@@ -711,14 +725,16 @@ static void setupUserInterface_Window(iWindow *d) {
711 setId_Widget(addChild_Widget( 725 setId_Widget(addChild_Widget(
712 navBar, iClob(newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"))), 726 navBar, iClob(newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"))),
713 "reload"); 727 "reload");
714 addChild_Widget(navBar, 728 setId_Widget(addChildFlags_Widget(navBar,
715 iClob(newIcon_LabelWidget( 729 iClob(newIcon_LabelWidget(
716 "\U0001f3e0", SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home"))); 730 "\U0001f3e0", SDLK_h, KMOD_PRIMARY | KMOD_SHIFT, "navigate.home")),
731 collapse_WidgetFlag),
732 "navbar.home");
717#if !defined (iHaveNativeMenus) 733#if !defined (iHaveNativeMenus)
718 iLabelWidget *navMenu = 734 iLabelWidget *navMenu =
719 makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_)); 735 makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_));
720 setAlignVisually_LabelWidget(navMenu, iTrue); 736 setAlignVisually_LabelWidget(navMenu, iTrue);
721 addChild_Widget(navBar, iClob(navMenu)); 737 setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu");
722#else 738#else
723 insertMenuItems_MacOS("File", 1, fileMenuItems_, iElemCount(fileMenuItems_)); 739 insertMenuItems_MacOS("File", 1, fileMenuItems_, iElemCount(fileMenuItems_));
724 insertMenuItems_MacOS("Edit", 2, editMenuItems_, iElemCount(editMenuItems_)); 740 insertMenuItems_MacOS("Edit", 2, editMenuItems_, iElemCount(editMenuItems_));
@@ -773,6 +789,24 @@ static void setupUserInterface_Window(iWindow *d) {
773 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); 789 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev")));
774 addChild_Widget(searchBar, iClob(newIcon_LabelWidget("\u2a2f", SDLK_ESCAPE, 0, "find.close"))); 790 addChild_Widget(searchBar, iClob(newIcon_LabelWidget("\u2a2f", SDLK_ESCAPE, 0, "find.close")));
775 } 791 }
792#if defined (iPlatformAppleMobile)
793 /* Bottom toolbar. */
794 if (isPhone_iOS()) {
795 iWidget *toolBar = new_Widget();
796 addChild_Widget(div, iClob(toolBar));
797 setId_Widget(toolBar, "toolbar");
798 setFlags_Widget(toolBar, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
799 arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
800 setBackgroundColor_Widget(toolBar, uiBackground_ColorId);
801 addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), frameless_WidgetFlag);
802 addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f872", "navigate.forward")), frameless_WidgetFlag);
803 addChildFlags_Widget(toolBar, iClob(newLargeIcon_LabelWidget("\U0001f464", "sidebar.mode arg:3 show:1")), frameless_WidgetFlag);
804 iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_));
805 setFont_LabelWidget(menuButton, uiLabelLarge_FontId);
806 addChildFlags_Widget(toolBar, iClob(menuButton), frameless_WidgetFlag);
807 }
808#endif
809 updatePadding_Window_(d);
776 iWidget *tabsMenu = makeMenu_Widget(d->root, 810 iWidget *tabsMenu = makeMenu_Widget(d->root,
777 (iMenuItem[]){ 811 (iMenuItem[]){
778 { "Close Tab", 0, 0, "tabs.close" }, 812 { "Close Tab", 0, 0, "tabs.close" },
@@ -1195,6 +1229,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
1195 return iTrue; 1229 return iTrue;
1196 } 1230 }
1197 case SDL_WINDOWEVENT_RESIZED: 1231 case SDL_WINDOWEVENT_RESIZED:
1232 updatePadding_Window_(d);
1198 if (d->isMinimized) { 1233 if (d->isMinimized) {
1199 updateRootSize_Window_(d, iTrue); 1234 updateRootSize_Window_(d, iTrue);
1200 return iTrue; 1235 return iTrue;
@@ -1353,7 +1388,7 @@ void draw_Window(iWindow *d) {
1353 /* Clear the window. The clear color is visible as a border around the window 1388 /* Clear the window. The clear color is visible as a border around the window
1354 when the custom frame is being used. */ { 1389 when the custom frame is being used. */ {
1355#if defined (iPlatformAppleMobile) 1390#if defined (iPlatformAppleMobile)
1356 const iColor back = get_Color(uiBackground_ColorId); 1391 const iColor back = get_Color(tmBackground_ColorId);
1357#else 1392#else
1358 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && 1393 const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap &&
1359 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP 1394 ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP