refactor UI for better separation

This commit is contained in:
Drew Weymouth
2022-12-24 16:47:00 -08:00
parent 5c644faeaf
commit cefb25133e
8 changed files with 99 additions and 79 deletions
+69
View File
@@ -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)
}
+124
View File
@@ -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)
}
+54
View File
@@ -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()
}
+153
View File
@@ -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)
}