diff --git a/app/app.go b/app/app.go index 0137f1b..1d8791f 100644 --- a/app/app.go +++ b/app/app.go @@ -12,25 +12,9 @@ import ( "github.com/faiface/beep/speaker" ) -// TogglePause pauses/unpauses audio when a channel is playing -func TogglePause(ctx *context.AppContext) { - - // nothing to do if nothing has been streamed - if ctx.AudioStream == nil { - return - } - - ctx.AudioStream.Close() - if !ctx.IsPlaying { - PlayChannel(ctx.CurrentChannel, ctx) - } - - ctx.IsPlaying = !ctx.IsPlaying -} - // PlayChannel begins streaming the provided channel after fetching its playlist // If a channel is already playing, the old stream is stopped first, clearing up resources. -// This function is asynchronous and creates a single streaming resource: he audio stream held by the application +// This function is *asynchronous* and creates a single streaming resource: the audio stream held by the application // context. To clean up resources created by this function, Close() the application's audio stream. func PlayChannel(chn *components.ChannelItem, ctx *context.AppContext) { @@ -59,14 +43,31 @@ func PlayChannel(chn *components.ChannelItem, ctx *context.AppContext) { speaker.Play(ctx.AudioStream) ctx.IsPlaying = true - setNowPlaying(chn, ctx) + updateNowPlaying(chn, ctx) } }() } -func setNowPlaying(chn *components.ChannelItem, ctx *context.AppContext) { +// TogglePause pauses/unpauses audio when a channel is playing +func TogglePause(ctx *context.AppContext) { + + // nothing to do if nothing has been streamed + if ctx.AudioStream == nil { + return + } + + ctx.AudioStream.Close() + if !ctx.IsPlaying { + PlayChannel(ctx.CurrentChannel, ctx) + } + + ctx.IsPlaying = !ctx.IsPlaying +} + +func updateNowPlaying(chn *components.ChannelItem, ctx *context.AppContext) { ctx.CurrentChannel = chn cp := difm.GetCurrentlyPlaying(ctx) + ctx.View.App.QueueUpdateDraw(func() { ctx.View.NowPlaying.Channel = chn track := cp.Track diff --git a/dicli.go b/dicli.go index 68a3634..92f7cf7 100644 --- a/dicli.go +++ b/dicli.go @@ -9,9 +9,9 @@ import ( "github.com/acaloiaro/dicli/context" "github.com/acaloiaro/dicli/difm" "github.com/acaloiaro/dicli/views" + "github.com/rivo/tview" "github.com/gdamore/tcell" - "github.com/rivo/tview" "github.com/spf13/pflag" "github.com/spf13/viper" ) @@ -45,6 +45,37 @@ func main() { } func run() { + configureEventHandling() + configureUIComponents() + layout := buildUILayout() + + err := ctx.View.App. + SetRoot(layout, true). + SetFocus(ctx.View.ChannelList). + Run() + + if err != nil { + panic(err) + } +} + +func buildUILayout() *tview.Flex { + main := tview.NewFlex() + main.SetDirection(tview.FlexRow) + + flex := tview.NewFlex() + flex. + AddItem(ctx.View.ChannelList, 0, 1, false). + AddItem(ctx.View.NowPlaying, 0, 2, false) + + main. + AddItem(flex, 0, 15, false). + AddItem(ctx.View.Keybindings, 0, 1, false) + + return main +} + +func configureEventHandling() { ctx.View.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Rune() { case 'q': @@ -62,28 +93,27 @@ func run() { return event }) +} +func configureUIComponents() { + + // configure the channel list channels := difm.ListChannels() for _, chn := range channels { ctx.View.ChannelList.AddItem(chn.Name, "", 0, func() { - go func() { - chn := channels[ctx.View.ChannelList.GetCurrentItem()] - app.PlayChannel(&chn, ctx) - }() + chn := channels[ctx.View.ChannelList.GetCurrentItem()] + app.PlayChannel(&chn, ctx) }) } - flex := tview.NewFlex() - flex. - AddItem(ctx.View.ChannelList, 0, 1, false). - AddItem(ctx.View.NowPlaying, 0, 2, false) - - err := ctx.View.App. - SetRoot(flex, true). - SetFocus(ctx.View.ChannelList). - Run() - - if err != nil { - panic(err) + // configure the keybinding view + bindings := []views.UIKeybinding{ + views.UIKeybinding{Shortcut: "q", Description: "Quit", Func: func() {}}, + views.UIKeybinding{Shortcut: "p", Description: "Pause", Func: func() {}}, + views.UIKeybinding{Shortcut: "j", Description: "Scroll Up", Func: func() {}}, + views.UIKeybinding{Shortcut: "k", Description: "Scroll Down", Func: func() {}}, + views.UIKeybinding{Shortcut: "Enter", Description: "Play Selected", Func: func() {}}, } + + ctx.View.Keybindings.Bindings = bindings } diff --git a/views/views.go b/views/views.go index 9c3d0a6..1d93b6e 100644 --- a/views/views.go +++ b/views/views.go @@ -12,13 +12,29 @@ type AppView struct { App *tview.Application ChannelList *tview.List NowPlaying *NowPlayingView + Keybindings *KeybindingView } +// KeybindingView is a custom view for dispalying the keyboard bindings available to users +type KeybindingView struct { + *tview.Box + Bindings []UIKeybinding +} + +// UIKeybinding is a helper struct for building a ControlsView +type UIKeybinding struct { + Shortcut string // a keybinding to bind to this control + Description string // a description of what the keybinding does + Func func() // the funcion to execute when the binding is pressed +} + +// CreateAppView creates the primary application view of di-tui func CreateAppView() *AppView { return &AppView{ App: tview.NewApplication(), ChannelList: createChannelList(), - NowPlaying: newNowPlaying(&components.ChannelItem{Name: "N/A"}), + NowPlaying: newNowPlaying(&components.ChannelItem{}), + Keybindings: createKeybindings(), } } @@ -48,16 +64,29 @@ func (n *NowPlayingView) Draw(screen tcell.Screen) { x, y, width, _ := n.GetInnerRect() line := fmt.Sprintf("%s[white] %s", "Channel:", n.Channel.Name) - tview.Print(screen, line, x, y, width, tview.AlignLeft, tcell.ColorYellow) + tview.Print(screen, line, x, y, width, tview.AlignLeft, tcell.ColorBlue) line = fmt.Sprintf("%s[white] %s", "Artist:", n.Artist) - tview.Print(screen, line, x, y+1, width, tview.AlignLeft, tcell.ColorYellow) + tview.Print(screen, line, x, y+1, width, tview.AlignLeft, tcell.ColorBlue) line = fmt.Sprintf("%s[white] %s", "Track:", n.Track) - tview.Print(screen, line, x, y+2, width, tview.AlignLeft, tcell.ColorYellow) + tview.Print(screen, line, x, y+2, width, tview.AlignLeft, tcell.ColorBlue) } +// Draw draws this primitive onto the screen. +func (n *KeybindingView) Draw(screen tcell.Screen) { + n.Box.Draw(screen) + x, y, width, _ := n.GetInnerRect() + + previousWidth := 0 + for _, bnd := range n.Bindings { + line := fmt.Sprintf("(%s)[white] %s", bnd.Shortcut, bnd.Description) + tview.Print(screen, line, x+previousWidth, y, width, tview.AlignLeft, tcell.ColorBlue) + previousWidth += len(bnd.Shortcut) + len(bnd.Description) + 5 + } +} + func createChannelList() *tview.List { list := tview.NewList() list. @@ -67,3 +96,12 @@ func createChannelList() *tview.List { return list } + +func createKeybindings() *KeybindingView { + kbv := &KeybindingView{Box: tview.NewBox()} + kbv. + SetBorder(true). + SetTitle(" Key Bindings ") + + return kbv +}