add horizontal and monocle layout and rework/cleanup drawing logic - sfeed_curs… | |
git clone git://git.codemadness.org/sfeed_curses | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 83f15d40dff4c353efba704e7c51f05e077179d2 | |
parent 18bd36ede3a9d2ea2f8dbad50a3ce0858d272584 | |
Author: Hiltjo Posthuma <[email protected]> | |
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(-) | |
--- | |
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 … | |
.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 | |
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 l… | |
#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_REVER… | |
}; | |
+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 != PaneF… | |
+ panes[PaneItems].hidden = layout == LayoutMonocle && (selpane != PaneI… | |
+ 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 … | |
+ 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 scrollba… | |
- panes[PaneItems].height = panes[PaneFeeds].height; | |
- panes[PaneItems].y = panes[PaneFeeds].y; | |
- panes[PaneItems].hidden = !panes[PaneItems].width || !panes[PaneItems]… | |
+ 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->to… | |
- 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].nrow… | |
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 : Layou… | |
+ 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; | |
} | |
diff --git a/themes/mono.h b/themes/mono.h | |
@@ -7,6 +7,7 @@ | |
#define THEME_SCROLLBAR_NORMAL() do { attrmode(ATTR_FAINT_ON); } while(… | |
#define THEME_SCROLLBAR_TICK_FOCUS() do { attrmode(ATTR_REVERSE_ON); } while(… | |
#define THEME_SCROLLBAR_TICK_NORMAL() do { attrmode(ATTR_REVERSE_ON); } while(… | |
+#define THEME_LINEBAR() do { attrmode(ATTR_FAINT_ON); } while(… | |
#define THEME_STATUSBAR() do { attrmode(ATTR_REVERSE_ON); } while(… | |
#define THEME_INPUT_LABEL() do { attrmode(ATTR_REVERSE_ON); } while(… | |
#define THEME_INPUT_NORMAL() do { } while(… | |
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)… | |
#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("\… | |
#define THEME_INPUT_LABEL() do { } while(0) | |
#define THEME_INPUT_NORMAL() do { } while(0) | |
diff --git a/themes/templeos.h b/themes/templeos.h | |
@@ -11,9 +11,14 @@ | |
#define THEME_SCROLLBAR_NORMAL() do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBG… | |
#define THEME_SCROLLBAR_TICK_FOCUS() do { SETBGCOLOR(0x00, 0x00, 0xaa); SETFG… | |
#define THEME_SCROLLBAR_TICK_NORMAL() do { SETBGCOLOR(0x00, 0x00, 0xaa); SETFG… | |
+#define THEME_LINEBAR() do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBG… | |
#define THEME_STATUSBAR() do { ttywrite("\x1b[6m"); SETBGCOLOR(0x0… | |
#define THEME_INPUT_LABEL() do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBG… | |
#define THEME_INPUT_NORMAL() do { SETFGCOLOR(0x00, 0x00, 0xaa); SETBG… | |
#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 … |