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" */