Lagrange [work/v1.13]

Adapting UI for terminal

=> 7834830d05f785abd56282af6865d69e2a651165

diff --git a/Depends.cmake b/Depends.cmake
index 7408cfb4..d74ce89d 100644
--- a/Depends.cmake
+++ b/Depends.cmake
@@ -192,6 +192,10 @@ if (ENABLE_WINSPARKLE)
 endif ()
 
 find_package (PkgConfig REQUIRED)
-pkg_check_modules (SDL2 REQUIRED sdl2)
+if (ENABLE_TUI)
+    pkg_check_modules (SDL2 REQUIRED sealcurses)
+else ()
+    pkg_check_modules (SDL2 REQUIRED sdl2)
+endif ()
 pkg_check_modules (MPG123 IMPORTED_TARGET libmpg123)
 pkg_check_modules (WEBP IMPORTED_TARGET libwebp)
diff --git a/src/app.c b/src/app.c
index 8977914e..caf9d32d 100644
--- a/src/app.c
+++ b/src/app.c
@@ -61,6 +61,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -1646,8 +1647,46 @@ void processEvents_App(enum iAppEventMode eventMode) {
                 }
 #endif
                 iBool wasUsed = iFalse;
+                /* Focus navigation events take prioritity. */
+                if (!wasUsed) {
+                    /* Keyboard focus navigation with arrow keys. */
+                    iWidget *menubar = NULL;
+                    if (ev.type == SDL_KEYDOWN && ev.key.keysym.mod == 0 && focus_Widget() &&
+                        !cmp_String(id_Widget(parent_Widget(focus_Widget())), "menu")) {
+                        setCurrent_Window(window_Widget(focus_Widget()));
+                        const int key = ev.key.keysym.sym;
+                        if (key == SDLK_DOWN || key == SDLK_UP) {
+                            iWidget *nextFocus = findFocusable_Widget(focus_Widget(),
+                                                                      key == SDLK_UP
+                                                                          ? backward_WidgetFocusDir
+                                                                          : forward_WidgetFocusDir);
+                            if (nextFocus && parent_Widget(nextFocus) == parent_Widget(focus_Widget())) {
+                                setFocus_Widget(nextFocus);
+                            }
+                            wasUsed = iTrue;
+                        }
+                        else if ((key == SDLK_LEFT || key == SDLK_RIGHT) &&
+                                 (menubar = findParent_Widget(focus_Widget(), "menubar")) != NULL) {
+                            iWidget *button = parent_Widget(parent_Widget(focus_Widget()));
+                            size_t index = indexOfChild_Widget(menubar, button);
+                            const size_t curIndex = index;
+                            //printf("index:%zu\n", index); SDL_Delay(1000);
+                            if (key == SDLK_LEFT && index > 0) {
+                                index--;
+                            }
+                            else if (key == SDLK_RIGHT && index < childCount_Widget(menubar) - 1) {
+                                index++;
+                            }
+                            if (curIndex != index) {
+                                setFocus_Widget(child_Widget(menubar, index));                                
+                                postCommand_Widget(child_Widget(menubar, index), "trigger");
+                            }
+                            wasUsed = iTrue;
+                        }
+                    }
+                }
                 /* Per-window processing. */
-                if (!isEmpty_PtrArray(&d->mainWindows)) {
+                if (!wasUsed && !isEmpty_PtrArray(&d->mainWindows)) {
                     listWindows_App_(d, &windows);
                     iConstForEach(PtrArray, iter, &windows) {
                         iWindow *window = iter.ptr;
@@ -1861,6 +1900,9 @@ void refresh_App(void) {
                     break;
             }
             win->frameCount++;
+#if defined (iPlatformTerminal)
+            sleep_Thread(1.0 / 60.0);
+#endif
         }
     }
     if (d->warmupFrames > 0) {
diff --git a/src/defs.h b/src/defs.h
index 9e967217..764060b8 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -64,15 +64,6 @@ enum iScrollType {
     max_ScrollType
 };
 
-enum iReturnKeyFlag {
-    return_ReturnKeyFlag        = 0,
-    shiftReturn_ReturnKeyFlag   = 1,
-    controlReturn_ReturnKeyFlag = 2,
-    guiReturn_ReturnKeyFlag     = 3,
-    mask_ReturnKeyFlag          = 0xf,
-    accept_ReturnKeyFlag        = 4, /* shift */
-};
-
 enum iToolbarAction {
     back_ToolbarAction        = 0,
     forward_ToolbarAction     = 1,
@@ -91,23 +82,37 @@ enum iToolbarAction {
     max_ToolbarAction
 };
 
+enum iReturnKeyFlag {
+    noMod_ReturnKeyFlag   = 0,
+    shift_ReturnKeyFlag   = 1,
+    control_ReturnKeyFlag = 2,
+    gui_ReturnKeyFlag     = 3,
+    mask_ReturnKeyFlag    = 0xf,
+    accept_ReturnKeyFlag  = 4, /* shift */
+};
+
+#define RETURN_KEY_BEHAVIOR(newlineFlag, acceptFlag) \
+    ((newlineFlag) & 3 | ((acceptFlag) << accept_ReturnKeyFlag))
+
 /* Return key behavior is not handled via normal bindings because only certain combinations
    are valid. */
 enum iReturnKeyBehavior {
-    acceptWithoutMod_ReturnKeyBehavior =
-        shiftReturn_ReturnKeyFlag | (return_ReturnKeyFlag << accept_ReturnKeyFlag),
-    acceptWithShift_ReturnKeyBehavior =
-        return_ReturnKeyFlag | (shiftReturn_ReturnKeyFlag << accept_ReturnKeyFlag),
+//    acceptWithoutMod_ReturnKeyBehavior =
+//        shiftReturn_ReturnKeyFlag | (return_ReturnKeyFlag << accept_ReturnKeyFlag),    
+//    acceptWithShift_ReturnKeyBehavior =
+//        return_ReturnKeyFlag | (shiftReturn_ReturnKeyFlag << accept_ReturnKeyFlag),
     acceptWithPrimaryMod_ReturnKeyBehavior =
 #if defined (iPlatformApple)
-        return_ReturnKeyFlag | (guiReturn_ReturnKeyFlag << accept_ReturnKeyFlag),
+        RETURN_KEY_BEHAVIOR(0, gui_ReturnKeyFlag),
 #else
-        return_ReturnKeyFlag | (controlReturn_ReturnKeyFlag << accept_ReturnKeyFlag),
+        RETURN_KEY_BEHAVIOR(control_ReturnKeyFlag, 0),
 #endif
-#if defined (iPlatformAndroidMobile)
-    default_ReturnKeyBehavior = acceptWithShift_ReturnKeyBehavior,
+#if defined (iPlatformTerminal)
+    default_ReturnKeyBehavior = RETURN_KEY_BEHAVIOR(0, gui_ReturnKeyFlag),
+#elif defined (iPlatformAndroidMobile)
+    default_ReturnKeyBehavior = RETURN_KEY_BEHAVIOR(0, shift_ReturnKeyFlag),
 #else
-    default_ReturnKeyBehavior = acceptWithoutMod_ReturnKeyBehavior,
+    default_ReturnKeyBehavior = RETURN_KEY_BEHAVIOR(shift_ReturnKeyFlag, 0),
 #endif
 };
 
@@ -202,7 +207,10 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
 #define toggleYes_Icon      check_Icon
 #define toggleNo_Icon       bullet_Icon
 
-#if defined (iPlatformApple)
+#if defined (iPlatformTerminal)
+#   define shift_Icon       "Sh"
+#   define shiftReturn_Icon "Sh-" return_Icon
+#elif defined (iPlatformApple)
 #   define shift_Icon       "\u21e7"
 #   define shiftReturn_Icon shift_Icon return_Icon
 #else
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 6e0d14d4..bfd477ba 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -640,10 +640,10 @@ static void doLayout_GmDocument_(iGmDocument *d) {
     const iPrefs *prefs             = prefs_App();
     const iBool   isMono            = isForcedMonospace_GmDocument_(d);
     const iBool   isGopher          = isGopher_GmDocument_(d);
-    const iBool   isNarrow          = d->size.x < 90 * gap_Text;
-    const iBool   isVeryNarrow      = d->size.x <= 70 * gap_Text;
-    const iBool   isExtremelyNarrow = d->size.x <= 60 * gap_Text;
-    const iBool   isFullWidthImages = (d->outsideMargin < 5 * gap_UI);
+    const iBool   isNarrow          = d->size.x < 90 * gap_Text * aspect_UI;
+    const iBool   isVeryNarrow      = d->size.x <= 70 * gap_Text * aspect_UI;
+    const iBool   isExtremelyNarrow = d->size.x <= 60 * gap_Text * aspect_UI;
+    const iBool   isFullWidthImages = (d->outsideMargin < 5 * gap_UI * aspect_UI);
     
     initTheme_GmDocument_(d);
     d->isLayoutInvalidated = iFalse;
@@ -873,15 +873,12 @@ static void doLayout_GmDocument_(iGmDocument *d) {
         if (type == bullet_GmLineType) {
             /* TODO: Literata bullet is broken? */
             iGmRun bulRun = run;
-#if 0
-            if (prefs->font == literata_TextFont) {
-                /* Something wrong this the glyph in Literata, looks cropped. */
-                bulRun.font = FONT_ID(default_FontId, regular_FontStyle,
-                                                 contentRegular_FontSize);
-            }
-#endif
             bulRun.color = tmQuote_ColorId;
+#if defined (iPlatformTerminal)
+            bulRun.visBounds.pos = addX_I2(pos, indents[text_GmLineType] * gap_Text);
+#else
             bulRun.visBounds.pos = addX_I2(pos, (indents[text_GmLineType] - 0.55f) * gap_Text);
+#endif
             bulRun.visBounds.size =
                 init_I2((indents[bullet_GmLineType] - indents[text_GmLineType]) * gap_Text,
                         lineHeight_Text(bulRun.font));
@@ -903,8 +900,8 @@ static void doLayout_GmDocument_(iGmDocument *d) {
             quoteRun.visBounds.size = measure_Text(quoteRun.font, quote).bounds.size;
             quoteRun.visBounds.pos =
                 add_I2(pos,
-                       init_I2((indents[quote_GmLineType] - 5) * gap_Text,
-                               lineHeight_Text(quote_FontId) / 2 - bottom_Rect(vis)));
+                       init_I2((indents[quote_GmLineType] - 5 * aspect_UI) * gap_Text,
+                               (lineHeight_Text(quote_FontId) / 2 - bottom_Rect(vis)) * aspect_UI));
             quoteRun.bounds = zero_Rect(); /* just visual */
             quoteRun.flags |= decoration_GmRunFlag;
             pushBack_Array(&d->layout, "eRun);
diff --git a/src/prefs.c b/src/prefs.c
index fa7af089..f304da11 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -67,6 +67,9 @@ void init_Prefs(iPrefs *d) {
         d->bottomNavBar  = iFalse;
         d->bottomTabBar  = iFalse;        
     }
+#if defined (iPlatformTerminal)
+    d->bottomNavBar      = iTrue;
+#endif
     d->menuBar           = (deviceType_App() == desktop_AppDeviceType);
     d->pinSplit          = 1;
     d->time24h           = iTrue;
diff --git a/src/ui/banner.c b/src/ui/banner.c
index 9ff3e524..e9f9457c 100644
--- a/src/ui/banner.c
+++ b/src/ui/banner.c
@@ -63,15 +63,22 @@ struct Impl_Banner {
 
 iDefineTypeConstruction(Banner)
 
-#define itemGap_Banner_     (3 * gap_UI)
-#define itemVPad_Banner_    (2 * gap_UI)
-#define itemHPad_Banner_    (3 * gap_UI)
-#define bottomPad_Banner_   (4 * gap_UI)
+#if defined (iPlatformTerminal)
+#   define itemGap_Banner_     (1 * gap_UI)
+#   define itemVPad_Banner_    (1 * gap_UI)
+#   define itemHPad_Banner_    (2 * gap_UI)
+#   define bottomPad_Banner_   (1 * gap_UI)
+#else
+#   define itemGap_Banner_     (3 * gap_UI)
+#   define itemVPad_Banner_    (2 * gap_UI)
+#   define itemHPad_Banner_    (3 * gap_UI)
+#   define bottomPad_Banner_   (4 * gap_UI)
+#endif
 
 static void updateHeight_Banner_(iBanner *d) {
     d->rect.size.y = 0;
     if (!isEmpty_String(&d->site)) {
-        d->siteHeight = lineHeight_Text(banner_FontId) * 2;
+        d->siteHeight = iMax(3, lineHeight_Text(banner_FontId) * 2);
         d->rect.size.y += d->siteHeight;
     }
     const size_t numItems = size_Array(&d->items);
@@ -204,7 +211,7 @@ void draw_Banner(const iBanner *d) {
     }
     iRect  bounds = d->rect;
     /* TODO: use d->siteHeight */
-    iInt2  pos    = addY_I2(topLeft_Rect(bounds), lineHeight_Text(banner_FontId) / 2);
+    iInt2  pos    = addY_I2(topLeft_Rect(bounds), iMax(1, lineHeight_Text(banner_FontId) / 2));
     iPaint p;
     init_Paint(&p);
 //    drawRect_Paint(&p, bounds, red_ColorId);
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c
index b7a1a5a6..7d8e7999 100644
--- a/src/ui/certimportwidget.c
+++ b/src/ui/certimportwidget.c
@@ -125,7 +125,7 @@ void init_CertImportWidget(iCertImportWidget *d) {
 #endif
         { "${cancel}" },
         { uiTextAction_ColorEscape "${dlg.certimport.import}",
-          SDLK_RETURN, KMOD_PRIMARY,
+          SDLK_RETURN, KMOD_ACCEPT,
           "certimport.accept" }
     };
     init_Widget(w);
diff --git a/src/ui/certlistwidget.c b/src/ui/certlistwidget.c
index 61851d22..e514f974 100644
--- a/src/ui/certlistwidget.c
+++ b/src/ui/certlistwidget.c
@@ -372,9 +372,12 @@ static void draw_CertItem_(const iCertItem *d, iPaint *p, iRect itemRect,
     iString icon;
     initUnicodeN_String(&icon, &d->icon, 1);
     iInt2 cPos = topLeft_Rect(itemRect);
-    const int indent = 1.4f * lineHeight_Text(font);
+    int indent = 1.4f * lineHeight_Text(font);
+#if defined (iPlatformTerminal)
+    indent += 2 * gap_UI;
+#endif
     addv_I2(&cPos,
-            init_I2(3 * gap_UI,
+            init_I2(3 * gap_UI * aspect_UI,
                     (itemHeight - lineHeight_Text(uiLabel_FontId) * 2 - lineHeight_Text(font)) /
                         2));
     const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId
@@ -420,7 +423,12 @@ void init_CertListWidget(iCertListWidget *d) {
 }
 
 void updateItemHeight_CertListWidget(iCertListWidget *d) {
-    setItemHeight_ListWidget(&d->list, 3.5f * lineHeight_Text(d->itemFonts[0]));
+#if !defined (iPlatformTerminal)
+    const float height = 3.5f;
+#else
+    const int height = 4;
+#endif 
+    setItemHeight_ListWidget(&d->list, height * lineHeight_Text(d->itemFonts[0]));
 }
 
 iBool updateItems_CertListWidget(iCertListWidget *d) {
diff --git a/src/ui/color.h b/src/ui/color.h
index 0d94f0c2..356831d2 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -195,6 +195,7 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) {
 #define permanent_ColorId           0x80  /* cannot be changed via escapes */
 #define fillBackground_ColorId      0x100 /* fill background with same color, but alpha 0 */
 #define opaque_ColorId              0x200
+#define underline_ColorId           0x400
 
 #define asciiBase_ColorEscape       33
 #define asciiExtended_ColorEscape   (128 - asciiBase_ColorEscape)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 53e63414..478e2a26 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -547,13 +547,13 @@ static int documentWidth_DocumentView_(const iDocumentView *d) {
     const iWidget *w        = constAs_Widget(d->owner);
     const iRect    bounds   = bounds_Widget(w);
     const iPrefs * prefs    = prefs_App();
-    const int      minWidth = 50 * gap_UI; /* lines must fit a word at least */
+    const int      minWidth = 50 * gap_UI * aspect_UI; /* lines must fit a word at least */
     const float    adjust   = iClamp((float) bounds.size.x / gap_UI / 11 - 12,
                                 -1.0f, 10.0f); /* adapt to width */
     //printf("%f\n", adjust); fflush(stdout);
     int prefsWidth = prefs->lineWidth;
 #if defined (iPlatformTerminal)
-    prefsWidth /= aspect_UI;
+    prefsWidth /= aspect_UI * 0.8f;
 #endif
     return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2),
                  fontSize_UI * prefsWidth * prefs->zoomPercent / 100);
@@ -1919,7 +1919,7 @@ static void draw_DocumentView_(const iDocumentView *d) {
                                     init_I2(bounds.size.x, documentTopPad_DocumentView_(d)) },
                            docBgColor);
             setPos_Banner(banner, addY_I2(topLeft_Rect(docBounds),
-                                          -pos_SmoothScroll(&d->scrollY)));
+                                          floorf(-pos_SmoothScroll(&d->scrollY))));
             draw_Banner(banner);
         }
         const int yBottom = yTop + size_GmDocument(d->doc).y;
diff --git a/src/ui/font.h b/src/ui/font.h
index 34b399bf..6b429a55 100644
--- a/src/ui/font.h
+++ b/src/ui/font.h
@@ -83,6 +83,10 @@ iLocalDef enum iFontStyle style_FontId(enum iFontId id) {
     return (id / max_FontSize) % max_FontStyle;
 }
 
+iLocalDef enum iFontSize size_FontId(enum iFontId id) {
+    return id % max_FontSize;
+}
+
 iLocalDef iBool isControl_Char(iChar c) {
     return isDefaultIgnorable_Char(c) || isVariationSelector_Char(c) || isFitzpatrickType_Char(c);
 }
@@ -240,6 +244,7 @@ enum iRunMode {
     permanentColorFlag_RunMode      = iBit(11),
     alwaysVariableWidthFlag_RunMode = iBit(12),
     fillBackground_RunMode          = iBit(13),
+    underline_RunMode               = iBit(14),
 };
 
 int     runFlags_FontId (enum iFontId fontId);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index d843eb20..950558de 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -39,6 +39,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #include 
 #include 
 #include 
+#include 
 
 #if defined (iPlatformAppleDesktop)
 #   include "macos.h"
@@ -732,8 +733,16 @@ static uint32_t cursorTimer_(uint32_t interval, void *w) {
     return interval;
 }
 
+iLocalDef iBool isBlinkingCursor_(void) {
+#if defined (iPlatformTerminal)
+    return iFalse; /* terminal will do it */
+#else
+    return prefs_App()->blinkingCursor;
+#endif
+}
+
 static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, int doStart) {
-    if (!prefs_App()->blinkingCursor && doStart == 1) {
+    if (!isBlinkingCursor_() && doStart == 1) {
         doStart = iFalse;
     }
     if (doStart && !d->timer) {
@@ -785,7 +794,7 @@ static void updateTextInputRect_InputWidget_(const iInputWidget *d) {
         setRect_SystemTextInput(d->sysCtrl, contentBounds_InputWidget_(d));
     }
 #endif
-#if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile)
+#if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) && !defined (SDL_SEAL_CURSES)
     const iRect bounds = bounds_Widget(constAs_Widget(d));
     SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y });
 #endif
@@ -2696,11 +2705,13 @@ static void draw_InputWidget_(const iInputWidget *d) {
     /* `lines` is already up to date and ready for drawing. */
     fillRect_Paint(
         &p, bounds, isFocused ? uiInputBackgroundFocused_ColorId : uiInputBackground_ColorId);
+#if !defined (iPlatformTerminal)
     drawRectThickness_Paint(&p,
                             adjusted_Rect(bounds, neg_I2(one_I2()), zero_I2()),
                             isFocused ? gap_UI / 4 : 1,
                             isFocused ? uiInputFrameFocused_ColorId
-                                      : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
+                            : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId);
+#endif
     if (d->sysCtrl) {
         /* The system-provided control is drawing the text. */
         drawChildren_Widget(w);
@@ -2779,7 +2790,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
         wrapText.context  = NULL;
     }
     /* Draw the insertion point. */
-    if (isFocused && (d->cursorVis || !prefs_App()->blinkingCursor) &&
+    if (isFocused && (d->cursorVis || !isBlinkingCursor_()) &&
         contains_Range(&visLines, d->cursor.y) &&
         (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) {
         iInt2    curSize;
@@ -2818,6 +2829,10 @@ static void draw_InputWidget_(const iInputWidget *d) {
                                     addX_I2(advance,
                                             (d->mode == insert_InputMode ? -curSize.x / 2 : 0)));
         const iRect curRect  = { curPos, curSize };
+#if defined (SDL_SEAL_CURSES)
+        /* Tell where to place the terminal cursor. */
+        SDL_SetTextInputRect((const SDL_Rect *) &curRect);  
+#endif
         fillRect_Paint(&p, curRect, uiInputCursor_ColorId);
         if (d->mode == overwrite_InputMode) {
             /* The `gap_UI` offset below is a hack. They are used because for some reason the
diff --git a/src/ui/keys.h b/src/ui/keys.h
index b4ea3e5d..751af068 100644
--- a/src/ui/keys.h
+++ b/src/ui/keys.h
@@ -33,7 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #   define preferences_KeyShortcut      SDLK_COMMA,         0
 #   define reload_KeyShortcut           SDLK_r,             0
 #   define newTab_KeyShortcut           SDLK_t,             0
-#   define closeTab_KeyShortcut         SDLK_w,             KMOD_SHIFT
+#   define closeTab_KeyShortcut         SDLK_w,             KMOD_PRIMARY
 #   define prevTab_KeyShortcut          SDLK_LEFTBRACKET,   0
 #   define nextTab_KeyShortcut          SDLK_RIGHTBRACKET,  0
 #   define navigateBack_KeyShortcut     SDLK_LEFT,          0
@@ -42,10 +42,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #   define navigateRoot_KeyShortcut     SDLK_r,             KMOD_PRIMARY
 #   define subscribeToPage_KeyShortcut  SDLK_d,             0
 #   define leftSidebar_KeyShortcut      SDLK_l,             KMOD_SHIFT
+#   define rightSidebar_KeyShortcut     SDLK_p,             KMOD_SHIFT
 #   define leftSidebarTab_KeyModifier   0
 #   define byWord_KeyModifier           KMOD_ALT
 #   define byLine_KeyModifier           KMOD_PRIMARY
-#   define rightSidebarTab_KeyModifier  KMOD_CTRL
+#   define rightSidebarTab_KeyModifier  KMOD_ALT
 #elif defined (iPlatformApple)
 #   define pageInfo_KeyShortcut         SDLK_i, KMOD_PRIMARY
 #   define preferences_KeyShortcut      SDLK_COMMA,         KMOD_PRIMARY
@@ -60,6 +61,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #   define navigateRoot_KeyShortcut     SDLK_UP,            KMOD_SHIFT | KMOD_PRIMARY
 #   define subscribeToPage_KeyShortcut  SDLK_d,             KMOD_SHIFT | KMOD_PRIMARY
 #   define leftSidebar_KeyShortcut      SDLK_l,             KMOD_PRIMARY | KMOD_SHIFT
+#   define rightSidebar_KeyShortcut     SDLK_p,             KMOD_PRIMARY | KMOD_SHIFT
 #   define leftSidebarTab_KeyModifier   KMOD_PRIMARY
 #   define byWord_KeyModifier           KMOD_ALT
 #   define byLine_KeyModifier           KMOD_PRIMARY
@@ -78,6 +80,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #   define navigateRoot_KeyShortcut     SDLK_UP,            KMOD_SHIFT | KMOD_ALT
 #   define subscribeToPage_KeyShortcut  SDLK_d,             KMOD_SHIFT | KMOD_PRIMARY
 #   define leftSidebar_KeyShortcut      SDLK_l,             KMOD_PRIMARY | KMOD_SHIFT
+#   define rightSidebar_KeyShortcut     SDLK_p,             KMOD_PRIMARY | KMOD_SHIFT
 #   define leftSidebarTab_KeyModifier   KMOD_PRIMARY
 #   define byWord_KeyModifier           KMOD_CTRL
 #   define byLine_KeyModifier           0
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 926cfecd..074e5309 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -368,6 +368,12 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
     init_Paint(&p);
     int bg, fg, frame, frame2, iconColor, metaColor;
     getColors_LabelWidget_(d, &bg, &fg, &frame, &frame2, &iconColor, &metaColor);
+#if defined (iPlatformTerminal)
+    /* Indicate focused label with an underline attribute. */
+    if (isFocused_Widget(w)) {
+        fg |= underline_ColorId;
+    }
+#endif
     setBaseAttributes_Text(d->font, fg);
     const enum iColorId colorEscape = parseEscape_Color(cstr_String(&d->label), NULL);
     const iBool isCaution = (colorEscape == uiTextCaution_ColorId);
@@ -376,7 +382,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
     }
     if (isFocused_Widget(w)) {
         iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1));
-        drawRectThickness_Paint(&p, frameRect, gap_UI / 4, frame);        
+        drawRectThickness_Paint(&p, frameRect, gap_UI / 4, uiTextAction_ColorId /*frame*/);        
     }
     else if (~flags & frameless_WidgetFlag) {
         iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1));
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index e14b43a9..3339ce14 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -397,6 +397,9 @@ void init_LookupWidget(iLookupWidget *d) {
     d->list = addChildFlags_Widget(w, iClob(new_ListWidget()),
                                    resizeToParentWidth_WidgetFlag |
                                    resizeToParentHeight_WidgetFlag);
+#if defined (iPlatformTerminal)
+    setPadding_Widget(as_Widget(d->list), 2, 2, 2, 2);
+#endif
     d->cursor = iInvalidPos;
     d->work = new_Thread(worker_LookupWidget_);
     setUserData_Thread(d->work, d);
@@ -683,7 +686,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) {
                                       : bottomLeft_Rect(bounds_Widget(url));
             setPos_Widget(
                 w, windowToLocal_Widget(w, max_I2(zero_I2(), addX_I2(topLeft, -extraWidth / 2))));
-#if defined(iPlatformMobile)
+#if defined (iPlatformMobile)
             /* TODO: Check this again. */
             /* Adjust height based on keyboard size. */ {
                 if (!atBottom) {
diff --git a/src/ui/metrics.c b/src/ui/metrics.c
index 1379207a..c3b7f339 100644
--- a/src/ui/metrics.c
+++ b/src/ui/metrics.c
@@ -28,7 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #if defined (iPlatformTerminal)
 #   define defaultFontSize_Metrics     1
 #   define defaultGap_Metrics          1
-const float aspect_UI = 0.4f;
+const float aspect_UI = 0.5f;
 #else
 #   define defaultFontSize_Metrics     18
 #   define defaultGap_Metrics          4
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 5f19ec3e..ba9fbdab 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -125,6 +125,11 @@ void drawRect_Paint(const iPaint *d, iRect rect, int color) {
 }
 
 void drawRectThickness_Paint(const iPaint *d, iRect rect, int thickness, int color) {
+#if defined (iPlatformTerminal)
+    if (thickness == 0) {
+        addv_I2(&rect.size, one_I2());
+    }
+#endif
     thickness = iClamp(thickness, 1, 4);
     while (thickness--) {
         drawRect_Paint(d, rect, color);
diff --git a/src/ui/root.c b/src/ui/root.c
index b0bffd58..0f3c831c 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -63,7 +63,7 @@ static const iMenuItem desktopNavMenuItems_[] = {
     { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" },
     { "---" },
     { leftHalf_Icon " ${menu.sidebar.left}", leftSidebar_KeyShortcut, "sidebar.toggle" },
-    { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" },
+    { rightHalf_Icon " ${menu.sidebar.right}", rightSidebar_KeyShortcut, "sidebar2.toggle" },
     { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
     { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" },
     { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" },
@@ -90,7 +90,7 @@ static const iMenuItem tabletNavMenuItems_[] = {
     { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" },
     { "---" },
     { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" },
-    { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" },
+    { rightHalf_Icon " ${menu.sidebar.right}", rightSidebar_KeyShortcut, "sidebar2.toggle" },
     { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" },
     { "---" },
     { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" },
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c
index 1ea8d3ba..3e06ee90 100644
--- a/src/ui/scrollwidget.c
+++ b/src/ui/scrollwidget.c
@@ -30,6 +30,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 
 iDefineObjectConstruction(ScrollWidget)
 
+#if defined (iPlatformTerminal)
+const int fadeTime_ScrollWidget_   = 10;
+const int unfadeTime_ScrollWidget_ = 10;
+#else
+const int fadeTime_ScrollWidget_   = 200;
+const int unfadeTime_ScrollWidget_ = 66;
+#endif
+
 static float minOpacity_(void) {
 #if !defined (iPlatformApple)
     if (deviceType_App() == desktop_AppDeviceType) {
@@ -125,7 +133,7 @@ static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) {
 static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) {
     d->fadeStart = SDL_GetTicks() + 1000;
     if (targetValue_Anim(&d->opacity) < opacity) {
-        setValue_Anim(&d->opacity, opacity, 66);
+        setValue_Anim(&d->opacity, opacity, unfadeTime_ScrollWidget_);
         addTickerRoot_App(animateOpacity_ScrollWidget_, as_Widget(d)->root, d);
     }
     if (!d->willCheckFade && d->fadeEnabled) {
@@ -180,7 +188,7 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) {
     }
     if (isCommand_UserEvent(ev, "scrollbar.fade")) {
         if (d->fadeEnabled && d->willCheckFade && SDL_GetTicks() > d->fadeStart) {
-            setValue_Anim(&d->opacity, minOpacity_(), 200);
+            setValue_Anim(&d->opacity, minOpacity_(), fadeTime_ScrollWidget_);
             remove_Periodic(periodic_App(), d);
             d->willCheckFade = iFalse;
             if (!isFinished_Anim(&d->opacity)) {
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index ccf9c8a4..bd5e2109 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -698,7 +698,12 @@ static size_t findItem_SidebarWidget_(const iSidebarWidget *d, int id) {
 }
 
 static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) {
-    const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 3.5f, 1.2f };
+    /* Note: identity item height is defined by CertListWidget */
+#if !defined (iPlatformTerminal)
+    const float heights[max_SidebarMode] = { 1.333f, 2.333f, 1.333f, 0, 1.2f };
+#else
+    const float heights[max_SidebarMode] = { 1, 3, 1, 0, 1 };
+#endif
     if (d->list) {
         setItemHeight_ListWidget(d->list, heights[d->mode] * lineHeight_Text(d->itemFonts[0]));
     }
@@ -2050,7 +2055,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
         const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
                                : (tmHeading1_ColorId + d->indent / (4 * gap_UI));
         drawRange_Text(font,
-                       init_I2(pos.x + 3 * gap_UI + d->indent,
+                       init_I2(pos.x + (3 * gap_UI + d->indent) * aspect_UI,
                                mid_Rect(itemRect).y - lineHeight_Text(font) / 2),
                        fg,
                        range_String(&d->label));
@@ -2069,7 +2074,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
             drawRange_Text(
                 uiLabelLargeBold_FontId,
                 add_I2(pos,
-                       init_I2(3 * gap_UI,
+                       init_I2(3 * gap_UI * aspect_UI,
                                itemHeight - lineHeight_Text(uiLabelLargeBold_FontId) - 1 * gap_UI)),
                 uiIcon_ColorId,
                 range_String(&d->meta));
@@ -2079,14 +2084,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
             const int titleFont = sidebar->itemFonts[isUnread ? 1 : 0];
             const int h1 = lineHeight_Text(uiLabel_FontId);
             const int h2 = lineHeight_Text(titleFont);
-            iRect iconArea = { addY_I2(pos, 0), init_I2(iconPad, itemHeight) };
-            /*
-            if (isUnread) {
-                fillRect_Paint(
-                    p,
-                    (iRect){ topLeft_Rect(iconArea), init_I2(gap_UI / 2, height_Rect(iconArea)) },
-                    iconColor);
-            }*/
+            iRect iconArea = { addY_I2(pos, 0), init_I2(iconPad * aspect_UI, itemHeight) };
             /* Icon. */ {
                 /* TODO: Use the primary hue from the theme of this site. */
                 iString str;
@@ -2111,7 +2109,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
             int         metaFg    = isPressing ? fg : uiSubheading_ColorId;
             iInt2       titleSize = measureRange_Text(titleFont, range_String(&d->label)).bounds.size;
             const iInt2 metaSize  = measureRange_Text(uiLabel_FontId, range_String(&d->meta)).bounds.size;
-            pos.x += iconPad;
+            pos.x += iconPad * aspect_UI;
             const int avail = width_Rect(itemRect) - iconPad - 3 * gap_UI;
             const int labelFg = isPressing ? fg : (isUnread ? uiTextStrong_ColorId : uiText_ColorId);
             if (titleSize.x > avail && metaSize.x < avail * 0.75f) {
@@ -2150,7 +2148,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
         appendChar_String(&str, d->icon ? d->icon : 0x1f588);
         const int leftIndent = d->indent * gap_UI * 4;
         const iRect iconArea = { addX_I2(pos, gap_UI + leftIndent),
-                                 init_I2(1.75f * lineHeight_Text(font), itemHeight) };
+                                 init_I2(1.75f * lineHeight_Text(font) / aspect_UI, itemHeight) };
         drawCentered_Text(font,
                           iconArea,
                           iTrue,
@@ -2217,7 +2215,8 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
                 drawRange_Text(
                     uiLabelLargeBold_FontId,
                     add_I2(drawPos,
-                           init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(uiLabelLargeBold_FontId)) / 2)),
+                           init_I2(3 * gap_UI * aspect_UI,
+                                   (itemHeight - lineHeight_Text(uiLabelLargeBold_FontId)) / 2)),
                     uiIcon_ColorId,
                     range_String(&d->meta));
             }
diff --git a/src/ui/text.c b/src/ui/text.c
index bf3ddf6e..ccb15151 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -180,6 +180,7 @@ static void drawBoundedN_Text_(int fontId, iInt2 pos, int boundWidth, iBool just
               &(iRunArgs){ .mode = draw_RunMode |
                                    (color & permanent_ColorId ? permanentColorFlag_RunMode : 0) |
                                    (color & fillBackground_ColorId ? fillBackground_RunMode : 0) |
+                                   (color & underline_ColorId ? underline_RunMode : 0) |
                                    runFlags_FontId(fontId),
                            .text        = text,
                            .maxLen      = maxLen,
@@ -241,6 +242,7 @@ void drawRangeN_Text(int fontId, iInt2 pos, int color, iRangecc text, size_t max
 }
 
 void drawOutline_Text(int fontId, iInt2 pos, int outlineColor, int fillColor, iRangecc text) {
+#if !defined (iPlatformTerminal)
     for (int off = 0; off < 4; ++off) {
         drawRange_Text(fontId,
                        add_I2(pos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)),
@@ -250,6 +252,9 @@ void drawOutline_Text(int fontId, iInt2 pos, int outlineColor, int fillColor, iR
     if (fillColor != none_ColorId) {
         drawRange_Text(fontId, pos, fillColor, text);
     }
+#else
+    drawRange_Text(fontId, pos, fillColor | fillBackground_ColorId, text);
+#endif    
 }
 
 iTextMetrics measureWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c
index fceb7a66..04e2ee74 100644
--- a/src/ui/text_simple.c
+++ b/src/ui/text_simple.c
@@ -108,12 +108,16 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
         SDL_SetRenderTextAttributes(
             render,
             (style == bold_FontStyle || style == semiBold_FontStyle ? SDL_TEXT_ATTRIBUTE_BOLD : 0) |
-                (style == italic_FontStyle ? SDL_TEXT_ATTRIBUTE_ITALIC : 0));
+                (style == italic_FontStyle ? SDL_TEXT_ATTRIBUTE_ITALIC : 0) |
+                (mode & underline_RunMode ? SDL_TEXT_ATTRIBUTE_BOLD | SDL_TEXT_ATTRIBUTE_UNDERLINE : 0));
 #endif
-    }
-    if (args->mode & fillBackground_RunMode) {
-        const iColor initial = get_Color(args->color);
-        SDL_SetRenderDrawColor(render, initial.r, initial.g, initial.b, 0);
+        if (args->mode & fillBackground_RunMode) {
+            const iColor initial = get_Color(args->color);
+            SDL_SetRenderDrawColor(render, initial.r, initial.g, initial.b, 0);
+//#if defined (SDL_SEAL_CURSES)
+//            SDL_SetRenderTextFillColor(render, initial.r, initial.g, initial.b, 255);
+//#endif
+        }
     }
     /* Text rendering is not very straightforward! Let's dive in... */
     iChar       prevCh = 0;
@@ -409,6 +413,11 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
     if (args->runAdvance_out) {
         *args->runAdvance_out = xposMax - orig.x;
     }
+#if defined (SDL_SEAL_CURSES)
+    if (mode & draw_RunMode) {
+        SDL_SetRenderTextFillColor(render, 0, 0, 0, 0);
+    }
+#endif
     return bounds;
 }
 
diff --git a/src/ui/text_terminal.c b/src/ui/text_terminal.c
index 9f9a0a66..4f94d07e 100644
--- a/src/ui/text_terminal.c
+++ b/src/ui/text_terminal.c
@@ -49,12 +49,6 @@ struct Impl_Font {
 static const iGlyph *glyph_Font_(iFont *d, iChar ch) {
     int w = width_Char(ch);
     w = iMin(2, w);
-//    if (ch == 0x200b) { /* zero-width code points */
-//        w = 0; 
-//    }
-//    else if (isEmoji_Char(ch) || ch == 0x2014 /* em dash */) {
-//        w = 2;
-//    }
     return &d->glyphs[w];   
 }
 
@@ -67,19 +61,19 @@ static iBool isRasterized_Glyph_(const iGlyph *d, int hoff) {
     return iTrue;
 }
 
-static void init_Font(iFont *d) {
+static void init_Font(iFont *d, int height) {
     d->spec = new_FontSpec();    
     d->font.file = NULL;
     d->font.spec = d->spec;
-    d->font.height = 1;
+    d->font.height = height;
     d->baseline = 0;
     for (int i = 0; i < 3; i++) {
         iGlyph *glyph = &d->glyphs[i];
         glyph->font = d;
         glyph->advance = i;
         for (size_t j = 0; j < iElemCount(glyph->d); j++) {
-            glyph->d[j]    = zero_I2();
-            glyph->rect[j] = init_Rect(0, 0, i, 1);
+            glyph->d[j]    = init_I2(0, height / 2);
+            glyph->rect[j] = init_Rect(0, 0, i, height);
         }
     }
 }
@@ -96,7 +90,7 @@ iDeclareType(TuiText)
 
 struct Impl_TuiText {
     iText  base;
-    iFont  fonts[3]; /* regular, bold, italic */
+    iFont  fonts[2][3]; /* height / regular, bold, italic  */
 };
 
 iLocalDef iTuiText *current_TuiText_(void) {
@@ -104,19 +98,28 @@ iLocalDef iTuiText *current_TuiText_(void) {
 }
 
 iBaseFont *font_Text(enum iFontId id) {
-    const enum iFontStyle style = style_FontId(id);    
-    size_t index = (style == bold_FontStyle || style == semiBold_FontStyle ? 1 :
-                        style == italic_FontStyle ? 2 : 0);
-    return ¤t_TuiText_()->fonts[index].font;
+    const enum iFontStyle style = style_FontId(id);
+    const enum iFontSize  size  = size_FontId(id);    
+    size_t sizeIndex = (size == contentHuge_FontSize ? 1 : 0);
+    size_t index     = (style == bold_FontStyle || style == semiBold_FontStyle ? 1
+                        : style == italic_FontStyle                            ? 2
+                                                                               : 0);
+    return ¤t_TuiText_()->fonts[sizeIndex][index].font;
 }
 
 enum iFontId fontId_Text(const iAnyFont *font) {
-    const iTuiText *d = current_TuiText_();
-    if (font == &d->fonts[2]) {
-        return FONT_ID(default_FontId, italic_FontStyle, 0);
-    }
-    if (font == &d->fonts[1]) {
-        return FONT_ID(default_FontId, bold_FontStyle, 0);
+    for (size_t sizeIndex = 0; sizeIndex < 2; sizeIndex++) {
+        const iTuiText *d = current_TuiText_();
+        const enum iFontSize size = (sizeIndex == 1 ? contentHuge_FontSize : 0);
+        if (font == &d->fonts[sizeIndex][2]) {
+            return FONT_ID(default_FontId, italic_FontStyle, size);
+        }
+        if (font == &d->fonts[sizeIndex][1]) {
+            return FONT_ID(default_FontId, bold_FontStyle, size);
+        }
+        if (font == &d->fonts[sizeIndex][0]) {
+            return FONT_ID(default_FontId, regular_FontStyle, size);
+        }
     }
     return default_FontId;
 }
@@ -128,15 +131,19 @@ iBaseFont *characterFont_BaseFont(iBaseFont *d, iChar ch) {
 
 static void init_TuiText(iTuiText *d, SDL_Renderer *render) {
     init_Text(&d->base, render);
-    iForIndices(i, d->fonts) {
-        init_Font(d->fonts + i);
+    iForIndices(s, d->fonts) {
+        iForIndices(i, d->fonts[s]) {
+            init_Font(d->fonts[s] + i, s == 1 ? 2 : 1);
+        }        
     }
     gap_Text = gap_UI;
 }
 
 static void deinit_TuiText(iTuiText *d) {
-    iForIndices(i, d->fonts) {
-        deinit_Font(d->fonts + i);
+    iForIndices(s, d->fonts) {
+        iForIndices(i, d->fonts[s]) {
+            deinit_Font(d->fonts[s] + i);
+        }
     }
     deinit_Text(&d->base);
 }
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index 16622cab..8ed38a99 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -177,7 +177,7 @@ void init_UploadWidget(iUploadWidget *d) {
         { "${upload.port}", 0, 0, "upload.setport" },
         { "---" },
         { "${close}", SDLK_ESCAPE, 0, "upload.cancel" },
-        { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, KMOD_PRIMARY, "upload.accept" }
+        { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, KMOD_ACCEPT, "upload.accept" }
     };
     if (isUsingPanelLayout_Mobile()) {
         const int infoFont = (deviceType_App() == phone_AppDeviceType ? uiLabelBig_FontId
diff --git a/src/ui/util.c b/src/ui/util.c
index e71a7483..a46d6117 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -82,7 +82,41 @@ static void removePlus_(iString *str) {
 }
 
 void toString_Sym(int key, int kmods, iString *str) {
-#if defined (iPlatformApple)
+#if defined (iPlatformTerminal)
+    if (kmods & KMOD_CTRL) {
+        appendCStr_String(str, "^");
+    }
+    if (kmods & (KMOD_ALT | KMOD_GUI)) {
+        appendCStr_String(str, "M-");
+    }
+    if (kmods & KMOD_SHIFT) {
+        appendCStr_String(str, "Sh-");
+    }
+    if (key == SDLK_BACKSPACE) {
+        removePlus_(str);
+        appendCStr_String(str, "BSP"); /* Erase to the Left */
+        return;
+    }
+    else if (key == 0x20) {
+        appendCStr_String(str, "SPC");
+        return;
+    }
+    else if (key == SDLK_ESCAPE) {
+        removePlus_(str);
+        appendCStr_String(str, "ESC"); /* Erase to the Right */
+        return;
+    }
+    else if (key == SDLK_DELETE) {
+        removePlus_(str);
+        appendCStr_String(str, "DEL"); /* Erase to the Right */
+        return;
+    }
+    else if (key == SDLK_RETURN) {
+        removePlus_(str);
+        appendCStr_String(str, "RET"); /* Leftwards arrow with a hook */
+        return;
+    }
+#elif defined (iPlatformApple)
     if (kmods & KMOD_CTRL) {
         appendChar_String(str, 0x2303);
     }
@@ -683,6 +717,7 @@ static iBool isCommandIgnoredByMenus_(const char *cmd) {
            equal_Command(cmd, "window.mouse.entered") ||
            equal_Command(cmd, "input.backup") ||
            equal_Command(cmd, "input.ended") ||
+           equal_Command(cmd, "focus.gained") ||
            equal_Command(cmd, "focus.lost") ||
            (equal_Command(cmd, "mouse.clicked") && !arg_Command(cmd)); /* button released */
 }
@@ -741,6 +776,7 @@ static iWidget *makeMenuSeparator_(void) {
     if (deviceType_App() != desktop_AppDeviceType) {
         sep->rect.size.y = gap_UI / 2;
     }
+    sep->rect.size.y = iMax(1, sep->rect.size.y);
     setFlags_Widget(sep, hover_WidgetFlag | fixedHeight_WidgetFlag, iTrue);
     return sep;
 }
@@ -919,6 +955,9 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
     else {
         setPadding1_Widget(menu, gap_UI / 2);
     }
+#if defined (iPlatformTerminal)
+    setPadding1_Widget(menu, 3);
+#endif
     setFlags_Widget(menu,
                     keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag |
                         arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
@@ -1073,7 +1112,8 @@ iLocalDef iBool isUsingMenuPopupWindows_(void) {
 }
 
 void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
-    const iBool postCommands = (menuOpenFlags & postCommands_MenuOpenFlags) != 0;
+    const iBool postCommands  = (menuOpenFlags & postCommands_MenuOpenFlags) != 0;
+    const iBool isMenuFocused = (focus_Widget() == parent_Widget(d));
 #if defined (LAGRANGE_MAC_CONTEXTMENU)
     const iArray *items = userData_Object(d);
     iAssert(flags_Widget(d) & nativeMenu_WidgetFlag);
@@ -1242,7 +1282,10 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, int menuOpenFlags) {
         postCommand_Widget(d, "menu.opened");
     }
     setupMenuTransition_Mobile(d, iTrue);
-#endif    
+#endif
+    if (isMenuFocused) {
+        setFocus_Widget(child_Widget(d, 0));
+    }
 }
 
 void closeMenu_Widget(iWidget *d) {
@@ -2096,6 +2139,8 @@ static iBool messageHandler_(iWidget *msg, const char *cmd) {
           equal_Command(cmd, "edgeswipe.ended") ||
           equal_Command(cmd, "layout.changed") ||
           equal_Command(cmd, "theme.changed") ||
+          equal_Command(cmd, "focus.lost") ||
+          equal_Command(cmd, "focus.gained") || 
           startsWith_CStr(cmd, "feeds.update.") ||
           startsWith_CStr(cmd, "window."))) {
         setupSheetTransition_Mobile(msg, dialogTransitionDir_Widget(msg));
@@ -2500,6 +2545,18 @@ iWidget *makeDialog_Widget(const char *id,
     return dlg;
 }
 
+static const char *returnKeyBehaviorStr_(int behavior) {
+    iString *nl = collectNew_String();
+    iString *ac = collectNew_String();
+    toString_Sym(SDLK_RETURN, lineBreakKeyMod_ReturnKeyBehavior(behavior), nl);
+    toString_Sym(SDLK_RETURN, acceptKeyMod_ReturnKeyBehavior(behavior), ac);
+    return format_CStr("${prefs.returnkey.linebreak} " uiTextAction_ColorEscape
+                       "%s" restore_ColorEscape
+                       "    ${prefs.returnkey.accept} " uiTextAction_ColorEscape "%s",
+                       cstr_String(nl),
+                       cstr_String(ac));
+}
+
 iWidget *makePreferences_Widget(void) {
     /* Common items. */
     const iMenuItem langItems[] = { { u8"Čeština - cs", 0, 0, "uilang id:cs" },
@@ -2528,27 +2585,25 @@ iWidget *makePreferences_Widget(void) {
                                     { u8"繁體/正體中文 - zh", 0, 0, "uilang id:zh_Hant" },
                                     { NULL } };
     const iMenuItem returnKeyBehaviors[] = {
-        { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape shift_Icon return_Icon
-                                                                    restore_ColorEscape
-          "    ${prefs.returnkey.accept} " uiTextAction_ColorEscape return_Icon,
+        { returnKeyBehaviorStr_(default_ReturnKeyBehavior),
           0,
           0,
           format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) },
-        { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape
-          "    ${prefs.returnkey.accept} " uiTextAction_ColorEscape shift_Icon return_Icon,
+#if !defined (iPlatformTerminal)
+        { returnKeyBehaviorStr_(RETURN_KEY_BEHAVIOR(0, shift_ReturnKeyFlag)),
           0,
           0,
-          format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) },
-        { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape
-          "    ${prefs.returnkey.accept} " uiTextAction_ColorEscape
-#if defined (iPlatformApple)
-          "\u2318" return_Icon,
-#else
-          "Ctrl" return_Icon,
-#endif
+          format_CStr("returnkey.set arg:%d", RETURN_KEY_BEHAVIOR(0, shift_ReturnKeyFlag)) },
+        { returnKeyBehaviorStr_(acceptWithPrimaryMod_ReturnKeyBehavior),
           0,
           0,
           format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) },
+#else
+        { returnKeyBehaviorStr_(RETURN_KEY_BEHAVIOR(gui_ReturnKeyFlag, 0)),
+          0,
+          0,
+          format_CStr("returnkey.set arg:%d", RETURN_KEY_BEHAVIOR(gui_ReturnKeyFlag, 0)) },        
+#endif
         { NULL }
     };
     iMenuItem toolbarActionItems[2][max_ToolbarAction];
@@ -3140,7 +3195,7 @@ static const iArray *makeBookmarkFolderItems_(iBool withNullTerminator) {
 iWidget *makeBookmarkEditor_Widget(iBool isFolder) {
     const iMenuItem actions[] = {
         { "${cancel}", 0, 0, "bmed.cancel" },
-        { uiTextAction_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" }
+        { uiTextAction_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_ACCEPT, "bmed.accept" }
     };
     iWidget *dlg = NULL;
     if (isUsingPanelLayout_Mobile()) {
@@ -3344,7 +3399,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) {
                                     { bookmarkId ? uiTextAction_ColorEscape "${dlg.feed.save}"
                                                  : uiTextAction_ColorEscape "${dlg.feed.sub}",
                                       SDLK_RETURN,
-                                      KMOD_PRIMARY,
+                                      KMOD_ACCEPT,
                                       format_CStr("feedcfg.accept bmid:%d", bookmarkId) } };
     if (isUsingPanelLayout_Mobile()) {
         const iMenuItem typeItems[] = {
@@ -3471,7 +3526,7 @@ iWidget *makeSiteSpecificSettings_Widget(const iString *url) {
     const char *sheetId = format_CStr("sitespec site:%s", cstr_Rangecc(urlRoot_String(url)));
     const iMenuItem actions[] = {
         { "${cancel}" },
-        { uiTextAction_ColorEscape "${sitespec.accept}", SDLK_RETURN, KMOD_PRIMARY, "sitespec.accept" }
+        { uiTextAction_ColorEscape "${sitespec.accept}", SDLK_RETURN, KMOD_ACCEPT, "sitespec.accept" }
     };
     if (isUsingPanelLayout_Mobile()) {
         dlg = makePanels_Mobile(sheetId, (iMenuItem[]){
@@ -3529,7 +3584,7 @@ iWidget *makeIdentityCreation_Widget(void) {
                                   { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" },
                                   { uiTextAction_ColorEscape "${dlg.newident.create}",
                                     SDLK_RETURN,
-                                    KMOD_PRIMARY,
+                                    KMOD_ACCEPT,
                                     "ident.accept" } };
     iUrl url;
     init_Url(&url, url_DocumentWidget(document_App()));
@@ -3847,7 +3902,7 @@ iWidget *makeUserDataImporter_Dialog(const iString *archivePath) {
         { "---" },
         { "${cancel}", SDLK_ESCAPE, 0, "importer.cancel" },
         { uiTextAction_ColorEscape "${import.userdata}",
-          SDLK_RETURN, KMOD_PRIMARY,
+          SDLK_RETURN, KMOD_ACCEPT,
           format_CStr("importer.accept path:%s", cstr_String(archivePath)) },
     };
     if (isUsingPanelLayout_Mobile()) {
diff --git a/src/ui/util.h b/src/ui/util.h
index c7c7d542..44f1a7c4 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -75,12 +75,18 @@ iLocalDef iBool isScrollFinished_MouseWheelEvent(const SDL_MouseWheelEvent *ev)
 
 iInt2   coord_MouseWheelEvent   (const SDL_MouseWheelEvent *);
 
-#if defined (iPlatformApple) && !defined (iPlatformTerminal)
+#if defined (iPlatformTerminal)
+#   define KMOD_PRIMARY     KMOD_CTRL
+#   define KMOD_SECONDARY   KMOD_SHIFT
+#   define KMOD_ACCEPT      KMOD_GUI
+#elif defined (iPlatformApple)
 #   define KMOD_PRIMARY     KMOD_GUI
 #   define KMOD_SECONDARY   KMOD_CTRL
+#   define KMOD_ACCEPT      KMOD_PRIMARY
 #else
 #   define KMOD_PRIMARY     KMOD_CTRL
 #   define KMOD_SECONDARY   KMOD_GUI
+#   define KMOD_ACCEPT      KMOD_PRIMARY
 #endif
 
 enum iOpenTabFlag {
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 938946c8..db08eed5 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -302,9 +302,9 @@ void setMinSize_Widget(iWidget *d, iInt2 minSize) {
 
 void setPadding_Widget(iWidget *d, int left, int top, int right, int bottom) {
     if (d) {
-        d->padding[0] = left;
+        d->padding[0] = left * aspect_UI;
         d->padding[1] = top * aspect_UI;
-        d->padding[2] = right;
+        d->padding[2] = right * aspect_UI;
         d->padding[3] = bottom * aspect_UI;
     }
 }
@@ -1595,6 +1595,13 @@ void drawBackground_Widget(const iWidget *d) {
         iPaint p;
         init_Paint(&p);
         if (d->bgColor >= 0) {
+#if defined (iPlatformTerminal)
+            if (d->bgColor == uiSeparator_ColorId && rect.size.y == 1) {
+                fillRect_Paint(&p, adjusted_Rect(rect, zero_I2(), init_I2(0, -1)),
+                               d->bgColor);
+                return;
+            }
+#endif
             fillRect_Paint(&p, rect, d->bgColor);
         }
         if (d->frameColor >= 0 && ~d->flags & frameless_WidgetFlag) {
diff --git a/src/ui/window.c b/src/ui/window.c
index 41488ca4..b64edc15 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -111,7 +111,7 @@ static const iMenuItem viewMenuItems_[] = {
     { "${menu.show.outline}", '5', leftSidebarTab_KeyModifier, "sidebar.mode arg:4 toggle:1" },
     { "---" },
     { "${menu.sidebar.left}", leftSidebar_KeyShortcut, "sidebar.toggle" },
-    { "${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" },
+    { "${menu.sidebar.right}", rightSidebar_KeyShortcut, "sidebar2.toggle" },
     { "---" },
     { "${menu.back}", SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" },
     { "${menu.forward}", SDLK_RIGHTBRACKET, KMOD_PRIMARY, "navigate.forward" },
@@ -1788,6 +1788,12 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
             }
             setCurrent_Root(NULL);
         }
+        /* Add some room for the active root indicator. */
+        for (int i = 0; i < 2; i++) {
+            if (w->roots[i]) {
+                w->roots[i]->widget->padding[1] = (splitMode ? 1 : 0);
+            }
+        }        
         d->splitMode = splitMode;
         postCommand_App("window.resized");
 #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.13/cdiff/7834830d05f785abd56282af6865d69e2a651165
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
99.027037 milliseconds
Gemini-to-HTML Time
2.246183 milliseconds

This content has been proxied by September (ba2dc).