Lagrange [work/v1.12]

Placing navbar and tab bars at the bottom

=> 7d43fe3071b9ba333c5033e94285f67f54606b9c

diff --git a/src/app.c b/src/app.c
index fb29617e..fbd92a06 100644
--- a/src/app.c
+++ b/src/app.c
@@ -283,6 +283,8 @@ static iString *serializePrefs_App_(const iApp *d) {
         { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages },
         { "prefs.biglede", &d->prefs.bigFirstParagraph },
         { "prefs.blink", &d->prefs.blinkingCursor },
+        { "prefs.bottomnavbar", &d->prefs.bottomNavBar },
+        { "prefs.bottomtabbar", &d->prefs.bottomTabBar },
         { "prefs.boldlink.dark", &d->prefs.boldLinkDark },
         { "prefs.boldlink.light", &d->prefs.boldLinkLight },
         { "prefs.boldlink.visited", &d->prefs.boldLinkVisited },
@@ -1302,8 +1304,9 @@ static void clearCache_App_(void) {
 }
 
 iObjectList *listAllDocuments_App(void) {
-    iWindow *oldWindow = get_Window();
-    iObjectList *allDocs = new_ObjectList();
+    iWindow     *oldWindow = get_Window();
+    iRoot       *oldRoot   = current_Root();
+    iObjectList *allDocs   = new_ObjectList();
     iConstForEach(PtrArray, window, mainWindows_App()) {
         setCurrent_Window(window.ptr);
         iObjectList *docs = listDocuments_App(NULL);
@@ -1313,6 +1316,7 @@ iObjectList *listAllDocuments_App(void) {
         iRelease(docs);
     }
     setCurrent_Window(oldWindow);
+    setCurrent_Root(oldRoot);
     return allDocs;
 }
 
diff --git a/src/prefs.c b/src/prefs.c
index 995da0bc..3ec7448a 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -58,6 +58,8 @@ void init_Prefs(iPrefs *d) {
     d->sideIcon          = iTrue;
     d->hideToolbarOnScroll = iTrue;
     d->blinkingCursor    = iTrue;
+    d->bottomNavBar      = iTrue;
+    d->bottomTabBar      = iTrue;
     d->pinSplit          = 1;
     d->time24h           = iTrue;
     d->returnKey         = default_ReturnKeyBehavior;
diff --git a/src/prefs.h b/src/prefs.h
index 7cbbdb4c..7f4509de 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -66,6 +66,8 @@ enum iPrefsBool {
     hideToolbarOnScroll_PrefsBool,
     
     blinkingCursor_PrefsBool,
+    bottomNavBar_PrefsBool,
+    bottomTabBar_PrefsBool,
     
     /* Document presentation */
     sideIcon_PrefsBool,
@@ -77,9 +79,9 @@ enum iPrefsBool {
     smoothScrolling_PrefsBool,
     loadImageInsteadOfScrolling_PrefsBool,
     openDataUrlImagesOnLoad_PrefsBool,
+    
     collapsePreOnLoad_PrefsBool,
     openArchiveIndexPages_PrefsBool,
-    
     addBookmarksToBottom_PrefsBool,
     warnAboutMissingGlyphs_PrefsBool,
     
@@ -98,6 +100,7 @@ enum iPrefsBool {
     justifyParagraph_PrefsBool,
     quoteIcon_PrefsBool,
     centerShortDocs_PrefsBool,
+    
     plainTextWrap_PrefsBool,
     
     /* Meta */
@@ -122,6 +125,8 @@ struct Impl_Prefs {
             iBool hideToolbarOnScroll;
             
             iBool blinkingCursor;
+            iBool bottomNavBar;
+            iBool bottomTabBar;
             
             /* Document presentation */
             iBool sideIcon;
@@ -133,9 +138,9 @@ struct Impl_Prefs {
             iBool smoothScrolling;
             iBool loadImageInsteadOfScrolling;
             iBool openDataUrlImagesOnLoad;
-            iBool collapsePreOnLoad;
-            iBool openArchiveIndexPages;
             
+            iBool collapsePreOnLoad;
+            iBool openArchiveIndexPages;            
             iBool addBookmarksToBottom;
             iBool warnAboutMissingGlyphs;
             
@@ -154,6 +159,7 @@ struct Impl_Prefs {
             iBool justifyParagraph;
             iBool quoteIcon;
             iBool centerShortDocs;
+            
             iBool plainTextWrap;
         };
     };
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index d0e1bd91..d0d565d7 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -881,7 +881,9 @@ static void updateVisible_DocumentView_(iDocumentView *d) {
     /* After scrolling/resizing stops, begin pre-rendering the visbuf contents. */ {
         removeTicker_App(prerender_DocumentWidget_, d->owner);
         remove_Periodic(periodic_App(), d);
-        add_Periodic(periodic_App(), d->owner, "document.render");
+        if (~d->owner->flags & animationPlaceholder_DocumentWidgetFlag) {
+            add_Periodic(periodic_App(), d->owner, "document.render");
+        }
     }
 }
 
@@ -3594,12 +3596,12 @@ static iWidget *swipeParent_DocumentWidget_(iDocumentWidget *d) {
 static void setupSwipeOverlay_DocumentWidget_(iDocumentWidget *d, iWidget *overlay) {
     iWidget *w = as_Widget(d);
     iWidget *swipeParent = swipeParent_DocumentWidget_(d);
+    iAssert(overlay);
     /* The target takes the old document and jumps on top. */
     overlay->rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2()));
     /* Note: `innerToWindow_Widget` does not apply visual offset. */
     overlay->rect.size = w->rect.size;
     setFlags_Widget(overlay, fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue);
-//        swap_DocumentWidget_(target, d->doc, d);
     setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue);
     as_Widget(d)->offsetRef = swipeParent;
     /* `overlay` animates off the screen to the right. */
@@ -3623,11 +3625,15 @@ static void setupSwipeOverlay_DocumentWidget_(iDocumentWidget *d, iWidget *overl
 
 static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
     /* TODO: Cleanup
+     
        If DocumentWidget is refactored to split the document presentation from state
        and request management (a new DocumentView class), plain views could be used for this
        animation without having to mess with the complete state of the DocumentWidget. That
        seems like a less error-prone approach -- the current implementation will likely break
        down (again) if anything is changed in the document internals.
+       
+       2022-03-16: Yeah, something did break, again. "swipeout" is not found if the tab bar
+       is moved to the bottom, when swiping back.
     */
     iWidget *w = as_Widget(d);
     /* The swipe animation is implemented in a rather complex way. It utilizes both cached
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index a4f5498a..afd9c10e 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -657,7 +657,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
     else if (isResize_UserEvent(ev) || equal_Command(cmd, "keyboard.changed") ||
              (equal_Command(cmd, "layout.changed") &&
               equal_Rangecc(range_Command(cmd, "id"), "navbar"))) {
-        /* Position the lookup popup under the URL bar. */ {
+        /* Position the lookup popup in relation to the URL bar. */ {
             iRoot    *root       = w->root;
             iWidget  *url        = findChild_Widget(root->widget, "url");
             const int minWidth   = iMin(120 * gap_UI, width_Rect(safeRect_Root(root)));
@@ -667,19 +667,28 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
                 extraWidth = minWidth - urlWidth;
             }
             const iRect navBarBounds = bounds_Widget(findChild_Widget(root->widget, "navbar"));
+            const iBool atBottom = prefs_App()->bottomNavBar;
             setFixedSize_Widget(
                 w,
                 init_I2(width_Widget(url) + extraWidth,
-                        (bottom_Rect(rect_Root(root)) - bottom_Rect(navBarBounds)) / 2));
-            setPos_Widget(w,
-                          windowToLocal_Widget(w,
-                                               max_I2(zero_I2(),
-                                                      addX_I2(bottomLeft_Rect(bounds_Widget(url)),
-                                                              -extraWidth / 2))));
+                        (atBottom ? top_Rect(navBarBounds)
+                                  : (bottom_Rect(rect_Root(root)) - bottom_Rect(navBarBounds))) /
+                            2));
+            const iInt2 topLeft = atBottom
+                                      ? addY_I2(topLeft_Rect(bounds_Widget(url)), -w->rect.size.y)
+                                      : bottomLeft_Rect(bounds_Widget(url));
+            setPos_Widget(
+                w, windowToLocal_Widget(w, max_I2(zero_I2(), addX_I2(topLeft, -extraWidth / 2))));
 #if defined(iPlatformMobile)
             /* TODO: Check this again. */
             /* Adjust height based on keyboard size. */ {
-                w->rect.size.y = bottom_Rect(visibleRect_Root(root)) - top_Rect(bounds_Widget(w));
+                if (!atBottom) {
+                    w->rect.size.y = bottom_Rect(visibleRect_Root(root)) - top_Rect(bounds_Widget(w));
+                }
+                else {
+                    w->rect.pos = windowToLocal_Widget(w, visibleRect_Root(root).pos);
+                    w->rect.size.y = height_Rect(visibleRect_Root(root)) - height_Rect(navBarBounds);
+                }
 #   if defined (iPlatformAppleMobile)
                 if (deviceType_App() == phone_AppDeviceType) {
                     float l = 0.0f, r = 0.0f;
@@ -738,7 +747,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
                     setFocus_Widget(url);
                     return iTrue;
                 case SDLK_UP:
-                    if (!moveCursor_LookupWidget_(d, -1)) {
+                    if (!moveCursor_LookupWidget_(d, -1) && !prefs_App()->bottomNavBar) {
                         setCursor_LookupWidget_(d, iInvalidPos);
                         setFocus_Widget(url);
                     }
@@ -767,9 +776,10 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
         }
         /* Focus switching between URL bar and lookup results. */
         if (isVisible_Widget(w)) {
-            if (((key == SDLK_DOWN && !mods) || key == SDLK_TAB) &&
-                focus_Widget() == findWidget_App("url") &&
-                numItems_ListWidget(d->list)) {
+            if (((!mods && ((key == SDLK_DOWN && !prefs_App()->bottomNavBar) ||
+                            (key == SDLK_UP && prefs_App()->bottomNavBar))) ||
+                 key == SDLK_TAB) &&
+                focus_Widget() == findWidget_App("url") && numItems_ListWidget(d->list)) {
                 setCursor_LookupWidget_(d, 1); /* item 0 is always the first heading */
                 setFocus_Widget(w);
                 return iTrue;
diff --git a/src/ui/mobile.c b/src/ui/mobile.c
index 19d39bf1..3b47d4a1 100644
--- a/src/ui/mobile.c
+++ b/src/ui/mobile.c
@@ -1027,6 +1027,17 @@ void setupSheetTransition_Mobile(iWidget *sheet, int flags) {
     }
 }
 
+int leftSafeInset_Mobile(void) {
+#if defined (iPlatformAppleMobile)
+    float left;
+    safeAreaInsets_iOS(&left, NULL, NULL, NULL);
+    return iRound(left);
+#else
+    return 0;
+#endif
+}
+
+
 int bottomSafeInset_Mobile(void) {
 #if defined (iPlatformAppleMobile)
     float bot;
diff --git a/src/ui/mobile.h b/src/ui/mobile.h
index a719f20b..79986771 100644
--- a/src/ui/mobile.h
+++ b/src/ui/mobile.h
@@ -68,4 +68,5 @@ enum iTransitionDir {
 void        setupMenuTransition_Mobile  (iWidget *menu,  iBool isIncoming);
 void        setupSheetTransition_Mobile (iWidget *sheet, int flags);
 
+int         leftSafeInset_Mobile        (void);
 int         bottomSafeInset_Mobile      (void);
diff --git a/src/ui/root.c b/src/ui/root.c
index a7c39e22..bff2dbb9 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -241,6 +241,8 @@ static int       loadAnimIndex_      = 0;
 
 static iRoot *   activeRoot_     = NULL;
 
+static void     setupMovableElements_Root_  (iRoot *);
+
 iDefineTypeConstruction(Root)
 iDefineAudienceGetter(Root, visualOffsetsChanged)
 
@@ -521,6 +523,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
             addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos);            
             setWidth_SidebarWidget(sidebar, 73.0f);
             setFlags_Widget(as_Widget(sidebar), fixedHeight_WidgetFlag | fixedPosition_WidgetFlag, iFalse);
+            showToolbar_Root(root->root, iFalse);
         }
         else {
             addChild_Widget(root, iClob(sidebar));
@@ -532,26 +535,8 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
             setMidHeight_SidebarWidget(sidebar, midHeight);
             setFixedSize_Widget(as_Widget(sidebar), init_I2(-1, midHeight));
             setPos_Widget(as_Widget(sidebar), init_I2(0, height_Widget(root) - midHeight));
+            showToolbar_Root(root->root, iTrue);
         }
-#if 0
-        iSidebarWidget *sidebar = findChild_Widget(root, "sidebar");
-        iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2");
-        setFlags_Widget(findChild_Widget(as_Widget(sidebar), "buttons"),
-                        borderTop_WidgetFlag,
-                        isPortrait_App());
-        if (isLandscape_App()) {
-            addChildPos_Widget(findChild_Widget(root, "tabs.content"), iClob(sidebar), front_WidgetAddPos);
-            setWidth_SidebarWidget(sidebar, 73.0f);
-            if (isVisible_Widget(findWidget_App("sidebar2"))) {
-                postCommand_App("sidebar2.toggle");
-            }
-        }
-        else {
-            addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos);
-            setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI);
-            setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI);
-        }
-#endif
         return iFalse;
     }
     else if (equal_Command(cmd, "root.arrange")) {
@@ -651,7 +636,7 @@ static uint32_t updateReloadAnimation_Root_(uint32_t interval, void *root) {
 }
 
 static void setReloadLabel_Root_(iRoot *d, iBool animating) {
-    const iBool isMobile = deviceType_App() != desktop_AppDeviceType;
+//    const iBool isMobile = deviceType_App() != desktop_AppDeviceType;
     iLabelWidget *label = findChild_Widget(d->widget, "reload");
     updateTextCStr_LabelWidget(
         label, animating ? loadAnimationCStr_() : (/*isMobile ? pageMenuCStr_ :*/ reloadCStr_));
@@ -691,22 +676,21 @@ void updatePadding_Root(iRoot *d) {
 
 void updateToolbarColors_Root(iRoot *d) {
 #if defined (iPlatformMobile)
-    iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
-    if (toolBar) {
-        const iBool isSidebarVisible =
-            isVisible_Widget(findChild_Widget(d->widget, "sidebar")) ||
-            isVisible_Widget(findChild_Widget(d->widget, "sidebar2"));
-        const int bg = isSidebarVisible ? uiBackgroundSidebar_ColorId :
-                                          tmBannerBackground_ColorId;
-        setBackgroundColor_Widget(toolBar, bg);
+    iWidget *bottomBar = findChild_Widget(d->widget, "bottombar");
+    if (bottomBar) {
+        iWidget *toolBar = findChild_Widget(bottomBar, "toolbar");
+        const iWidget *tabs = findChild_Widget(d->widget, "doctabs");
+        const size_t numPages = childCount_Widget(findChild_Widget(tabs, "tabs.pages"));
+        const iBool useThemeColors = !prefs_App()->bottomNavBar &&
+                                     !(prefs_App()->bottomTabBar && numPages > 1);
+        const int bg = useThemeColors ? tmBannerBackground_ColorId : uiBackground_ColorId;
+        setBackgroundColor_Widget(bottomBar, bg);
         iForEach(ObjectList, i, children_Widget(toolBar)) {
-//            iLabelWidget *btn = i.object;
-            setTextColor_LabelWidget(i.object, isSidebarVisible ? uiTextDim_ColorId :
-                                     tmBannerIcon_ColorId);
+            setTextColor_LabelWidget(i.object, useThemeColors ? tmBannerIcon_ColorId : uiTextDim_ColorId);
             setBackgroundColor_Widget(i.object, bg); /* using noBackground, but ident has outline */
         }
         setTextColor_LabelWidget(findChild_Widget(toolBar, "toolbar.name"),
-                                 isSidebarVisible ? uiTextDim_ColorId : tmBannerIcon_ColorId);
+                                 useThemeColors ? tmBannerIcon_ColorId : uiTextDim_ColorId);
     }
 #else
     iUnused(d);
@@ -736,17 +720,19 @@ void notifyVisualOffsetChange_Root(iRoot *d) {
 void dismissPortraitPhoneSidebars_Root(iRoot *d) {
     if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) {
         iWidget *sidebar = findChild_Widget(d->widget, "sidebar");
-        iWidget *sidebar2 = findChild_Widget(d->widget, "sidebar2");
+//        iWidget *sidebar2 = findChild_Widget(d->widget, "sidebar2");
         if (isVisible_Widget(sidebar)) {
             postCommand_App("sidebar.toggle");
             setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag);
         }
+#if 0
         if (isVisible_Widget(sidebar2)) {
             postCommand_App("sidebar2.toggle");
             setVisualOffset_Widget(sidebar2, height_Widget(sidebar2), 250, easeIn_AnimFlag);
         }
         //        setFlags_Widget(findWidget_App("toolbar.ident"), noBackground_WidgetFlag, iTrue);
         //        setFlags_Widget(findWidget_App("toolbar.view"), noBackground_WidgetFlag, iTrue);
+#endif
     }
 }
 
@@ -799,14 +785,23 @@ iBool isNarrow_Root(const iRoot *d) {
     return width_Rect(safeRect_Root(d)) / gap_UI < 140;
 }
 
+static void updateNavBarParent_(iWidget *navBar) {
+    /* The navbar can be */
+}
+
 static void updateNavBarSize_(iWidget *navBar) {
     const iBool isPhone = deviceType_App() == phone_AppDeviceType;
     const iBool isNarrow = !isPhone && isNarrow_Root(navBar->root);
     /* Adjust navbar padding. */ {
-        int hPad = isPortraitPhone_App() ? 0 : isPhone || isNarrow ? gap_UI / 2 : (gap_UI * 3 / 2);
+        int hPad   = isPortraitPhone_App() ? 0 : isPhone || isNarrow ? gap_UI / 2 : (gap_UI * 3 / 2);
         int vPad   = gap_UI * 3 / 2;
+        int botPad = vPad / 2;
         int topPad = !findWidget_Root("winbar") ? gap_UI / 2 : 0;
-        setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, vPad / 2);
+        if (isLandscape_App() && prefs_App()->bottomNavBar) {
+            botPad += bottomSafeInset_Mobile();
+            hPad += leftSafeInset_Mobile();
+        }
+        setPadding_Widget(navBar, hPad, vPad / 3 + topPad, hPad, botPad);
     }
     /* Button sizing. */
     if (isNarrow ^ ((flags_Widget(navBar) & tight_WidgetFlag) != 0)) {
@@ -1069,6 +1064,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
             updateNavBarIdentity_(navBar);
         }
         setFocus_Widget(NULL);
+        updateToolbarColors_Root(as_Widget(doc)->root);
         makePaletteGlobal_GmDocument(document_DocumentWidget(doc));
         refresh_Widget(findWidget_Root("doctabs"));
     }
@@ -1197,6 +1193,30 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) {
         updateToolBarActions_(toolBar);
         return iFalse;        
     }
+    else if (equal_Command(cmd, "keyboard.changed") && prefs_App()->bottomNavBar) {
+        int height = arg_Command(cmd);
+        iWidget *bottomBar = findChild_Widget(root_Widget(toolBar), "bottombar");
+        if (!bottomBar) {
+            return iFalse;
+        }
+        if (focus_Widget() == findChild_Widget(root_Widget(toolBar), "url") && height > 0) {
+            int inputHeight = 5 * gap_UI; /* TODO: Why this amount? Something's funny here. */
+            int keyboardPad = height - (isPortrait_App() ? height_Widget(toolBar) : inputHeight);
+            bottomBar->padding[3] = keyboardPad;
+            arrange_Widget(bottomBar);
+            arrange_Widget(bottomBar);
+            setVisualOffset_Widget(bottomBar, keyboardPad, 0, 0);
+            setVisualOffset_Widget(bottomBar, 0, 400, easeOut_AnimFlag | softer_AnimFlag);
+        }
+        if (height == 0) {
+            setVisualOffset_Widget(bottomBar, -bottomBar->padding[3], 0, 0);
+            setVisualOffset_Widget(bottomBar, 0, 350, easeOut_AnimFlag | softer_AnimFlag);
+            bottomBar->padding[3] = 0;
+            arrange_Widget(bottomBar);
+            arrange_Widget(bottomBar);
+        }
+        return iFalse;
+    }
     return iFalse;
 }
 
@@ -1547,6 +1567,7 @@ void createUserInterface_Root(iRoot *d) {
         iWidget *docTabs = makeTabs_Widget(mainStack);
         setId_Widget(docTabs, "doctabs");
         setBackgroundColor_Widget(docTabs, uiBackground_ColorId);
+//        setTabBarPosition_Widget(docTabs, prefs_App()->bottomTabBar);
         iDocumentWidget *doc;
         appendTabPage_Widget(docTabs, iClob(doc = new_DocumentWidget()), "Document", 0, 0);
         addTabCloseButton_Widget(docTabs, as_Widget(doc), "tabs.close");
@@ -1616,17 +1637,24 @@ void createUserInterface_Root(iRoot *d) {
 #if defined (iPlatformMobile)
     /* Bottom toolbar. */
     if (deviceType_App() == phone_AppDeviceType) {
+        iWidget *bottomBar = new_Widget();
+        setId_Widget(bottomBar, "bottombar");
+        addChildFlags_Widget(root,
+                             iClob(bottomBar),
+                             moveToParentBottomEdge_WidgetFlag |
+                                 parentCannotResizeHeight_WidgetFlag | arrangeVertical_WidgetFlag |
+                                 arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag |
+                                 drawBackgroundToBottom_WidgetFlag);
         iWidget *toolBar = new_Widget();
-        addChild_Widget(root, iClob(toolBar));
+        addChild_Widget(bottomBar, iClob(toolBar));
         setId_Widget(toolBar, "toolbar");
         setDrawBufferEnabled_Widget(toolBar, iTrue);
         setCommandHandler_Widget(toolBar, handleToolBarCommands_);
-        setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag |
-                                     parentCannotResizeHeight_WidgetFlag |
-                                     resizeWidthOfChildren_WidgetFlag |
-                                     arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag |
-                                     commandOnClick_WidgetFlag |
-                                     drawBackgroundToBottom_WidgetFlag, iTrue);
+        setFlags_Widget(toolBar,
+                        //moveToParentBottomEdge_WidgetFlag | parentCannotResizeHeight_WidgetFlag |
+                            resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag |
+                            arrangeHorizontal_WidgetFlag | commandOnClick_WidgetFlag | collapse_WidgetFlag,
+                        iTrue);
         setId_Widget(addChildFlags_Widget(toolBar,
                                           iClob(newLargeIcon_LabelWidget("", "...")),
                                           frameless_WidgetFlag),
@@ -1641,21 +1669,18 @@ void createUserInterface_Root(iRoot *d) {
                          iClob(newLargeIcon_LabelWidget("\U0001f464", "identmenu.open")),
                          frameless_WidgetFlag | fixedHeight_WidgetFlag),
                      "toolbar.ident");
-        setId_Widget(addChildFlags_Widget(toolBar,
-                                          iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")),
-                                          frameless_WidgetFlag | commandOnClick_WidgetFlag),
+        setId_Widget(addChildFlags_Widget(
+                         toolBar,
+                         iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")),
+                         frameless_WidgetFlag | commandOnClick_WidgetFlag),
                      "toolbar.view");
-                     iLabelWidget *idName;
+        iLabelWidget *idName;
         setId_Widget(addChildFlags_Widget(identButton,
                                           iClob(idName = new_LabelWidget("", NULL)),
                                           frameless_WidgetFlag |
-                                          noBackground_WidgetFlag |
-                                          moveToParentBottomEdge_WidgetFlag |
-                                          resizeToParentWidth_WidgetFlag
-                                          /*fixedPosition_WidgetFlag |
-                                          fixedSize_WidgetFlag |
-                                          ignoreForParentWidth_WidgetFlag |
-                                          ignoreForParentHeight_WidgetFlag*/),
+                                              noBackground_WidgetFlag |
+                                              moveToParentBottomEdge_WidgetFlag |
+                                              resizeToParentWidth_WidgetFlag),
                      "toolbar.name");
         setFont_LabelWidget(idName, uiLabelTiny_FontId);
         iLabelWidget *menuButton = makeMenuButton_LabelWidget(menu_Icon, phoneNavMenuItems_,
@@ -1679,6 +1704,7 @@ void createUserInterface_Root(iRoot *d) {
         setId_Widget(menu, "toolbar.menu"); /* view menu */
     }
 #endif
+    setupMovableElements_Root_(d);
     updateNavBarActions_(navBar);
     updatePadding_Root(d);
     /* Global context menus. */ {
@@ -1773,23 +1799,181 @@ void createUserInterface_Root(iRoot *d) {
     }
 }
 
+static void setupMovableElements_Root_(iRoot *d) {
+    /* The navbar and the tab bar may be move depending on preferences. */
+    const iPrefs *prefs = prefs_App();
+    iWidget *bottomBar = findChild_Widget(d->widget, "bottombar");
+    iWidget *toolBar   = findChild_Widget(bottomBar, "toolbar");
+    iWidget *navBar    = findChild_Widget(d->widget, "navbar");
+    iWidget *winBar    = findChild_Widget(d->widget, "winbar"); /* optional: custom window frame */
+    iWidget *div       = findChild_Widget(d->widget, "navdiv");
+    iWidget *docTabs   = findChild_Widget(d->widget, "doctabs");
+    iWidget *tabBar    = findChild_Widget(docTabs, "tabs.buttons");
+    if (prefs->bottomNavBar) {
+        if (deviceType_App() == phone_AppDeviceType) {
+            /* When at the bottom, the navbar is at the top of the bottombar, and gets fully hidden
+               when the toolbar is hidden. */
+            if (parent_Widget(navBar) != bottomBar) {
+                removeChild_Widget(navBar->parent, navBar);
+                addChildPos_Widget(bottomBar, navBar, front_WidgetAddPos);
+                iRelease(navBar);
+            }
+        }
+        else {
+            /* On desktop/tablet, a bottom navbar is at the bottom of the main layout. */
+            removeChild_Widget(navBar->parent, navBar);
+            addChildPos_Widget(div, navBar, back_WidgetAddPos);
+            iRelease(navBar);
+        }
+    }
+    else {
+        /* In the top navbar layout, the navbar is always the first (or second) child. */
+        removeChild_Widget(navBar->parent, navBar);
+        if (winBar) {
+            iAssert(indexOfChild_Widget(div, winBar) == 0);
+            insertChildAfter_Widget(div, navBar, 0);
+        }
+        else {
+            addChildPos_Widget(div, navBar, front_WidgetAddPos);
+        }
+        iRelease(navBar);
+    }
+    iChangeFlags(tabBar->flags2, permanentVisualOffset_WidgetFlag2, prefs->bottomTabBar);
+    setTabBarPosition_Widget(docTabs, prefs->bottomTabBar);
+    arrange_Widget(d->widget);
+    postRefresh_App();
+}
+
+static void setBottomBarPosition_(iWidget *bottomBar, iBool show, iBool animate) {
+    if (deviceType_App() != phone_AppDeviceType) {
+        return;
+    }
+    const iPrefs *prefs = prefs_App();
+    float bottomSafe = 0.0f;
+    iWidget *tabBar = NULL;
+    iRoot *root = bottomBar->root;
+    iWidget *docTabs = findChild_Widget(root->widget, "doctabs");
+    iWidget *toolBar = findChild_Widget(bottomBar, "toolbar");
+    iWidget *navBar = findChild_Widget(root->widget, "navbar");
+    const int height = size_Root(root).y - top_Rect(boundsWithoutVisualOffset_Widget(bottomBar));
+    size_t numPages = 0;
+    iBool bottomTabBar = prefs->bottomTabBar;
+    if (prefs->bottomTabBar || prefs->bottomNavBar) {
+        tabBar = findChild_Widget(docTabs, "tabs.buttons");
+        numPages = tabCount_Widget(docTabs);
+        if (numPages == 1) {
+            bottomTabBar = iFalse; /* it's not visible */
+        }
+    }
+#if defined (iPlatformAppleMobile)
+    if (bottomTabBar) {
+        safeAreaInsets_iOS(NULL, NULL, NULL, &bottomSafe);
+        if (bottomSafe >= gap_UI) {
+            bottomSafe -= gap_UI; /* kludge: something's leaving a gap between the tabs and the bottombar */
+        }
+    }
+#endif
+    showCollapsed_Widget(toolBar, isPortrait_App());
+    if (show) {
+        if (flags_Widget(bottomBar) & hidden_WidgetFlag) {
+            setFlags_Widget(bottomBar, hidden_WidgetFlag, iFalse);
+            setVisualOffset_Widget(bottomBar, 0, 200 * animate, easeOut_AnimFlag);
+            if (isPortraitPhone_App()) {
+                setVisualOffset_Widget(toolBar, 0, 200 * animate, 0);
+            }
+            setVisualOffset_Widget(navBar, 0, 200 * animate, 0);
+        }
+        if (bottomTabBar) {
+            /* Tab bar needs to stay visible, too. */
+            setVisualOffset_Widget(tabBar, -bottomBar->rect.size.y, 200 * animate, easeOut_AnimFlag);
+            //tabBar->flags2 |= permanentVisualOffset_WidgetFlag2;
+        }
+    }
+    else {
+        if (~flags_Widget(bottomBar) & hidden_WidgetFlag) {
+            setFlags_Widget(bottomBar, hidden_WidgetFlag, iTrue);
+            /* Close any menus that open via the toolbar. */
+            closeMenu_Widget(findChild_Widget(findWidget_App("toolbar.navmenu"), "menu"));
+            closeMenu_Widget(findChild_Widget(bottomBar, "toolbar.menu"));
+            setVisualOffset_Widget(bottomBar, height - bottomSafe, 200 * animate, easeOut_AnimFlag);
+        }
+        if (bottomTabBar) {
+            if (isPortraitPhone_App()) {
+                setVisualOffset_Widget(toolBar, bottomSafe, 200 * animate, 0);
+            }
+            if (prefs->bottomNavBar) {
+                setVisualOffset_Widget(navBar, bottomSafe, 200 * animate, 0);
+            }
+            setVisualOffset_Widget(tabBar, -bottomSafe, 200 * animate, easeOut_AnimFlag);
+            //tabBar->flags2 |= hiddenWithVisualOffset_WidgetFlag2;
+        }
+    }
+}
+
 void showToolbar_Root(iRoot *d, iBool show) {
-    /* The toolbar is only used on phone portrait layout. */
-    if (isLandscape_App()) return;
-    iWidget *toolBar = findChild_Widget(d->widget, "toolbar");
-    if (!toolBar) return;
-    const int height = size_Root(d).y - top_Rect(boundsWithoutVisualOffset_Widget(toolBar));
-    if (show && !isVisible_Widget(toolBar)) {
-        setFlags_Widget(toolBar, hidden_WidgetFlag, iFalse);
-        setVisualOffset_Widget(toolBar, 0, 200, easeOut_AnimFlag);
+    iWidget *bottomBar = findChild_Widget(d->widget, "bottombar");
+    if (!bottomBar) return;
+    const iPrefs *prefs = prefs_App();
+    /* The toolbar is only used in the portrait phone layout, but the bottom bar may have other
+       elements regardless. The toolbar is needed for clearing the bottom safe area when there
+       is a bottom tab bar, even if the URL is at the top. Note that the entire bottom bar may
+       be hidden, but the tab bar remains always visible if there are tabs open. */
+    if (isLandscape_App() && !prefs->bottomTabBar && !prefs->bottomNavBar) {
+        //setFlags_Widget(bottomBar, hidden_WidgetFlag, iTrue);
+        setBottomBarPosition_(bottomBar, iFalse, iTrue);
+        return;
     }
-    else if (!show && isVisible_Widget(toolBar)) {
+//    iWidget *toolBar = findChild_Widget(bottomBar, "toolbar");
+//    iWidget *navBar = findChild_Widget(d->widget, "navbar");
+//    const int height = size_Root(d).y - top_Rect(boundsWithoutVisualOffset_Widget(bottomBar));
+//    float bottomSafe = 0;
+//    const iBool isBottomTabBar = prefs_App()->bottomTabBar;
+//    iWidget *tabBar = NULL;
+//    if (isBottomTabBar) {
+//        tabBar = findChild_Widget(findChild_Widget(d->widget, "doctabs"), "tabs.buttons");
+//        const size_t numPages = childCount_Widget(findChild_Widget(tabs, "tabs.pages"));
+//    }
+//#if defined (iPlatformAppleMobile)
+//    if (isBottomTabBar) {
+//        safeAreaInsets_iOS(NULL, NULL, NULL, &bottomSafe);
+//        if (bottomSafe >= gap_UI) {
+//            bottomSafe -= gap_UI;
+//        }
+//    }
+//#endif
+    setBottomBarPosition_(bottomBar, show, iTrue);
+    
+#if 0
+    if (show && (!isVisible_Widget(bottomBar) || (isBottomTabBar && ~flags_Widget(tabBar) & dragged_WidgetFlag))) {
+        setFlags_Widget(bottomBar, hidden_WidgetFlag, iFalse);
+        setVisualOffset_Widget(bottomBar, 0, 200, easeOut_AnimFlag);
+        setVisualOffset_Widget(toolBar, 0, 200, 0);
+        if (prefs_App()->bottomNavBar) {
+            setVisualOffset_Widget(navBar, 0, 200, 0);
+        }
+        if (isBottomTabBar) {
+            /* Tab bar needs to stay visible, too. */
+            setVisualOffset_Widget(tabBar, -bottomBar->rect.size.y, 200, easeOut_AnimFlag);
+            setFlags_Widget(tabBar, dragged_WidgetFlag, iTrue);
+                /* force it to be visible; `dragged` applies the offset even after the animation */
+        }
+    }
+    else if (!show && isVisible_Widget(bottomBar)) {
         /* Close any menus that open via the toolbar. */
         closeMenu_Widget(findChild_Widget(findWidget_App("toolbar.navmenu"), "menu"));
-        closeMenu_Widget(findChild_Widget(toolBar, "toolbar.menu"));
-        setFlags_Widget(toolBar, hidden_WidgetFlag, iTrue);
-        setVisualOffset_Widget(toolBar, height, 200, easeOut_AnimFlag);
+        closeMenu_Widget(findChild_Widget(bottomBar, "toolbar.menu"));
+        setFlags_Widget(bottomBar, hidden_WidgetFlag, iTrue);
+        setVisualOffset_Widget(bottomBar, height - bottomSafe, 200, easeOut_AnimFlag);
+        setVisualOffset_Widget(toolBar, bottomSafe, 200, 0);
+        if (prefs_App()->bottomNavBar) {
+            setVisualOffset_Widget(navBar, bottomSafe, 200, 0);
+        }
+        if (isBottomTabBar) {
+            setVisualOffset_Widget(tabBar, -bottomSafe, 200, easeOut_AnimFlag);
+            tabBar->flags2 |= hiddenWithVisualOffset_WidgetFlag2;
+        }
     }
+#endif
 }
 
 size_t windowIndex_Root(const iRoot *d) {
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 70059b6e..17a4e87b 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -454,6 +454,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct
             break;
         }
         case bookmarks_SidebarMode: {
+            iAssert(get_Root() == d->widget.root);
             iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) {
                 const iBookmark *bm = i.ptr;
                 if (isBookmarkFolded_SidebarWidget_(d, bm)) {
diff --git a/src/ui/text.c b/src/ui/text.c
index 1ef211ad..62e6b641 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -1818,7 +1818,10 @@ void process_RunLayer_(iRunLayer *d, int layerIndex) {
                 d->xCursor = nextTabStop_Font_(d->font, d->xCursor) - xAdvance;
             }
             const float xf = d->xCursor + xOffset;
-            const float subpixel = xf - (int) xf;
+            float subpixel = xf - (int) xf;
+            if (subpixel < 0.0f) {
+                subpixel = 1.0f + subpixel;
+            }
             const int hoff = enableHalfPixelGlyphs_Text ? (int) (subpixel / offsetStep_Glyph_()) : 0;
             if (ch == 0x3001 || ch == 0x3002) {
                 /* Vertical misalignment?? */
diff --git a/src/ui/util.c b/src/ui/util.c
index 37901c00..176d24c0 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -1490,6 +1490,7 @@ iWidget *makeTabs_Widget(iWidget *parent) {
                         arrangeHeight_WidgetFlag,
                     iTrue);
     setId_Widget(buttons, "tabs.buttons");
+//    setBackgroundColor_Widget(buttons, red_ColorId);
     iWidget *content = addChildFlags_Widget(tabs, iClob(makeHDiv_Widget()), expand_WidgetFlag);
     setId_Widget(content, "tabs.content");
     iWidget *pages = addChildFlags_Widget(
@@ -1500,6 +1501,13 @@ iWidget *makeTabs_Widget(iWidget *parent) {
     return tabs;
 }
 
+void setTabBarPosition_Widget(iWidget *tabs, iBool atBottom) {
+    iWidget *buttons = findChild_Widget(tabs, "tabs.buttons");
+    removeChild_Widget(tabs, buttons);
+    addChildPos_Widget(tabs, buttons, atBottom ? back_WidgetAddPos : front_WidgetAddPos);
+    iRelease(buttons);
+}
+
 static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget *page,
                                const char *label, int key, int kmods) {
     iWidget *   pages   = findChild_Widget(tabs, "tabs.pages");
diff --git a/src/ui/util.h b/src/ui/util.h
index 31c8cedc..15dc021d 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -290,6 +290,7 @@ const char *    selectedDropdownCommand_LabelWidget (const iLabelWidget *dropBut
 /*-----------------------------------------------------------------------------------------------*/
 
 iWidget *       makeTabs_Widget         (iWidget *parent);
+void            setTabBarPosition_Widget(iWidget *tabs, iBool atBottom);
 void            appendTabPage_Widget    (iWidget *tabs, iWidget *page, const char *label, int key, int kmods);
 void            appendFramelessTabPage_Widget(iWidget *tabs, iWidget *page, const char *title, int shortcut, int kmods);
 iWidget *       appendTwoColumnTabPage_Widget(iWidget *tabs, const char *title, int shortcut, iWidget **headings,
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 80a36db4..82ff4c57 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -168,8 +168,10 @@ void deinit_Widget(iWidget *d) {
 //    printf("deinit_Widget %p (%s):\ttreesize=%d\td_obj=%d\n", d, class_Widget(d)->name, nt, totalCount_Object() - no);
     delete_WidgetDrawBuffer(d->drawBuf);
 #if 0 && !defined (NDEBUG)
-    printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id),
-           d->flags & keepOnTop_WidgetFlag ? 1 : 0);
+    if (cmp_String(&d->id, "")) {
+        printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id),
+               d->flags & keepOnTop_WidgetFlag ? 1 : 0);
+    }
 #endif
     deinit_String(&d->id);
     if (d->flags & keepOnTop_WidgetFlag) {
@@ -960,7 +962,8 @@ int visualOffsetByReference_Widget(const iWidget *d) {
 }
 
 static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) {
-    if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) {
+    if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag) ||
+        (d->flags2 & permanentVisualOffset_WidgetFlag2)) {
         const int off = iRound(value_Anim(&d->visualOffset));
         if (d->flags & horizontalOffset_WidgetFlag) {
             pos->x += off;
@@ -1009,7 +1012,9 @@ iRect boundsWithoutVisualOffset_Widget(const iWidget *d) {
 
 iInt2 innerToWindow_Widget(const iWidget *d, iInt2 innerCoord) {
     for (const iWidget *w = d; w; w = w->parent) {
-        addv_I2(&innerCoord, w->rect.pos);
+        iInt2 pos = w->rect.pos;
+        applyVisualOffset_Widget_(w, &pos);
+        addv_I2(&innerCoord, pos);
     }
     return innerCoord;
 }
@@ -1026,13 +1031,13 @@ iBool contains_Widget(const iWidget *d, iInt2 windowCoord) {
 }
 
 iBool containsExpanded_Widget(const iWidget *d, iInt2 windowCoord, int expand) {
-    const iRect bounds = {
-        zero_I2(),
+    iRect bounds = {
+        innerToWindow_Widget(d, zero_I2()),
         addY_I2(d->rect.size,
                 d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0)
     };
     return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds,
-                         windowToInner_Widget(d, windowCoord));
+                         windowCoord);
 }
 
 iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) {
@@ -1056,7 +1061,8 @@ iLocalDef iBool isHidden_Widget_(const iWidget *d) {
 }
 
 iLocalDef iBool isDrawn_Widget_(const iWidget *d) {
-    return !isHidden_Widget_(d) || d->flags & visualOffset_WidgetFlag;
+    return !isHidden_Widget_(d) || (d->flags & visualOffset_WidgetFlag &&
+                                    ~d->flags2 & permanentVisualOffset_WidgetFlag2);
 }
 
 static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) {
@@ -1103,6 +1109,14 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
                     fflush(stdout);
                 }
 #endif
+#if 0
+                if (ev->type == SDL_MOUSEBUTTONDOWN) {
+                    printf("[%p] %s:'%s' (on top) ate the button\n",
+                           widget, class_Widget(widget)->name,
+                           cstr_String(id_Widget(widget)));
+                    fflush(stdout);
+                }
+#endif
 #if 0
                 if (ev->type == SDL_MOUSEMOTION) {
                     printf("[%p] %s:'%s' (on top) ate the motion\n",
@@ -1135,12 +1149,12 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
            handle the events first. */
         iReverseForEach(ObjectList, i, d->children) {
             iWidget *child = as_Widget(i.object);
-            //iAssert(child->root == d->root);
+            iAssert(child->root == d->root);
             if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {
                 continue; /* Already dispatched. */
             }
             if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
-            /* Already dispatched. */
+                /* Already dispatched. */
                 continue;
             }
             if (dispatchEvent_Widget(child, ev)) {
@@ -1180,8 +1194,9 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
                 return iTrue;
             }
         }
+        iAssert(get_Root() == d->root);
         if (class_Widget(d)->processEvent(d, ev)) {
-            //iAssert(get_Root() == d->root);
+            iAssert(get_Root() == d->root);
             return iTrue;
         }
     }
@@ -1387,6 +1402,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
                             d, argLabel_Command(cmd, "side") == 1 ? "swipe.back" : "swipe.forward");
                     }
                     setFlags_Widget(d, dragged_WidgetFlag, iFalse);
+                    return iTrue;
                 }
                 if (d->commandHandler && d->commandHandler(d, ev->user.data1)) {
                     return iTrue;
diff --git a/src/ui/widget.h b/src/ui/widget.h
index c1b3a9a4..7b9de966 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -127,6 +127,7 @@ enum iWidgetFlag2 {
     slidingSheetDraggable_WidgetFlag2       = iBit(1),
     fadeBackground_WidgetFlag2              = iBit(2),
     visibleOnParentSelected_WidgetFlag2     = iBit(3),
+    permanentVisualOffset_WidgetFlag2       = iBit(4), /* usually visual offset overrides hiding */
 };
 
 enum iWidgetAddPos {
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.12/cdiff/7d43fe3071b9ba333c5033e94285f67f54606b9c
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
99.114232 milliseconds
Gemini-to-HTML Time
1.525607 milliseconds

This content has been proxied by September (ba2dc).