add horizontal and monocle layout and rework/cleanup drawing logic - sfeed_curses - sfeed curses UI (now part of sfeed, development is in sfeed)
 (HTM) git clone git://git.codemadness.org/sfeed_curses
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 83f15d40dff4c353efba704e7c51f05e077179d2
 (DIR) parent 18bd36ede3a9d2ea2f8dbad50a3ce0858d272584
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Wed, 10 Mar 2021 17:22:05 +0100
       
       add horizontal and monocle layout and rework/cleanup drawing logic
       
       - Add the layouts horizontal and monocle.
       
       - The monocle layout replaces the mode of hiding the sidebar using 's'.  This
         's' keybind now toggles between a monocle and non-monocle layout (vertical or
         horizontal). When a feed is read from stdin (no filenames) then the default
         layout will now be monocle also.
       
       - The monocle layout also works nicely on smaller portable screens (such as a
         Motorola Droid4) or on smaller terminals or if a more newsboat-like style is
         wanted.
       
       - Layouts can be changed with the keybinds '1', '2', '3', a preference can also
         be set at startup using the recently added SFEED_AUTOCMD feature, like so:
         SFEED_AUTOCMD=2 sfeed_curses.
       
       Diffstat:
         M sfeed_curses.1                      |      22 +++++++++++++++++-----
         M sfeed_curses.c                      |     273 ++++++++++++++++++++++---------
         M themes/mono.h                       |       1 +
         M themes/newsboat.h                   |       1 +
         M themes/templeos.h                   |       5 +++++
       
       5 files changed, 222 insertions(+), 80 deletions(-)
       ---
 (DIR) diff --git a/sfeed_curses.1 b/sfeed_curses.1
       @@ -1,4 +1,4 @@
       -.Dd February 20, 2021
       +.Dd March 10, 2021
        .Dt SFEED_CURSES 1
        .Os
        .Sh NAME
       @@ -78,13 +78,16 @@ Reload all feed files which were specified as arguments on startup.
        .It m
        Toggle mouse-mode.
        .It s
       -Toggle showing the feeds pane sidebar.
       +Toggle between monocle layout and the previous non-monocle layout.
        .It <
       -Use fixed sidebar width and decrease fixed width by 1 column.
       +Use a fixed sidebar size for the current layout and decrease the fixed width or
       +height by 1 column.
        .It >
       -Use fixed sidebar width and increase fixed width by 1 column.
       +Use a fixed sidebar size for the current layout and increase the fixed width or
       +height by 1 column.
        .It =
       -Reset sidebar width to automatic adjustment.
       +Reset the sidebar width or height to automatic adjustment for the current
       +layout.
        .It t
        Toggle showing only feeds with new items in the sidebar.
        .It a, e, @
       @@ -128,6 +131,15 @@ Mark all items of the current loaded feed as unread.
        This will only work when
        .Ev SFEED_URL_FILE
        is set.
       +.It 1
       +Set the current layout to a vertical mode. Showing a feeds sidebar to the left
       +and the feed items to the right.
       +.It 2
       +Set the current layout to a horizontal mode. Showing a feeds sidebar on the top
       +and the feed items on the bottom.
       +.It 3
       +Set the current layout to a monocle mode. Showing only one feeds or an feed
       +items pane at once.
        .It q, EOF
        Quit
        .El
 (DIR) diff --git a/sfeed_curses.c b/sfeed_curses.c
       @@ -25,12 +25,14 @@
        #include "minicurses.h"
        #endif
        
       -#define LEN(a) sizeof((a))/sizeof((a)[0])
       +#define LEN(a)   sizeof((a))/sizeof((a)[0])
        #define MAX(a,b) ((a) > (b) ? (a) : (b))
        
        #define PAD_TRUNCATE_SYMBOL    "\xe2\x80\xa6" /* symbol: "ellipsis" */
        #define SCROLLBAR_SYMBOL_BAR   "\xe2\x94\x82" /* symbol: "light vertical" */
        #define SCROLLBAR_SYMBOL_TICK  " "
       +#define LINEBAR_SYMBOL_BAR     "\xe2\x94\x80" /* symbol: "light horizontal" */
       +#define LINEBAR_SYMBOL_RIGHT   "\xe2\x94\xa4" /* symbol: "light vertical and left" */
        #define UTF_INVALID_SYMBOL     "\xef\xbf\xbd" /* symbol: "replacement" */
        
        /* color-theme */
       @@ -43,6 +45,10 @@ enum {
                ATTR_RESET = 0,        ATTR_BOLD_ON = 1, ATTR_FAINT_ON = 2, ATTR_REVERSE_ON = 7
        };
        
       +enum Layout {
       +        LayoutVertical = 0, LayoutHorizontal, LayoutMonocle, LayoutLast
       +};
       +
        enum Pane { PaneFeeds, PaneItems, PaneLast };
        
        enum {
       @@ -100,6 +106,14 @@ struct statusbar {
                int dirty; /* needs draw update */
        };
        
       +struct linebar {
       +        int x; /* absolute x position on the screen */
       +        int y; /* absolute y position on the screen */
       +        int width; /* absolute width of the line */
       +        int hidden; /* is visible or not */
       +        int dirty; /* needs draw update */
       +};
       +
        /* /UI */
        
        struct item {
       @@ -130,7 +144,7 @@ struct feed {
        void alldirty(void);
        void cleanup(void);
        void draw(void);
       -int getsidebarwidth(void);
       +int getsidebarsize(void);
        void markread(struct pane *, off_t, off_t, int);
        void pane_draw(struct pane *);
        void sighandler(int);
       @@ -140,12 +154,15 @@ void urls_free(void);
        int urls_isnew(const char *);
        void urls_read(void);
        
       +static struct linebar linebar;
        static struct statusbar statusbar;
        static struct pane panes[PaneLast];
        static struct scrollbar scrollbars[PaneLast]; /* each pane has a scrollbar */
        static struct win win;
        static size_t selpane;
       -static int fixedsidebarwidth = -1; /* fixed sidebar width, < 0 is automatic */
       +/* fixed sidebar size, < 0 is automatic */
       +static int fixedsidebarsizes[LayoutLast] = { -1, -1, -1 };
       +static int layout = LayoutVertical, prevlayout = LayoutVertical;
        static int onlynew = 0; /* show only new in sidebar */
        static int usemouse = 1; /* use xterm mouse tracking */
        
       @@ -817,36 +834,75 @@ pane_draw(struct pane *p)
        }
        
        void
       +setlayout(int n)
       +{
       +        if (layout != LayoutMonocle)
       +                prevlayout = layout; /* previous non-monocle layout */
       +        layout = n;
       +}
       +
       +void
        updategeom(void)
        {
       -        int w, x;
       -
       -        panes[PaneFeeds].width = getsidebarwidth();
       -        if (win.width && panes[PaneFeeds].width >= win.width)
       -                panes[PaneFeeds].width = win.width - 1;
       -        panes[PaneFeeds].x = 0;
       -        panes[PaneFeeds].y = 0;
       -        /* reserve space for statusbar */
       -        panes[PaneFeeds].height = MAX(win.height - 1, 1);
       -
       -        /* NOTE: updatesidebar() must happen before this function for the
       -           remaining width */
       -        if (!panes[PaneFeeds].hidden) {
       -                w = win.width - panes[PaneFeeds].width;
       -                x = panes[PaneFeeds].x + panes[PaneFeeds].width;
       +        int barsize, h, w, x = 0, y = 0;
       +
       +        panes[PaneFeeds].hidden = layout == LayoutMonocle && (selpane != PaneFeeds);
       +        panes[PaneItems].hidden = layout == LayoutMonocle && (selpane != PaneItems);
       +        linebar.hidden = layout != LayoutHorizontal;
       +
       +        w = win.width;
       +        /* always reserve space for statusbar */
       +        h = MAX(win.height - 1, 1);
       +
       +        panes[PaneFeeds].x = x;
       +        panes[PaneFeeds].y = y;
       +
       +        switch (layout) {
       +        case LayoutVertical:
       +                /* NOTE: updatesidebar() must happen before this function for the
       +                   remaining width */
       +                barsize = getsidebarsize();
       +                if (w && barsize >= w)
       +                        barsize = w - 1;
       +
       +                panes[PaneFeeds].width = MAX(barsize, 0);
       +                x += panes[PaneFeeds].width;
       +                w -= panes[PaneFeeds].width;
       +
                        /* space for scrollbar if sidebar is visible */
                        w--;
                        x++;
       -        } else {
       -                w = win.width;
       -                x = 0;
       +
       +                panes[PaneFeeds].height = MAX(h, 1);
       +                break;
       +        case LayoutHorizontal:
       +                barsize = getsidebarsize();
       +                if (h && barsize >= h / 2)
       +                        barsize = h / 2;
       +
       +                panes[PaneFeeds].height = MAX(barsize, 1);
       +
       +                h -= panes[PaneFeeds].height;
       +                y += panes[PaneFeeds].height;
       +
       +                linebar.x = 0;
       +                linebar.y = y;
       +                linebar.width = win.width;
       +
       +                h -= 1;
       +                y += 1;
       +                panes[PaneFeeds].width = MAX(w - 1, 0);
       +                break;
       +        case LayoutMonocle:
       +                panes[PaneFeeds].height = MAX(h, 1);
       +                panes[PaneFeeds].width = MAX(w - 1, 0);
       +                break;
                }
        
       +        panes[PaneItems].width = MAX(w - 1, 0);
       +        panes[PaneItems].height = MAX(h, 1);
                panes[PaneItems].x = x;
       -        panes[PaneItems].width = MAX(w - 1, 0); /* rest and space for scrollbar */
       -        panes[PaneItems].height = panes[PaneFeeds].height;
       -        panes[PaneItems].y = panes[PaneFeeds].y;
       -        panes[PaneItems].hidden = !panes[PaneItems].width || !panes[PaneItems].height;
       +        panes[PaneItems].y = y;
        
                scrollbars[PaneFeeds].x = panes[PaneFeeds].x + panes[PaneFeeds].width;
                scrollbars[PaneFeeds].y = panes[PaneFeeds].y;
       @@ -856,7 +912,7 @@ updategeom(void)
                scrollbars[PaneItems].x = panes[PaneItems].x + panes[PaneItems].width;
                scrollbars[PaneItems].y = panes[PaneItems].y;
                scrollbars[PaneItems].size = panes[PaneItems].height;
       -        scrollbars[PaneItems].hidden = panes[PaneItems].hidden;
       +        scrollbars[PaneItems].hidden = panes[PaneItems].width ? 0 : 1;
        
                /* statusbar below */
                statusbar.width = win.width;
       @@ -1046,6 +1102,27 @@ uiprompt(int x, int y, char *fmt, ...)
        }
        
        void
       +linebar_draw(struct linebar *b)
       +{
       +        int i;
       +
       +        if (!b->dirty)
       +                return;
       +        b->dirty = 0;
       +        if (b->hidden || !b->width)
       +                return;
       +
       +        cursorsave();
       +        cursormove(b->x, b->y);
       +        THEME_LINEBAR();
       +        for (i = 0; i < b->width - 1; i++)
       +                ttywrite(LINEBAR_SYMBOL_BAR);
       +        ttywrite(LINEBAR_SYMBOL_RIGHT);
       +        attrmode(ATTR_RESET);
       +        cursorrestore();
       +}
       +
       +void
        statusbar_draw(struct statusbar *s)
        {
                if (!s->dirty)
       @@ -1338,29 +1415,40 @@ feeds_reloadall(void)
        }
        
        int
       -getsidebarwidth(void)
       +getsidebarsize(void)
        {
                struct feed *feed;
                size_t i;
       -        int len, width = 0;
       -
       -        /* fixed sidebar width? else calculate an optimal size automatically */
       -        if (fixedsidebarwidth >= 0)
       -                return fixedsidebarwidth;
       -
       -        for (i = 0; i < nfeeds; i++) {
       -                feed = &feeds[i];
       -
       -                len = snprintf(NULL, 0, " (%lu/%lu)", feed->totalnew, feed->total) +
       -                        colw(feed->name);
       -                if (len > width)
       -                        width = len;
       -
       -                if (onlynew && feed->totalnew == 0)
       -                        continue;
       +        int len, size;
       +
       +        /* fixed sidebar size? else calculate an optimal size automatically */
       +        if (fixedsidebarsizes[layout] >= 0)
       +                return fixedsidebarsizes[layout];
       +
       +        switch (layout) {
       +        case LayoutVertical:
       +                for (i = 0, size = 0; i < nfeeds; i++) {
       +                        feed = &feeds[i];
       +                        len = snprintf(NULL, 0, " (%lu/%lu)",
       +                                       feed->totalnew, feed->total) +
       +                                       colw(feed->name);
       +                        if (len > size)
       +                                size = len;
       +
       +                        if (onlynew && feed->totalnew == 0)
       +                                continue;
       +                }
       +                return size;
       +        case LayoutHorizontal:
       +                for (i = 0, size = 0; i < nfeeds; i++) {
       +                        feed = &feeds[i];
       +                        if (onlynew && feed->totalnew == 0)
       +                                continue;
       +                        size++;
       +                }
       +                return size;
                }
       -
       -        return width;
       +        return 0;
        }
        
        void
       @@ -1370,15 +1458,24 @@ updatesidebar(void)
                struct row *row;
                struct feed *feed;
                size_t i, nrows;
       -        int oldwidth;
       +        int oldvalue, newvalue;
        
                p = &panes[PaneFeeds];
       -
                if (!p->rows)
                        p->rows = ecalloc(sizeof(p->rows[0]), nfeeds + 1);
        
       -        oldwidth = p->width;
       -        p->width = getsidebarwidth();
       +        switch (layout) {
       +        case LayoutVertical:
       +                oldvalue = p->width;
       +                newvalue = getsidebarsize();
       +                p->width = newvalue;
       +                break;
       +        case LayoutHorizontal:
       +                oldvalue = p->height;
       +                newvalue = getsidebarsize();
       +                p->height = newvalue;
       +                break;
       +        }
        
                nrows = 0;
                for (i = 0; i < nfeeds; i++) {
       @@ -1395,10 +1492,18 @@ updatesidebar(void)
                }
                p->nrows = nrows;
        
       -        if (p->width != oldwidth)
       -                updategeom();
       -        else
       +        switch (layout) {
       +        case LayoutVertical:
       +        case LayoutHorizontal:
       +                if (oldvalue != newvalue)
       +                        updategeom();
       +                else
       +                        p->dirty = 1;
       +                break;
       +        default:
                        p->dirty = 1;
       +                break;
       +        }
        
                if (!p->nrows)
                        p->pos = 0;
       @@ -1429,6 +1534,7 @@ alldirty(void)
                panes[PaneItems].dirty = 1;
                scrollbars[PaneFeeds].dirty = 1;
                scrollbars[PaneItems].dirty = 1;
       +        linebar.dirty = 1;
                statusbar.dirty = 1;
        }
        
       @@ -1440,8 +1546,8 @@ draw(void)
                size_t i;
        
                if (win.dirty) {
       -                clearscreen();
                        win.dirty = 0;
       +                clearscreen();
                }
        
                /* There is the same amount and indices of panes and scrollbars. */
       @@ -1456,6 +1562,8 @@ draw(void)
                        scrollbar_draw(&scrollbars[i]);
                }
        
       +        linebar_draw(&linebar);
       +
                /* If item selection text changed then update the status text. */
                if ((row = pane_row_get(&panes[PaneItems], panes[PaneItems].pos))) {
                        item = (struct item *)row->data;
       @@ -1481,7 +1589,7 @@ mousereport(int button, int release, int x, int y)
        
                for (i = 0; i < LEN(panes); i++) {
                        p = &panes[i];
       -                if (p->hidden)
       +                if (p->hidden || !p->width || !p->height)
                                continue;
        
                        if (!(x >= p->x && x < p->x + p->width &&
       @@ -1510,6 +1618,11 @@ mousereport(int button, int release, int x, int y)
                                        /* redraw row: counts could be changed */
                                        updatesidebar();
                                        updatetitle();
       +
       +                                if (layout == LayoutMonocle) {
       +                                        selpane = PaneItems;
       +                                        updategeom();
       +                                }
                                } else if (i == PaneItems) {
                                        if (dblclick && !changedpane) {
                                                row = pane_row_get(p, p->pos);
       @@ -1788,6 +1901,9 @@ main(int argc, char *argv[])
                urlfile = getenv("SFEED_URL_FILE"); /* can be NULL */
                cmdenv = getenv("SFEED_AUTOCMD"); /* can be NULL */
        
       +        setlayout(argc <= 1 ? LayoutMonocle : LayoutVertical);
       +        selpane = layout == LayoutMonocle ? PaneItems : PaneFeeds;
       +
                panes[PaneFeeds].row_format = feed_row_format;
                panes[PaneFeeds].row_match = feed_row_match;
                panes[PaneItems].row_format = item_row_format;
       @@ -1825,14 +1941,6 @@ main(int argc, char *argv[])
                if (argc == 1)
                        feeds[0].fp = NULL;
        
       -        if (argc > 1) {
       -                panes[PaneFeeds].hidden = 0;
       -                selpane = PaneFeeds;
       -        } else {
       -                panes[PaneFeeds].hidden = 1;
       -                selpane = PaneItems;
       -        }
       -
                if ((devnullfd = open("/dev/null", O_WRONLY)) == -1)
                        die("open: /dev/null");
        
       @@ -1918,15 +2026,21 @@ keyleft:
                                if (selpane == PaneFeeds)
                                        break;
                                selpane = PaneFeeds;
       +                        if (layout == LayoutMonocle)
       +                                updategeom();
                                break;
        keyright:
                        case 'l':
                                if (selpane == PaneItems)
                                        break;
                                selpane = PaneItems;
       +                        if (layout == LayoutMonocle)
       +                                updategeom();
                                break;
                        case '\t':
                                selpane = selpane == PaneFeeds ? PaneItems : PaneFeeds;
       +                        if (layout == LayoutMonocle)
       +                                updategeom();
                                break;
        startpos:
                        case 'g':
       @@ -2004,24 +2118,18 @@ nextpage:
                                usemouse = !usemouse;
                                mousemode(usemouse);
                                break;
       -                case 's': /* toggle sidebar */
       -                        panes[PaneFeeds].hidden = !panes[PaneFeeds].hidden;
       -                        if (selpane == PaneFeeds && panes[selpane].hidden)
       -                                selpane = PaneItems;
       -                        updategeom();
       -                        break;
                        case '<': /* decrease fixed sidebar width */
                        case '>': /* increase fixed sidebar width */
       -                        if (fixedsidebarwidth < 0)
       -                                fixedsidebarwidth = getsidebarwidth();
       -                        if (ch == '<' && fixedsidebarwidth > 0)
       -                                fixedsidebarwidth--;
       +                        if (fixedsidebarsizes[layout] < 0)
       +                                fixedsidebarsizes[layout] = getsidebarsize();
       +                        if (ch == '<' && fixedsidebarsizes[layout] > 0)
       +                                fixedsidebarsizes[layout]--;
                                else if (ch != '<')
       -                                fixedsidebarwidth++;
       +                                fixedsidebarsizes[layout]++;
                                updategeom();
                                break;
       -                case '=': /* reset fixed sidebar width to automatic size */
       -                        fixedsidebarwidth = -1;
       +                case '=': /* reset fixed sidebar to automatic size */
       +                        fixedsidebarsizes[layout] = -1;
                                updategeom();
                                break;
                        case 't': /* toggle showing only new in sidebar */
       @@ -2043,6 +2151,11 @@ nextpage:
                                        /* redraw row: counts could be changed */
                                        updatesidebar();
                                        updatetitle();
       +
       +                                if (layout == LayoutMonocle) {
       +                                        selpane = PaneItems;
       +                                        updategeom();
       +                                }
                                } else if (selpane == PaneItems && panes[selpane].nrows) {
                                        row = pane_row_get(p, p->pos);
                                        item = (struct item *)row->data;
       @@ -2083,6 +2196,16 @@ nextpage:
                                        markread(p, p->pos, p->pos, ch == 'r');
                                }
                                break;
       +                case 's': /* toggle layout between monocle or non-monocle */
       +                        setlayout(layout == LayoutMonocle ? prevlayout : LayoutMonocle);
       +                        updategeom();
       +                        break;
       +                case '1': /* vertical layout */
       +                case '2': /* horizontal layout */
       +                case '3': /* monocle layout */
       +                        setlayout(ch - '1');
       +                        updategeom();
       +                        break;
                        case 4: /* EOT */
                        case 'q': goto end;
                        }
 (DIR) diff --git a/themes/mono.h b/themes/mono.h
       @@ -7,6 +7,7 @@
        #define THEME_SCROLLBAR_NORMAL()      do { attrmode(ATTR_FAINT_ON);   } while(0)
        #define THEME_SCROLLBAR_TICK_FOCUS()  do { attrmode(ATTR_REVERSE_ON); } while(0)
        #define THEME_SCROLLBAR_TICK_NORMAL() do { attrmode(ATTR_REVERSE_ON); } while(0)
       +#define THEME_LINEBAR()               do { attrmode(ATTR_FAINT_ON);   } while(0)
        #define THEME_STATUSBAR()             do { attrmode(ATTR_REVERSE_ON); } while(0)
        #define THEME_INPUT_LABEL()           do { attrmode(ATTR_REVERSE_ON); } while(0)
        #define THEME_INPUT_NORMAL()          do {                            } while(0)
 (DIR) diff --git a/themes/newsboat.h b/themes/newsboat.h
       @@ -7,6 +7,7 @@
        #define THEME_SCROLLBAR_NORMAL()      do { ttywrite("\x1b[34m");    } while(0)
        #define THEME_SCROLLBAR_TICK_FOCUS()  do { ttywrite("\x1b[44m");    } while(0) /* blue bg */
        #define THEME_SCROLLBAR_TICK_NORMAL() do { ttywrite("\x1b[44m");    } while(0)
       +#define THEME_LINEBAR()               do { ttywrite("\x1b[34m");    } while(0)
        #define THEME_STATUSBAR()             do { attrmode(ATTR_BOLD_ON); ttywrite("\x1b[93;44m"); } while(0)
        #define THEME_INPUT_LABEL()           do {                          } while(0)
        #define THEME_INPUT_NORMAL()          do {                          } while(0)
 (DIR) diff --git a/themes/templeos.h b/themes/templeos.h
       @@ -11,9 +11,14 @@
        #define THEME_SCROLLBAR_NORMAL()      do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBGCOLOR(0xff, 0xff, 0xff); } while(0)
        #define THEME_SCROLLBAR_TICK_FOCUS()  do { SETBGCOLOR(0x00, 0x00, 0xaa); SETFGCOLOR(0xff, 0xff, 0xff); } while(0)
        #define THEME_SCROLLBAR_TICK_NORMAL() do { SETBGCOLOR(0x00, 0x00, 0xaa); SETFGCOLOR(0xff, 0xff, 0xff); } while(0)
       +#define THEME_LINEBAR()               do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBGCOLOR(0xff, 0xff, 0xff); } while(0)
        #define THEME_STATUSBAR()             do { ttywrite("\x1b[6m"); SETBGCOLOR(0x00, 0x00, 0xaa); SETFGCOLOR(0xff, 0xff, 0xff); } while(0) /* blink statusbar */
        #define THEME_INPUT_LABEL()           do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBGCOLOR(0xff, 0xff, 0xff); } while(0)
        #define THEME_INPUT_NORMAL()          do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBGCOLOR(0xff, 0xff, 0xff); } while(0)
        
        #undef SCROLLBAR_SYMBOL_BAR
        #define SCROLLBAR_SYMBOL_BAR  "\xe2\x95\x91" /* symbol: "double vertical" */
       +#undef LINEBAR_SYMBOL_BAR
       +#define LINEBAR_SYMBOL_BAR     "\xe2\x95\x90" /* symbol: "double horizontal" */
       +#undef LINEBAR_SYMBOL_RIGHT
       +#define LINEBAR_SYMBOL_RIGHT   "\xe2\x95\xa3" /* symbol: "double vertical and left" */