5 Commits

Author SHA1 Message Date
b4222365c3 Bump version 2022-09-03 15:13:07 +02:00
b5e48c29f9 Put connecting/disconnected messages in status bar
Those are general status messages, and seem to belong to the bottom.
Partially motivated by the status bar being empty when disconnected.

And add a missing window invalidation.
2022-09-03 15:04:30 +02:00
eaa19be1c8 Fix Clang build 2022-09-03 14:39:47 +02:00
dad95ef444 X11: render partially visible list items 2022-09-03 13:23:07 +02:00
7e74d1a80a X11: make the scrollbar span the full height 2022-09-03 13:02:47 +02:00
3 changed files with 59 additions and 54 deletions

View File

@@ -1,5 +1,5 @@
cmake_minimum_required (VERSION 3.0) cmake_minimum_required (VERSION 3.0)
project (nncmpp VERSION 1.2.0 LANGUAGES C) project (nncmpp VERSION 2.0.0 LANGUAGES C)
# Moar warnings # Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)

4
NEWS
View File

@@ -1,4 +1,4 @@
Unreleased 2.0.0 (2022-09-03)
* Added an optional X11 user interface * Added an optional X11 user interface
@@ -12,6 +12,8 @@ Unreleased
* Made it possible to adjust the spectrum analyzer's FPS limit * Made it possible to adjust the spectrum analyzer's FPS limit
* Moved "Disconnected" and "Connecting..." messages to the status bar
* Fixed possibility of connection timeouts with PulseAudio integration * Fixed possibility of connection timeouts with PulseAudio integration

107
nncmpp.c
View File

@@ -1836,7 +1836,7 @@ app_layout_song_info (void)
} }
if (album) if (album)
{ {
app_push (&l, g.ui->label (attrs[0], " from " + !artist)); app_push (&l, g.ui->label (attrs[0], &" from "[!artist]));
app_push (&l, g.ui->label (attrs[1], album)); app_push (&l, g.ui->label (attrs[1], album));
} }
@@ -1982,28 +1982,17 @@ app_layout_tabs (void)
static void static void
app_layout_header (void) app_layout_header (void)
{ {
if (g.client.state == MPD_CONNECTED)
{ {
struct layout l = {}; struct layout lt = {};
app_push_fill (&l, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125)); app_push_fill (&lt, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
app_flush_layout (&l); app_flush_layout (&lt);
}
switch (g.client.state)
{
case MPD_CONNECTED:
app_layout_status (); app_layout_status ();
break;
case MPD_CONNECTING:
app_layout_text ("Connecting to MPD...", APP_ATTR (NORMAL));
break;
case MPD_DISCONNECTED:
app_layout_text ("Disconnected", APP_ATTR (NORMAL));
}
{ struct layout lb = {};
struct layout l = {}; app_push_fill (&lb, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125));
app_push_fill (&l, g.ui->padding (APP_ATTR (NORMAL), 0, 0.125)); app_flush_layout (&lb);
app_flush_layout (&l);
} }
app_layout_tabs (); app_layout_tabs ();
@@ -2014,7 +2003,7 @@ app_layout_header (void)
} }
static int static int
app_visible_items (void) app_visible_items_height (void)
{ {
struct widget *list = NULL; struct widget *list = NULL;
LIST_FOR_EACH (struct widget, w, g.widgets.head) LIST_FOR_EACH (struct widget, w, g.widgets.head)
@@ -2024,7 +2013,13 @@ app_visible_items (void)
hard_assert (list != NULL); hard_assert (list != NULL);
// The raw number of items that would have fit on the terminal // The raw number of items that would have fit on the terminal
return MAX (0, list->height / g.ui_vunit); return MAX (0, list->height);
}
static int
app_visible_items (void)
{
return app_visible_items_height () / g.ui_vunit;
} }
/// Figure out scrollbar appearance. @a s is the minimal slider length as well /// Figure out scrollbar appearance. @a s is the minimal slider length as well
@@ -2032,7 +2027,7 @@ app_visible_items (void)
struct scrollbar { long length, start; } struct scrollbar { long length, start; }
app_compute_scrollbar (struct tab *tab, long visible, long s) app_compute_scrollbar (struct tab *tab, long visible, long s)
{ {
long top = tab->item_top, total = tab->item_count; long top = s * tab->item_top, total = s * tab->item_count;
if (total < visible) if (total < visible)
return (struct scrollbar) { 0, 0 }; return (struct scrollbar) { 0, 0 };
if (visible == 1) if (visible == 1)
@@ -2042,7 +2037,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s)
// Only be at the top or bottom when the top or bottom item can be seen. // Only be at the top or bottom when the top or bottom item can be seen.
// The algorithm isn't optimal but it's a bitch to get right. // The algorithm isn't optimal but it's a bitch to get right.
double available_length = s * visible - 2 - s + 1; double available_length = visible - 2 - s + 1;
double lenf = s + available_length * visible / total, length = 0.; double lenf = s + available_length * visible / total, length = 0.;
long offset = 1 + available_length * top / total + modf (lenf, &length); long offset = 1 + available_length * top / total + modf (lenf, &length);
@@ -2050,7 +2045,7 @@ app_compute_scrollbar (struct tab *tab, long visible, long s)
if (top == 0) if (top == 0)
return (struct scrollbar) { length, 0 }; return (struct scrollbar) { length, 0 };
if (top + visible >= total) if (top + visible >= total)
return (struct scrollbar) { length, s * visible - length }; return (struct scrollbar) { length, visible - length };
return (struct scrollbar) { length, offset }; return (struct scrollbar) { length, offset };
} }
@@ -2099,7 +2094,7 @@ app_sublayout_list (struct widget *list)
{ {
struct tab *tab = g.active_tab; struct tab *tab = g.active_tab;
int to_show = MIN ((int) tab->item_count - tab->item_top, int to_show = MIN ((int) tab->item_count - tab->item_top,
list->height / g.ui_vunit); ceil ((double) list->height / g.ui_vunit));
struct layout l = {}; struct layout l = {};
for (int row = 0; row < to_show; row++) for (int row = 0; row < to_show; row++)
@@ -2267,6 +2262,10 @@ app_layout_statusbar (void)
} }
else if (g.client.state == MPD_CONNECTED) else if (g.client.state == MPD_CONNECTED)
app_layout_mpd_status (); app_layout_mpd_status ();
else if (g.client.state == MPD_CONNECTING)
app_layout_text ("Connecting to MPD...", attrs[0]);
else if (g.client.state == MPD_DISCONNECTED)
app_layout_text ("Disconnected", attrs[0]);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2839,9 +2838,8 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
else if (tab->item_mark < 0) else if (tab->item_mark < 0)
tab->item_mark = tab->item_selected; tab->item_mark = tab->item_selected;
// TODO: Probably will need to fix up item->top
// for partially visible items in X11.
tab->item_selected = row_index + tab->item_top; tab->item_selected = row_index + tab->item_top;
app_ensure_selection_visible ();
app_invalidate (); app_invalidate ();
if (modifiers & APP_KEYMOD_DOUBLE_CLICK) if (modifiers & APP_KEYMOD_DOUBLE_CLICK)
@@ -2852,7 +2850,7 @@ app_process_left_mouse_click (struct widget *w, int x, int y, int modifiers)
{ {
struct tab *tab = g.active_tab; struct tab *tab = g.active_tab;
int visible_items = app_visible_items (); int visible_items = app_visible_items ();
tab->item_top = (float) y / w->height tab->item_top = (double) y / w->height
* (int) tab->item_count - visible_items / 2; * (int) tab->item_count - visible_items / 2;
app_invalidate (); app_invalidate ();
break; break;
@@ -5005,6 +5003,7 @@ app_on_reconnect (void *user_data)
mpd_queue_reconnect (); mpd_queue_reconnect ();
} }
free (address); free (address);
app_invalidate ();
} }
// --- TUI --------------------------------------------------------------------- // --- TUI ---------------------------------------------------------------------
@@ -5171,7 +5170,7 @@ tui_render_scrollbar (struct widget *self)
return; return;
} }
struct scrollbar bar = app_compute_scrollbar (tab, visible_items, 8); struct scrollbar bar = app_compute_scrollbar (tab, visible_items * 8, 8);
bar.length += bar.start; bar.length += bar.start;
int start_part = bar.start % 8; bar.start /= 8; int start_part = bar.start % 8; bar.start /= 8;
@@ -5567,55 +5566,56 @@ x11_make_label (chtype attrs, const char *label)
} }
// On a 20x20 raster to make it feasible to design on paper. // On a 20x20 raster to make it feasible to design on paper.
static const XPointDouble x11_stop = {INFINITY, INFINITY}, #define X11_STOP {INFINITY, INFINITY}
static const XPointDouble
x11_icon_previous[] = x11_icon_previous[] =
{ {
{10, 0}, {0, 10}, {10, 20}, x11_stop, {10, 0}, {0, 10}, {10, 20}, X11_STOP,
{20, 0}, {10, 10}, {20, 20}, x11_stop, x11_stop, {20, 0}, {10, 10}, {20, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_pause[] = x11_icon_pause[] =
{ {
{1, 0}, {7, 0}, {7, 20}, {1, 20}, x11_stop, {1, 0}, {7, 0}, {7, 20}, {1, 20}, X11_STOP,
{13, 0}, {19, 0}, {19, 20}, {13, 20}, x11_stop, x11_stop, {13, 0}, {19, 0}, {19, 20}, {13, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_play[] = x11_icon_play[] =
{ {
{0, 0}, {20, 10}, {0, 20}, x11_stop, x11_stop, {0, 0}, {20, 10}, {0, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_stop[] = x11_icon_stop[] =
{ {
{0, 0}, {20, 0}, {20, 20}, {0, 20}, x11_stop, x11_stop, {0, 0}, {20, 0}, {20, 20}, {0, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_next[] = x11_icon_next[] =
{ {
{0, 0}, {10, 10}, {0, 20}, x11_stop, {0, 0}, {10, 10}, {0, 20}, X11_STOP,
{10, 0}, {20, 10}, {10, 20}, x11_stop, x11_stop, {10, 0}, {20, 10}, {10, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_repeat[] = x11_icon_repeat[] =
{ {
{0, 12}, {0, 6}, {3, 3}, {13, 3}, {13, 0}, {20, 4.5}, {0, 12}, {0, 6}, {3, 3}, {13, 3}, {13, 0}, {20, 4.5},
{13, 9}, {13, 6}, {3, 6}, {3, 10}, x11_stop, {13, 9}, {13, 6}, {3, 6}, {3, 10}, X11_STOP,
{0, 15.5}, {7, 11}, {7, 14}, {17, 14}, {17, 10}, {20, 8}, {0, 15.5}, {7, 11}, {7, 14}, {17, 14}, {17, 10}, {20, 8},
{20, 14}, {17, 17}, {7, 17}, {7, 20}, x11_stop, x11_stop, {20, 14}, {17, 17}, {7, 17}, {7, 20}, X11_STOP, X11_STOP,
}, },
x11_icon_random[] = x11_icon_random[] =
{ {
{0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, x11_stop, {0, 6}, {0, 3}, {5, 3}, {6, 4.5}, {4, 7.5}, {3, 6}, X11_STOP,
{9, 15.5}, {11, 12.5}, {12, 14}, {13, 14}, {13, 11}, {20, 15.5}, {9, 15.5}, {11, 12.5}, {12, 14}, {13, 14}, {13, 11}, {20, 15.5},
{13, 20}, {13, 17}, {10, 17}, x11_stop, {13, 20}, {13, 17}, {10, 17}, X11_STOP,
{0, 17}, {0, 14}, {3, 14}, {10, 3}, {13, 3}, {13, 0}, {20, 4.5}, {0, 17}, {0, 14}, {3, 14}, {10, 3}, {13, 3}, {13, 0}, {20, 4.5},
{13, 9}, {13, 6}, {12, 6}, {5, 17}, x11_stop, x11_stop, {13, 9}, {13, 6}, {12, 6}, {5, 17}, X11_STOP, X11_STOP,
}, },
x11_icon_single[] = x11_icon_single[] =
{ {
{7, 6}, {7, 4}, {9, 2}, {12, 2}, {12, 15}, {14, 15}, {14, 18}, {7, 6}, {7, 4}, {9, 2}, {12, 2}, {12, 15}, {14, 15}, {14, 18},
{7, 18}, {7, 15}, {9, 15}, {9, 6}, x11_stop, x11_stop, {7, 18}, {7, 15}, {9, 15}, {9, 6}, X11_STOP, X11_STOP,
}, },
x11_icon_consume[] = x11_icon_consume[] =
{ {
{0, 13}, {0, 7}, {4, 3}, {10, 3}, {14, 7}, {5, 10}, {14, 13}, {0, 13}, {0, 7}, {4, 3}, {10, 3}, {14, 7}, {5, 10}, {14, 13},
{10, 17}, {4, 17}, x11_stop, {10, 17}, {4, 17}, X11_STOP,
{16, 12}, {16, 8}, {20, 8}, {20, 12}, x11_stop, x11_stop, {16, 12}, {16, 8}, {20, 8}, {20, 12}, X11_STOP, X11_STOP,
}; };
static const XPointDouble * static const XPointDouble *
@@ -5791,10 +5791,8 @@ x11_render_scrollbar (struct widget *self)
x11_render_padding (self); x11_render_padding (self);
struct tab *tab = g.active_tab; struct tab *tab = g.active_tab;
// FIXME: This isn't an integer number in this case.
int visible_items = app_visible_items ();
struct scrollbar bar = struct scrollbar bar =
app_compute_scrollbar (tab, visible_items, g.ui_vunit); app_compute_scrollbar (tab, app_visible_items_height (), g.ui_vunit);
XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture, XRenderFillRectangle (g.dpy, PictOpSrc, g.x11_pixmap_picture,
x11_fg_attrs (self->attrs), x11_fg_attrs (self->attrs),
@@ -5814,17 +5812,22 @@ x11_make_scrollbar (chtype attrs)
return w; return w;
} }
// TODO: Handle partial items, otherwise this is the same as tui_render_list().
static void static void
x11_render_list (struct widget *self) x11_render_list (struct widget *self)
{ {
x11_render_padding (self); // We could do that for all widgets, but it would be kind-of pointless.
XRenderSetPictureClipRectangles (g.dpy, g.x11_pixmap_picture, 0, 0,
&(XRectangle) { self->x, self->y, self->width, self->height }, 1);
x11_render_padding (self);
LIST_FOR_EACH (struct widget, w, self->on_sublayout (self)) LIST_FOR_EACH (struct widget, w, self->on_sublayout (self))
{ {
w->on_render (w); w->on_render (w);
free (w); free (w);
} }
XRenderChangePicture (g.dpy, g.x11_pixmap_picture, CPClipMask,
&(XRenderPictureAttributes) { .clip_mask = None });
} }
static struct widget * static struct widget *
@@ -6204,6 +6207,7 @@ on_x11_selection_request (XSelectionRequestEvent *ev)
Atom xa_utf8 = XInternAtom (g.dpy, "UTF8_STRING", False); Atom xa_utf8 = XInternAtom (g.dpy, "UTF8_STRING", False);
Atom targets[] = { xa_targets, XA_STRING, xa_compound_text, xa_utf8 }; Atom targets[] = { xa_targets, XA_STRING, xa_compound_text, xa_utf8 };
XEvent response = {};
bool ok = false; bool ok = false;
Atom property = ev->property ? ev->property : ev->target; Atom property = ev->property ? ev->property : ev->target;
if (!g.x11_selection) if (!g.x11_selection)
@@ -6238,7 +6242,6 @@ on_x11_selection_request (XSelectionRequestEvent *ev)
XFree (text.value); XFree (text.value);
out: out:
XEvent response = {};
response.xselection.type = SelectionNotify; response.xselection.type = SelectionNotify;
// XXX: We should check it against the event causing XSetSelectionOwner(). // XXX: We should check it against the event causing XSetSelectionOwner().
response.xselection.time = ev->time; response.xselection.time = ev->time;