diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.go | 45 | ||||
-rw-r--r-- | src/footer.go | 9 | ||||
-rw-r--r-- | src/header.go | 47 | ||||
-rw-r--r-- | src/page_artists.go | 53 |
4 files changed, 110 insertions, 44 deletions
@@ -4,6 +4,7 @@ import ( | |||
4 | "fmt" | 4 | "fmt" |
5 | "os" | 5 | "os" |
6 | 6 | ||
7 | "git.sixfoisneuf.fr/termsonic/music" | ||
7 | "github.com/delucks/go-subsonic" | 8 | "github.com/delucks/go-subsonic" |
8 | "github.com/gdamore/tcell/v2" | 9 | "github.com/gdamore/tcell/v2" |
9 | "github.com/rivo/tview" | 10 | "github.com/rivo/tview" |
@@ -11,23 +12,26 @@ import ( | |||
11 | 12 | ||
12 | type app struct { | 13 | type app struct { |
13 | // General GUI | 14 | // General GUI |
14 | tv *tview.Application | 15 | tv *tview.Application |
15 | pages *tview.Pages | 16 | pages *tview.Pages |
16 | header *tview.TextView | 17 | headerSections *tview.TextView |
17 | footer *tview.TextView | 18 | headerNowPlaying *tview.TextView |
18 | cfg *Config | 19 | footer *tview.TextView |
20 | cfg *Config | ||
19 | 21 | ||
20 | // Artists panel | 22 | // Artists panel |
21 | artistsTree *tview.TreeView | 23 | artistsTree *tview.TreeView |
22 | songsList *tview.List | 24 | songsList *tview.List |
23 | 25 | ||
24 | // Subsonic variables | 26 | // Subsonic variables |
25 | sub *subsonic.Client | 27 | sub *subsonic.Client |
28 | playQueue *music.Queue | ||
26 | } | 29 | } |
27 | 30 | ||
28 | func Run(cfg *Config) { | 31 | func Run(cfg *Config) { |
29 | a := &app{ | 32 | a := &app{ |
30 | cfg: cfg, | 33 | cfg: cfg, |
34 | playQueue: music.NewQueue(nil), | ||
31 | } | 35 | } |
32 | 36 | ||
33 | a.tv = tview.NewApplication() | 37 | a.tv = tview.NewApplication() |
@@ -35,28 +39,13 @@ func Run(cfg *Config) { | |||
35 | a.footer = tview.NewTextView(). | 39 | a.footer = tview.NewTextView(). |
36 | SetDynamicColors(true) | 40 | SetDynamicColors(true) |
37 | 41 | ||
38 | a.header = tview.NewTextView(). | ||
39 | SetRegions(true). | ||
40 | SetChangedFunc(func() { | ||
41 | a.tv.Draw() | ||
42 | }). | ||
43 | SetHighlightedFunc(func(added, _, _ []string) { | ||
44 | hl := added[0] | ||
45 | cur, _ := a.pages.GetFrontPage() | ||
46 | |||
47 | if hl != cur { | ||
48 | a.switchToPage(hl) | ||
49 | } | ||
50 | }) | ||
51 | fmt.Fprintf(a.header, `["artists"]F1: Artists[""] | ["playlists"]F2: Playlists[""] | ["config"]F3: Configuration[""]`) | ||
52 | |||
53 | a.pages.SetBorder(true) | 42 | a.pages.SetBorder(true) |
54 | a.pages.AddPage("config", a.configPage(), true, false) | 43 | a.pages.AddPage("config", a.configPage(), true, false) |
55 | a.pages.AddPage("artists", a.artistsPage(), true, false) | 44 | a.pages.AddPage("artists", a.artistsPage(), true, false) |
56 | 45 | ||
57 | mainLayout := tview.NewFlex(). | 46 | mainLayout := tview.NewFlex(). |
58 | SetDirection(tview.FlexRow). | 47 | SetDirection(tview.FlexRow). |
59 | AddItem(a.header, 1, 1, false). | 48 | AddItem(a.buildHeader(), 1, 1, false). |
60 | AddItem(a.pages, 0, 3, true). | 49 | AddItem(a.pages, 0, 3, true). |
61 | AddItem(a.footer, 1, 1, false) | 50 | AddItem(a.footer, 1, 1, false) |
62 | 51 | ||
@@ -64,6 +53,7 @@ func Run(cfg *Config) { | |||
64 | a.switchToPage("config") | 53 | a.switchToPage("config") |
65 | } else { | 54 | } else { |
66 | a.sub, _ = buildSubsonicClient(a.cfg) | 55 | a.sub, _ = buildSubsonicClient(a.cfg) |
56 | a.playQueue.SetClient(a.sub) | ||
67 | err := a.refreshArtists() | 57 | err := a.refreshArtists() |
68 | if err != nil { | 58 | if err != nil { |
69 | a.alert("Could not refresh artists: %v", err) | 59 | a.alert("Could not refresh artists: %v", err) |
@@ -104,14 +94,17 @@ func (a *app) switchToPage(name string) { | |||
104 | switch name { | 94 | switch name { |
105 | case "artists": | 95 | case "artists": |
106 | a.pages.SwitchToPage("artists") | 96 | a.pages.SwitchToPage("artists") |
107 | a.header.Highlight("artists") | 97 | a.headerSections.Highlight("artists") |
108 | a.tv.SetFocus(a.artistsTree) | 98 | a.tv.SetFocus(a.artistsTree) |
99 | a.pages.SetBorder(false) | ||
109 | case "playlists": | 100 | case "playlists": |
110 | a.pages.SwitchToPage("playlists") | 101 | a.pages.SwitchToPage("playlists") |
111 | a.header.Highlight("playlists") | 102 | a.headerSections.Highlight("playlists") |
103 | a.pages.SetBorder(true) | ||
112 | case "config": | 104 | case "config": |
113 | a.pages.SwitchToPage("config") | 105 | a.pages.SwitchToPage("config") |
114 | a.header.Highlight("config") | 106 | a.headerSections.Highlight("config") |
107 | a.pages.SetBorder(true) | ||
115 | } | 108 | } |
116 | 109 | ||
117 | a.updateFooter() | 110 | a.updateFooter() |
diff --git a/src/footer.go b/src/footer.go index b0494d0..2695a61 100644 --- a/src/footer.go +++ b/src/footer.go | |||
@@ -1,14 +1,9 @@ | |||
1 | package src | 1 | package src |
2 | 2 | ||
3 | func (a *app) updateFooter() { | 3 | func (a *app) updateFooter() { |
4 | switch a.header.GetHighlights()[0] { | 4 | switch a.headerSections.GetHighlights()[0] { |
5 | case "artists": | 5 | case "artists": |
6 | switch a.tv.GetFocus() { | 6 | a.footer.SetText("[blue]l:[yellow] Next song [blue]k:[yellow] Toggle pause") |
7 | case a.artistsTree: | ||
8 | a.footer.SetText("Artists: [blue]Up/Down:[yellow] Move selection [blue]Space:[yellow] Select entry") | ||
9 | case a.songsList: | ||
10 | a.footer.SetText("Songs: [blue]Up/Down:[yellow] Move selection [blue]Space:[yellow] Play") | ||
11 | } | ||
12 | case "playlists": | 7 | case "playlists": |
13 | a.footer.SetText("Come back later!") | 8 | a.footer.SetText("Come back later!") |
14 | case "config": | 9 | case "config": |
diff --git a/src/header.go b/src/header.go new file mode 100644 index 0000000..5c85808 --- /dev/null +++ b/src/header.go | |||
@@ -0,0 +1,47 @@ | |||
1 | package src | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/delucks/go-subsonic" | ||
7 | "github.com/rivo/tview" | ||
8 | ) | ||
9 | |||
10 | func (a *app) buildHeader() tview.Primitive { | ||
11 | flex := tview.NewFlex() | ||
12 | flex.SetDirection(tview.FlexColumn) | ||
13 | |||
14 | a.headerSections = tview.NewTextView(). | ||
15 | SetRegions(true). | ||
16 | SetChangedFunc(func() { | ||
17 | a.tv.Draw() | ||
18 | }). | ||
19 | SetHighlightedFunc(func(added, _, _ []string) { | ||
20 | hl := added[0] | ||
21 | cur, _ := a.pages.GetFrontPage() | ||
22 | |||
23 | if hl != cur { | ||
24 | a.switchToPage(hl) | ||
25 | } | ||
26 | }) | ||
27 | fmt.Fprintf(a.headerSections, `["artists"]F1: Artists[""] | ["playlists"]F2: Playlists[""] | ["config"]F3: Configuration[""]`) | ||
28 | |||
29 | a.headerNowPlaying = tview.NewTextView().SetTextAlign(tview.AlignRight) | ||
30 | |||
31 | flex.AddItem(a.headerSections, 0, 1, false) | ||
32 | flex.AddItem(a.headerNowPlaying, 0, 1, false) | ||
33 | |||
34 | a.playQueue.SetOnChangeCallback(func(song *subsonic.Child, isPaused bool) { | ||
35 | if song != nil { | ||
36 | symbol := ">" | ||
37 | if isPaused { | ||
38 | symbol = "||" | ||
39 | } | ||
40 | a.headerNowPlaying.SetText(fmt.Sprintf("%s %s - %s", symbol, song.Title, song.Artist)) | ||
41 | } else { | ||
42 | a.headerNowPlaying.SetText("Not playing") | ||
43 | } | ||
44 | }) | ||
45 | |||
46 | return flex | ||
47 | } | ||
diff --git a/src/page_artists.go b/src/page_artists.go index e8ce180..4361767 100644 --- a/src/page_artists.go +++ b/src/page_artists.go | |||
@@ -4,6 +4,7 @@ import ( | |||
4 | "fmt" | 4 | "fmt" |
5 | "time" | 5 | "time" |
6 | 6 | ||
7 | "github.com/delucks/go-subsonic" | ||
7 | "github.com/gdamore/tcell/v2" | 8 | "github.com/gdamore/tcell/v2" |
8 | "github.com/rivo/tview" | 9 | "github.com/rivo/tview" |
9 | ) | 10 | ) |
@@ -14,9 +15,7 @@ type selection struct { | |||
14 | } | 15 | } |
15 | 16 | ||
16 | func (a *app) artistsPage() tview.Primitive { | 17 | func (a *app) artistsPage() tview.Primitive { |
17 | grid := tview.NewGrid(). | 18 | grid := tview.NewFlex().SetDirection(tview.FlexColumn) |
18 | SetColumns(40, 0). | ||
19 | SetBorders(true) | ||
20 | 19 | ||
21 | // Artist & album list | 20 | // Artist & album list |
22 | root := tview.NewTreeNode("Subsonic server").SetColor(tcell.ColorYellow) | 21 | root := tview.NewTreeNode("Subsonic server").SetColor(tcell.ColorYellow) |
@@ -37,18 +36,18 @@ func (a *app) artistsPage() tview.Primitive { | |||
37 | 36 | ||
38 | a.loadAlbumInPanel(sel.id) | 37 | a.loadAlbumInPanel(sel.id) |
39 | a.tv.SetFocus(a.songsList) | 38 | a.tv.SetFocus(a.songsList) |
40 | a.updateFooter() | ||
41 | }) | 39 | }) |
40 | a.artistsTree.SetBorderAttributes(tcell.AttrDim).SetBorder(true) | ||
42 | 41 | ||
43 | // Songs list for the selected album | 42 | // Songs list for the selected album |
44 | a.songsList = tview.NewList() | 43 | a.songsList = tview.NewList() |
45 | a.songsList.ShowSecondaryText(false) | 44 | a.songsList.ShowSecondaryText(false) |
45 | a.songsList.SetBorderAttributes(tcell.AttrDim).SetBorder(true) | ||
46 | 46 | ||
47 | // Change the left-right keys to switch between the panels | 47 | // Change the left-right keys to switch between the panels |
48 | a.artistsTree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | 48 | a.artistsTree.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { |
49 | if event.Key() == tcell.KeyLeft || event.Key() == tcell.KeyRight { | 49 | if event.Key() == tcell.KeyLeft || event.Key() == tcell.KeyRight { |
50 | a.tv.SetFocus(a.songsList) | 50 | a.tv.SetFocus(a.songsList) |
51 | a.updateFooter() | ||
52 | return nil | 51 | return nil |
53 | } | 52 | } |
54 | return event | 53 | return event |
@@ -57,14 +56,26 @@ func (a *app) artistsPage() tview.Primitive { | |||
57 | a.songsList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | 56 | a.songsList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { |
58 | if event.Key() == tcell.KeyLeft || event.Key() == tcell.KeyRight { | 57 | if event.Key() == tcell.KeyLeft || event.Key() == tcell.KeyRight { |
59 | a.tv.SetFocus(a.artistsTree) | 58 | a.tv.SetFocus(a.artistsTree) |
60 | a.updateFooter() | ||
61 | return nil | 59 | return nil |
62 | } | 60 | } |
63 | return event | 61 | return event |
64 | }) | 62 | }) |
65 | 63 | ||
66 | grid.AddItem(a.artistsTree, 0, 0, 1, 1, 0, 0, true) | 64 | grid.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { |
67 | grid.AddItem(a.songsList, 0, 1, 1, 2, 0, 0, false) | 65 | if event.Rune() == 'l' { |
66 | a.playQueue.Next() | ||
67 | return nil | ||
68 | } | ||
69 | |||
70 | if event.Rune() == 'k' { | ||
71 | a.playQueue.TogglePause() | ||
72 | return nil | ||
73 | } | ||
74 | return event | ||
75 | }) | ||
76 | |||
77 | grid.AddItem(a.artistsTree, 0, 1, true) | ||
78 | grid.AddItem(a.songsList, 0, 1, false) | ||
68 | 79 | ||
69 | return grid | 80 | return grid |
70 | } | 81 | } |
@@ -113,12 +124,32 @@ func (a *app) loadAlbumInPanel(id string) error { | |||
113 | return err | 124 | return err |
114 | } | 125 | } |
115 | 126 | ||
116 | a.songsList.SetTitle(album.Name) | 127 | var songs []*subsonic.Child |
128 | |||
117 | a.songsList.Clear() | 129 | a.songsList.Clear() |
118 | for _, song := range album.Child { | 130 | for i := len(album.Child) - 1; i >= 0; i-- { |
131 | song := album.Child[i] | ||
132 | songNoPtr := *song | ||
133 | songs = append([]*subsonic.Child{&songNoPtr}, songs...) | ||
134 | |||
135 | songsCopy := make([]*subsonic.Child, len(songs)) | ||
136 | copy(songsCopy, songs) | ||
137 | |||
119 | dur := time.Duration(song.Duration) * time.Second | 138 | dur := time.Duration(song.Duration) * time.Second |
120 | a.songsList.AddItem(fmt.Sprintf("%-10s %d - %s", fmt.Sprintf("[%s]", dur.String()), song.Track, song.Title), "", 0, nil) | 139 | |
140 | a.songsList.InsertItem(0, fmt.Sprintf("%-10s %d - %s", fmt.Sprintf("[%s]", dur.String()), song.Track, song.Title), "", 0, func() { | ||
141 | a.playQueue.Clear() | ||
142 | for _, s := range songsCopy { | ||
143 | a.playQueue.Append(s) | ||
144 | } | ||
145 | err := a.playQueue.Play() | ||
146 | if err != nil { | ||
147 | a.alert("Error: %v", err) | ||
148 | } | ||
149 | }) | ||
121 | } | 150 | } |
122 | 151 | ||
152 | a.songsList.SetCurrentItem(0) | ||
153 | |||
123 | return nil | 154 | return nil |
124 | } | 155 | } |