Better header layout
This commit is contained in:
parent
a72c5c2fc5
commit
767e87bd3f
154
nncmpp.c
154
nncmpp.c
|
@ -101,7 +101,13 @@ update_curses_terminal_size (void)
|
||||||
// global namespace and makes it harder to distinguish what functions relate to.
|
// global namespace and makes it harder to distinguish what functions relate to.
|
||||||
|
|
||||||
// Avoiding colours in the defaults here in order to support dumb terminals
|
// Avoiding colours in the defaults here in order to support dumb terminals
|
||||||
|
// TODO: we also want a different "highlighted" attribute for the top part
|
||||||
|
// -> then we have to do attrset(0)
|
||||||
|
// TODO: add two attributes for the gauge
|
||||||
|
// -> we should also make it possible to set characters for both parts
|
||||||
|
// TODO: create another attribute for selected items
|
||||||
#define ATTRIBUTE_TABLE(XX) \
|
#define ATTRIBUTE_TABLE(XX) \
|
||||||
|
XX( TOP, "top", -1, -1, 0 ) \
|
||||||
XX( HEADER, "header", -1, -1, A_REVERSE ) \
|
XX( HEADER, "header", -1, -1, A_REVERSE ) \
|
||||||
XX( ACTIVE, "header_active", -1, -1, A_UNDERLINE ) \
|
XX( ACTIVE, "header_active", -1, -1, A_UNDERLINE ) \
|
||||||
XX( EVEN, "even", -1, -1, 0 ) \
|
XX( EVEN, "even", -1, -1, 0 ) \
|
||||||
|
@ -194,8 +200,13 @@ static struct app_context
|
||||||
struct poller_timer reconnect_event;///< MPD reconnect timer
|
struct poller_timer reconnect_event;///< MPD reconnect timer
|
||||||
|
|
||||||
enum player_state state; ///< Player state
|
enum player_state state; ///< Player state
|
||||||
// TODO: probably save the full info reply
|
struct str_map song_info; ///< Current song info
|
||||||
char *song; ///< Currently playing song
|
|
||||||
|
// FIXME: this is doomed to drift unless we use POSIX CLOCK_MONOTONIC
|
||||||
|
struct poller_timer elapsed_event; ///< Seconds elapsed event
|
||||||
|
// TODO: initialize these to -1
|
||||||
|
int song_elapsed; ///< Song elapsed in seconds
|
||||||
|
int song_duration; ///< Song duration in seconds
|
||||||
|
|
||||||
// Data:
|
// Data:
|
||||||
|
|
||||||
|
@ -432,7 +443,7 @@ static void
|
||||||
app_free_context (void)
|
app_free_context (void)
|
||||||
{
|
{
|
||||||
mpd_client_free (&g_ctx.client);
|
mpd_client_free (&g_ctx.client);
|
||||||
free (g_ctx.song);
|
str_map_free (&g_ctx.song_info);
|
||||||
|
|
||||||
config_free (&g_ctx.config);
|
config_free (&g_ctx.config);
|
||||||
poller_free (&g_ctx.poller);
|
poller_free (&g_ctx.poller);
|
||||||
|
@ -672,36 +683,112 @@ app_write_utf8 (const char *str, chtype attrs, int n)
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear a row in the header to be used and increment the listview offset
|
||||||
|
static void
|
||||||
|
app_next_row (chtype attrs)
|
||||||
|
{
|
||||||
|
mvwhline (stdscr, g_ctx.list_offset++, 0, ' ' | attrs, COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
app_redraw_status (void)
|
||||||
|
{
|
||||||
|
if (g_ctx.state == PLAYER_STOPPED)
|
||||||
|
goto line;
|
||||||
|
|
||||||
|
// The map doesn't need to be initialized at all, so we need to check
|
||||||
|
struct str_map *map = &g_ctx.song_info;
|
||||||
|
if (!soft_assert (map->len != 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
char *title;
|
||||||
|
if ((title = str_map_find (map, "title"))
|
||||||
|
|| (title = str_map_find (map, "name"))
|
||||||
|
|| (title = str_map_find (map, "file")))
|
||||||
|
{
|
||||||
|
app_next_row (0);
|
||||||
|
app_write_utf8 (title, A_BOLD, COLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *artist = str_map_find (map, "artist");
|
||||||
|
char *album = str_map_find (map, "album");
|
||||||
|
if (artist || album)
|
||||||
|
{
|
||||||
|
app_next_row (0);
|
||||||
|
|
||||||
|
struct row_buffer buf;
|
||||||
|
row_buffer_init (&buf);
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
if (artist)
|
||||||
|
{
|
||||||
|
if (!first) row_buffer_append (&buf, " ", 0);
|
||||||
|
row_buffer_append (&buf, "by ", 0);
|
||||||
|
row_buffer_append (&buf, artist, A_BOLD);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
if (album)
|
||||||
|
{
|
||||||
|
if (!first) row_buffer_append (&buf, " ", 0);
|
||||||
|
row_buffer_append (&buf, "from ", 0);
|
||||||
|
row_buffer_append (&buf, album, A_BOLD);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.total_width > COLS)
|
||||||
|
row_buffer_ellipsis (&buf, COLS, 0);
|
||||||
|
row_buffer_flush (&buf);
|
||||||
|
row_buffer_free (&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
line:
|
||||||
|
app_next_row (0);
|
||||||
|
|
||||||
|
bool stopped = g_ctx.state == PLAYER_STOPPED;
|
||||||
|
app_write_utf8 ("<< ", stopped ? 0 : A_BOLD, -1);
|
||||||
|
|
||||||
|
if (g_ctx.state == PLAYER_PLAYING)
|
||||||
|
app_write_utf8 ("|| ", A_BOLD, -1);
|
||||||
|
else
|
||||||
|
app_write_utf8 ("|> ", A_BOLD, -1);
|
||||||
|
|
||||||
|
app_write_utf8 ("[] ", stopped ? 0 : A_BOLD, -1);
|
||||||
|
app_write_utf8 (">> ", stopped ? 0 : A_BOLD, -1);
|
||||||
|
|
||||||
|
if (stopped)
|
||||||
|
app_write_utf8 ("Stopped", 0, COLS);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: convert and display "song_elapsed / song_duration"
|
||||||
|
// TODO: display a gauge representing the same information
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: append the volume value if available
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
app_redraw_top (void)
|
app_redraw_top (void)
|
||||||
{
|
{
|
||||||
// TODO: this will eventually be dynamically computed depending on contents
|
// TODO: when it changes from the previous value, fix the selection
|
||||||
g_ctx.list_offset = 2;
|
g_ctx.list_offset = 0;
|
||||||
|
|
||||||
attrset (0);
|
attrset (APP_ATTR (TOP));
|
||||||
mvwhline (stdscr, 0, 0, 0, COLS);
|
|
||||||
switch (g_ctx.client.state)
|
switch (g_ctx.client.state)
|
||||||
{
|
{
|
||||||
case MPD_CONNECTED:
|
case MPD_CONNECTED:
|
||||||
switch (g_ctx.state)
|
app_redraw_status ();
|
||||||
{
|
|
||||||
case PLAYER_PLAYING:
|
|
||||||
case PLAYER_PAUSED:
|
|
||||||
app_write_utf8 (g_ctx.song, 0, COLS);
|
|
||||||
break;
|
|
||||||
case PLAYER_STOPPED:
|
|
||||||
app_write_utf8 ("Stopped", 0, COLS);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case MPD_CONNECTING:
|
case MPD_CONNECTING:
|
||||||
|
app_next_row (0);
|
||||||
app_write_utf8 ("Connecting to MPD...", 0, COLS);
|
app_write_utf8 ("Connecting to MPD...", 0, COLS);
|
||||||
break;
|
break;
|
||||||
case MPD_DISCONNECTED:
|
case MPD_DISCONNECTED:
|
||||||
|
app_next_row (0);
|
||||||
app_write_utf8 ("Disconnected", 0, COLS);
|
app_write_utf8 ("Disconnected", 0, COLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
attrset (APP_ATTR (HEADER));
|
attrset (APP_ATTR (HEADER));
|
||||||
mvwhline (stdscr, 1, 0, APP_ATTR (HEADER), COLS);
|
app_next_row (0);
|
||||||
// TODO: render this with APP_ATTR (ACTIVE) when the help tab is selected;
|
// TODO: render this with APP_ATTR (ACTIVE) when the help tab is selected;
|
||||||
// ...maybe the help tab should not even be on the list?
|
// ...maybe the help tab should not even be on the list?
|
||||||
size_t indent = app_write_utf8 (APP_TITLE, A_BOLD, -1);
|
size_t indent = app_write_utf8 (APP_TITLE, A_BOLD, -1);
|
||||||
|
@ -1163,6 +1250,7 @@ mpd_on_info_response (const struct mpd_response *response,
|
||||||
{
|
{
|
||||||
print_debug ("%s: %s",
|
print_debug ("%s: %s",
|
||||||
"retrieving MPD info failed", response->message_text);
|
"retrieving MPD info failed", response->message_text);
|
||||||
|
// TODO: invalidate any song-related data
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1179,24 +1267,26 @@ mpd_on_info_response (const struct mpd_response *response,
|
||||||
g_ctx.state = PLAYER_PAUSED;
|
g_ctx.state = PLAYER_PAUSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct str s;
|
// Note that we may receive a "time" field twice, however the right one
|
||||||
str_init (&s);
|
// wins here due to the order we send the commands in
|
||||||
|
char *time = str_map_find (&map, "time");
|
||||||
|
if (time)
|
||||||
|
{
|
||||||
|
char *colon = strchr (time, ':');
|
||||||
|
// TODO: split "time" at ':' -> elapsed seconds, total seconds;
|
||||||
|
// if there's no colon, use "duration"
|
||||||
|
}
|
||||||
|
|
||||||
char *mpd_song = NULL;
|
// TODO: if we're playing, parse the "elapsed" value and use it
|
||||||
if ((value = str_map_find (&map, "title"))
|
// to set a timer for status updates (cancel the timer at the start
|
||||||
|| (value = str_map_find (&map, "name"))
|
// of the info callback and upon disconnect)
|
||||||
|| (value = str_map_find (&map, "file")))
|
|
||||||
str_append_printf (&s, "\"%s\"", value);
|
|
||||||
if ((value = str_map_find (&map, "artist")))
|
|
||||||
str_append_printf (&s, " by \"%s\"", value);
|
|
||||||
if ((value = str_map_find (&map, "album")))
|
|
||||||
str_append_printf (&s, " from \"%s\"", value);
|
|
||||||
mpd_song = str_steal (&s);
|
|
||||||
|
|
||||||
str_map_free (&map);
|
// TODO: "volume" is a string, parse it nonetheless so that we can later
|
||||||
|
// tell MPD to change it
|
||||||
|
|
||||||
|
str_map_free (&g_ctx.song_info);
|
||||||
|
g_ctx.song_info = map;
|
||||||
|
|
||||||
free (g_ctx.song);
|
|
||||||
g_ctx.song = mpd_song;
|
|
||||||
app_redraw ();
|
app_redraw ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue