refactor UI for better separation
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type AddServerForm struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Nickname string
|
||||
Host string
|
||||
Username string
|
||||
Password string
|
||||
OnSubmit func()
|
||||
|
||||
container *fyne.Container
|
||||
}
|
||||
|
||||
var _ fyne.Widget = (*AddServerForm)(nil)
|
||||
|
||||
func NewAddServerForm(title string) *AddServerForm {
|
||||
a := &AddServerForm{}
|
||||
a.ExtendBaseWidget(a)
|
||||
titleLabel := widget.NewLabel(title)
|
||||
titleLabel.TextStyle.Bold = true
|
||||
nickField := widget.NewEntryWithData(binding.BindString(&a.Nickname))
|
||||
nickField.SetPlaceHolder("My Server")
|
||||
hostField := widget.NewEntryWithData(binding.BindString(&a.Host))
|
||||
hostField.SetPlaceHolder("http://localhost:4533")
|
||||
userField := widget.NewEntryWithData(binding.BindString(&a.Username))
|
||||
passField := widget.NewPasswordEntry()
|
||||
submit := widget.NewButton("Enter", func() {
|
||||
a.Password = passField.Text
|
||||
if a.OnSubmit != nil {
|
||||
a.OnSubmit()
|
||||
}
|
||||
})
|
||||
|
||||
a.container = container.NewVBox(
|
||||
container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()),
|
||||
container.New(layout.NewFormLayout(),
|
||||
widget.NewLabel("Nickname"),
|
||||
nickField,
|
||||
widget.NewLabel("Hostname"),
|
||||
hostField,
|
||||
widget.NewLabel("Username"),
|
||||
userField,
|
||||
widget.NewLabel("Password"),
|
||||
passField,
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
container.NewHBox(
|
||||
layout.NewSpacer(),
|
||||
submit))
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AddServerForm) MinSize() fyne.Size {
|
||||
a.ExtendBaseWidget(a)
|
||||
return fyne.NewSize(300, a.container.MinSize().Height)
|
||||
}
|
||||
|
||||
func (a *AddServerForm) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(a.container)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
|
||||
"supersonic/ui/layout"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"github.com/dweymouth/go-subsonic"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*AlbumCard)(nil)
|
||||
|
||||
var _ fyne.Widget = (*albumCover)(nil)
|
||||
var _ fyne.Tappable = (*albumCover)(nil)
|
||||
var _ fyne.DoubleTappable = (*albumCover)(nil)
|
||||
|
||||
type albumCover struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Im *canvas.Image
|
||||
OnDoubleTapped func()
|
||||
OnTapped func()
|
||||
}
|
||||
|
||||
func newAlbumCover() *albumCover {
|
||||
a := &albumCover{}
|
||||
a.ExtendBaseWidget(a)
|
||||
a.Im = &canvas.Image{FillMode: canvas.ImageFillContain}
|
||||
a.Im.SetMinSize(fyne.NewSize(200, 200))
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *albumCover) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(a.Im)
|
||||
}
|
||||
|
||||
func (a *albumCover) DoubleTapped(e *fyne.PointEvent) {
|
||||
if a.OnDoubleTapped != nil {
|
||||
a.OnDoubleTapped()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *albumCover) Tapped(e *fyne.PointEvent) {
|
||||
if a.OnTapped != nil {
|
||||
a.OnTapped()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *albumCover) SetImage(im image.Image) {
|
||||
a.Im.Image = im
|
||||
a.Refresh()
|
||||
}
|
||||
|
||||
type AlbumCard struct {
|
||||
widget.BaseWidget
|
||||
|
||||
albumID string
|
||||
title *widget.Label
|
||||
artist *widget.Label
|
||||
container *fyne.Container
|
||||
|
||||
// updated by AlbumGrid
|
||||
Cover *albumCover
|
||||
|
||||
// these fields are used by AlbumGrid to track async update tasks
|
||||
PrevAlbumID string
|
||||
ImgLoadCancel context.CancelFunc
|
||||
|
||||
OnPlay func()
|
||||
}
|
||||
|
||||
func (a *AlbumCard) MouseIn(*desktop.MouseEvent) {}
|
||||
|
||||
func (a *AlbumCard) MouseOut() {}
|
||||
|
||||
func (a *AlbumCard) MouseMoved(*desktop.MouseEvent) {}
|
||||
|
||||
func NewAlbumCard() *AlbumCard {
|
||||
a := &AlbumCard{
|
||||
title: widget.NewLabel(""),
|
||||
artist: widget.NewLabel(""),
|
||||
Cover: newAlbumCover(),
|
||||
}
|
||||
a.ExtendBaseWidget(a)
|
||||
a.Cover.OnDoubleTapped = func() {
|
||||
if a.OnPlay != nil {
|
||||
a.OnPlay()
|
||||
}
|
||||
}
|
||||
a.title.Wrapping = fyne.TextTruncate
|
||||
a.artist.Wrapping = fyne.TextTruncate
|
||||
a.title.TextStyle = fyne.TextStyle{Bold: true}
|
||||
|
||||
a.createContainer()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AlbumCard) createContainer() {
|
||||
titleArtist := container.New(&layout.VboxCustomPadding{ExtraPad: -16}, a.title, a.artist)
|
||||
c := container.New(&layout.VboxCustomPadding{ExtraPad: -5}, a.Cover, titleArtist)
|
||||
pad := &layout.CenterPadLayout{PadLeftRight: 20, PadTopBottom: 10}
|
||||
a.container = container.New(pad, c)
|
||||
}
|
||||
|
||||
func (a *AlbumCard) Update(al *subsonic.AlbumID3) {
|
||||
a.title.SetText(al.Name)
|
||||
a.artist.SetText(al.Artist)
|
||||
a.albumID = al.ID
|
||||
}
|
||||
|
||||
func (a *AlbumCard) AlbumID() string {
|
||||
return a.albumID
|
||||
}
|
||||
|
||||
func (a *AlbumCard) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(a.container)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"supersonic/ui/layout"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type NowPlayingCard struct {
|
||||
widget.BaseWidget
|
||||
|
||||
trackName *widget.Label
|
||||
artistName *widget.Label
|
||||
albumName *widget.Label
|
||||
cover *canvas.Image
|
||||
|
||||
c fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewNowPlayingCard() *NowPlayingCard {
|
||||
n := &NowPlayingCard{
|
||||
trackName: widget.NewLabel(""),
|
||||
artistName: widget.NewLabel(""),
|
||||
albumName: widget.NewLabel(""),
|
||||
cover: &canvas.Image{},
|
||||
}
|
||||
n.ExtendBaseWidget(n)
|
||||
n.trackName.Wrapping = fyne.TextTruncate
|
||||
n.trackName.TextStyle = fyne.TextStyle{Bold: true}
|
||||
n.artistName.Wrapping = fyne.TextTruncate
|
||||
n.albumName.Wrapping = fyne.TextTruncate
|
||||
n.cover.SetMinSize(fyne.NewSize(100, 100))
|
||||
n.cover.FillMode = canvas.ImageFillContain
|
||||
|
||||
n.c = container.NewBorder(nil, nil, n.cover, nil, container.New(&layout.VboxCustomPadding{ExtraPad: -10}, n.trackName, n.artistName, n.albumName))
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *NowPlayingCard) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(n.c)
|
||||
}
|
||||
|
||||
func (n *NowPlayingCard) Update(track, artist, album string, cover image.Image) {
|
||||
n.trackName.Text = track
|
||||
n.artistName.Text = artist
|
||||
n.albumName.Text = album
|
||||
n.cover.Image = cover
|
||||
n.c.Refresh()
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"supersonic/ui/util"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// TrackPosSlider is a custom slider that doesn't trigger
|
||||
// the seek action until drag end.
|
||||
type TrackPosSlider struct {
|
||||
widget.Slider
|
||||
|
||||
OnDragEnd func(float64)
|
||||
|
||||
isDragging bool
|
||||
lastDraggedValue float64
|
||||
}
|
||||
|
||||
func NewTrackPosSlider() *TrackPosSlider {
|
||||
slider := &TrackPosSlider{
|
||||
Slider: widget.Slider{
|
||||
Value: 0,
|
||||
Min: 0,
|
||||
Max: 1,
|
||||
Step: 0.002,
|
||||
Orientation: widget.Horizontal,
|
||||
},
|
||||
}
|
||||
slider.ExtendBaseWidget(slider)
|
||||
return slider
|
||||
}
|
||||
|
||||
func (t *TrackPosSlider) DragEnd() {
|
||||
t.isDragging = false
|
||||
t.Slider.DragEnd()
|
||||
if t.OnDragEnd != nil {
|
||||
t.OnDragEnd(t.lastDraggedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TrackPosSlider) Dragged(e *fyne.DragEvent) {
|
||||
t.isDragging = true
|
||||
t.Slider.Dragged(e)
|
||||
t.lastDraggedValue = t.Value
|
||||
}
|
||||
|
||||
func (t *TrackPosSlider) IsDragging() bool {
|
||||
return t.isDragging
|
||||
}
|
||||
|
||||
type PlayerControls struct {
|
||||
widget.BaseWidget
|
||||
|
||||
slider *TrackPosSlider
|
||||
curTimeLabel *widget.Label
|
||||
totalTimeLabel *widget.Label
|
||||
prev *widget.Button
|
||||
playpause *widget.Button
|
||||
next *widget.Button
|
||||
container *fyne.Container
|
||||
|
||||
totalTime float64
|
||||
}
|
||||
|
||||
var _ fyne.Widget = (*PlayerControls)(nil)
|
||||
|
||||
// NewPlayerControls sets up the seek bar, and transport buttons.
|
||||
func NewPlayerControls() *PlayerControls {
|
||||
pc := &PlayerControls{}
|
||||
pc.ExtendBaseWidget(pc)
|
||||
|
||||
pc.slider = NewTrackPosSlider()
|
||||
pc.curTimeLabel = widget.NewLabel("0:00")
|
||||
pc.totalTimeLabel = widget.NewLabel("0:00")
|
||||
|
||||
pc.slider.OnChanged = func(f float64) {
|
||||
time := f * pc.totalTime
|
||||
pc.curTimeLabel.SetText(util.SecondsToTimeString(time))
|
||||
}
|
||||
|
||||
pc.prev = widget.NewButtonWithIcon("", theme.MediaSkipPreviousIcon(), func() {})
|
||||
pc.next = widget.NewButtonWithIcon("", theme.MediaSkipNextIcon(), func() {})
|
||||
pc.playpause = widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {})
|
||||
|
||||
buttons := container.NewHBox(pc.prev, pc.playpause, pc.next)
|
||||
b := container.New(layout.NewCenterLayout(), buttons)
|
||||
|
||||
c := container.NewBorder(nil, nil, pc.curTimeLabel, pc.totalTimeLabel, pc.slider)
|
||||
pc.container = container.NewVBox(c, b)
|
||||
|
||||
return pc
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) OnSeek(f func(float64)) {
|
||||
pc.slider.OnDragEnd = f
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) OnSeekPrevious(f func()) {
|
||||
pc.prev.OnTapped = f
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) OnSeekNext(f func()) {
|
||||
pc.next.OnTapped = f
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) OnPlayPause(f func()) {
|
||||
pc.playpause.OnTapped = f
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) SetPlaying(playing bool) {
|
||||
if playing {
|
||||
pc.playpause.SetIcon(theme.MediaPauseIcon())
|
||||
} else {
|
||||
pc.playpause.SetIcon(theme.MediaPlayIcon())
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PlayerControls) UpdatePlayTime(curTime, totalTime float64) {
|
||||
// TODO: there is a bug with very long tracks (~20min) where the
|
||||
// curtime label will bounce back and forth +- 1sec (rounding issue?)
|
||||
pc.totalTime = totalTime
|
||||
v := 0.0
|
||||
if totalTime > 0 {
|
||||
v = curTime / totalTime
|
||||
}
|
||||
|
||||
updated := false
|
||||
tt := util.SecondsToTimeString(totalTime)
|
||||
if tt != pc.totalTimeLabel.Text {
|
||||
pc.totalTimeLabel.SetText(tt)
|
||||
updated = true
|
||||
}
|
||||
if !pc.slider.IsDragging() {
|
||||
ct := util.SecondsToTimeString(curTime)
|
||||
if ct != pc.curTimeLabel.Text {
|
||||
pc.curTimeLabel.SetText(ct)
|
||||
updated = true
|
||||
}
|
||||
if updated {
|
||||
// Only update slider once a second when time label changes
|
||||
pc.slider.SetValue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PlayerControls) CreateRenderer() fyne.WidgetRenderer {
|
||||
return widget.NewSimpleRenderer(p.container)
|
||||
}
|
||||
Reference in New Issue
Block a user