add IconButton and use in bottom panel

This commit is contained in:
Drew Weymouth
2024-05-27 11:16:39 -07:00
parent 0a818ae7b5
commit 96fed465d7
6 changed files with 225 additions and 57 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ var ResUpdownarrowSvg = &fyne.StaticResource{
var ResDefaultToml = &fyne.StaticResource{ var ResDefaultToml = &fyne.StaticResource{
StaticName: "default.toml", StaticName: "default.toml",
StaticContent: []byte( StaticContent: []byte(
"[SupersonicTheme]\nName = \"Default\"\nVersion = \"0.2\"\nSupportsDark = true\nSupportsLight = true\n\n[DarkColors]\nPageBackground = \"#0F0F0F\"\nListHeader = \"#232323\"\nPageHeader = \"#181d25\"\nBackground = \"#232323\"\nScrollBar = \"#F3F3F3\"\nButton = \"#14141432\"\nInputBackground = \"#14141432\"\n\n[LightColors]\nPageBackground = \"#FAFAFA\"\nListHeader = \"#E1DFE1\"\nPageHeader = \"#e1dfe1\"\nBackground = \"#E1DFE1\"\nScrollBar = \"#565656\"\nButton = \"#C8C8C8F0\"\nDisabledButton = \"#CDCDCDF0\"\nForeground = \"#0A0A0A\"\nHyperlink = \"#3737FB\""), "[SupersonicTheme]\nName = \"Default\"\nVersion = \"0.2\"\nSupportsDark = true\nSupportsLight = true\n\n[DarkColors]\nPageBackground = \"#0F0F0F\"\nListHeader = \"#232323\"\nPageHeader = \"#181d25\"\nBackground = \"#232323\"\nScrollBar = \"#F3F3F3\"\nButton = \"#14141432\"\nForeground = \"#e6e6e6\"\nInputBackground = \"#14141432\"\n\n[LightColors]\nPageBackground = \"#FAFAFA\"\nListHeader = \"#E1DFE1\"\nPageHeader = \"#e1dfe1\"\nBackground = \"#E1DFE1\"\nScrollBar = \"#565656\"\nButton = \"#C8C8C8F0\"\nDisabledButton = \"#CDCDCDF0\"\nForeground = \"#262626\"\nHyperlink = \"#3737FB\""),
} }
var ResLICENSE = &fyne.StaticResource{ var ResLICENSE = &fyne.StaticResource{
StaticName: "LICENSE", StaticName: "LICENSE",
+2 -1
View File
@@ -11,6 +11,7 @@ PageHeader = "#181d25"
Background = "#232323" Background = "#232323"
ScrollBar = "#F3F3F3" ScrollBar = "#F3F3F3"
Button = "#14141432" Button = "#14141432"
Foreground = "#e6e6e6"
InputBackground = "#14141432" InputBackground = "#14141432"
[LightColors] [LightColors]
@@ -21,5 +22,5 @@ Background = "#E1DFE1"
ScrollBar = "#565656" ScrollBar = "#565656"
Button = "#C8C8C8F0" Button = "#C8C8C8F0"
DisabledButton = "#CDCDCDF0" DisabledButton = "#CDCDCDF0"
Foreground = "#0A0A0A" Foreground = "#262626"
Hyperlink = "#3737FB" Hyperlink = "#3737FB"
+55 -4
View File
@@ -19,10 +19,12 @@ import (
) )
const ( const (
ColorNameListHeader fyne.ThemeColorName = "ListHeader" ColorNameListHeader fyne.ThemeColorName = "ListHeader"
ColorNamePageBackground fyne.ThemeColorName = "PageBackground" ColorNamePageBackground fyne.ThemeColorName = "PageBackground"
ColorNamePageHeader fyne.ThemeColorName = "PageHeader" ColorNamePageHeader fyne.ThemeColorName = "PageHeader"
ColorNameInactiveLyric fyne.ThemeColorName = "InactiveLyric" ColorNameInactiveLyric fyne.ThemeColorName = "InactiveLyric"
ColorNameIconButton fyne.ThemeColorName = "IconButton"
ColorNameHoveredIconButton fyne.ThemeColorName = "HoveredIconButton"
) )
var ( var (
@@ -111,6 +113,17 @@ func (m *MyTheme) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Col
foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant) foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant)
disabled := colorOrDefault(colors.Disabled, defColors.Disabled, theme.ColorNameDisabled, variant) disabled := colorOrDefault(colors.Disabled, defColors.Disabled, theme.ColorNameDisabled, variant)
return blendColors(foreground, disabled, 0.33) return blendColors(foreground, disabled, 0.33)
case ColorNameHoveredIconButton:
if variant == theme.VariantDark {
return color.White
}
return color.Black
case ColorNameIconButton:
foreground := colorOrDefault(colors.Foreground, defColors.Foreground, theme.ColorNameForeground, variant)
if variant == theme.VariantDark {
return darkenColor(foreground, 0.05)
}
return brightenColor(foreground, 0.2)
case ColorNameListHeader: case ColorNameListHeader:
return colorOrDefault(colors.ListHeader, defColors.ListHeader, name, variant) return colorOrDefault(colors.ListHeader, defColors.ListHeader, name, variant)
case ColorNamePageBackground: case ColorNamePageBackground:
@@ -256,6 +269,44 @@ func blendColors(a, b color.Color, fractionA float64) color.Color {
return color.RGBA{R: rAvg, G: gAvg, B: bAvg, A: aAvg} return color.RGBA{R: rAvg, G: gAvg, B: bAvg, A: aAvg}
} }
func brightenColor(c color.Color, fraction float64) color.Color {
r, g, b, a := c.RGBA()
r, g, b = brightenComponent(r, fraction), brightenComponent(g, fraction), brightenComponent(b, fraction)
return color.RGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
}
func darkenColor(c color.Color, fraction float64) color.Color {
r, g, b, a := c.RGBA()
r, g, b = darkenComponent(r, fraction), darkenComponent(g, fraction), darkenComponent(b, fraction)
return color.RGBA{
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
}
func brightenComponent(component uint32, fraction float64) uint32 {
brightened := component + uint32(float64(component)*fraction)
if brightened > 0xffff {
brightened = 0xffff
}
return brightened
}
func darkenComponent(component uint32, fraction float64) uint32 {
i := uint32(float64(component) * fraction)
if i > component {
return 0
}
return component - i
}
func readTTFFile(filepath string) ([]byte, error) { func readTTFFile(filepath string) ([]byte, error) {
if !strings.HasSuffix(filepath, ".ttf") { if !strings.HasSuffix(filepath, ".ttf") {
err := errors.New("only .ttf fonts are supported") err := errors.New("only .ttf fonts are supported")
+16 -42
View File
@@ -12,45 +12,23 @@ import (
"github.com/dweymouth/supersonic/ui/util" "github.com/dweymouth/supersonic/ui/util"
) )
var (
repeatThemedResource = theme.NewThemedResource(myTheme.RepeatIcon)
repeatOneThemedResource = theme.NewThemedResource(myTheme.RepeatOneIcon)
)
// The "aux" controls for playback, positioned to the right // The "aux" controls for playback, positioned to the right
// of the BottomPanel. Currently only volume control. // of the BottomPanel. Currently only volume control.
type AuxControls struct { type AuxControls struct {
widget.BaseWidget widget.BaseWidget
VolumeControl *VolumeControl VolumeControl *VolumeControl
loop *miniButton loop *IconButton
container *fyne.Container container *fyne.Container
} }
type miniButton struct {
widget.Button
}
func newMiniButton(icon fyne.Resource) *miniButton {
b := &miniButton{
Button: widget.Button{
Icon: icon,
},
}
b.ExtendBaseWidget(b)
return b
}
func (b *miniButton) MinSize() fyne.Size {
return fyne.NewSize(24, 24)
}
func NewAuxControls(initialVolume int) *AuxControls { func NewAuxControls(initialVolume int) *AuxControls {
a := &AuxControls{ a := &AuxControls{
VolumeControl: NewVolumeControl(initialVolume), VolumeControl: NewVolumeControl(initialVolume),
loop: newMiniButton(myTheme.RepeatIcon), loop: NewIconButton(myTheme.RepeatIcon, nil),
} }
a.loop.IconSize = IconButtonSizeSmaller
a.container = container.NewHBox( a.container = container.NewHBox(
layout.NewSpacer(), layout.NewSpacer(),
container.NewVBox( container.NewVBox(
@@ -75,16 +53,15 @@ func (a *AuxControls) OnChangeLoopMode(f func()) {
func (a *AuxControls) SetLoopMode(mode backend.LoopMode) { func (a *AuxControls) SetLoopMode(mode backend.LoopMode) {
switch mode { switch mode {
case backend.LoopAll: case backend.LoopAll:
a.loop.Icon = repeatThemedResource a.loop.Highlighted = true
repeatThemedResource.ColorName = theme.ColorNamePrimary a.loop.SetIcon(myTheme.RepeatIcon)
case backend.LoopOne: case backend.LoopOne:
a.loop.Icon = repeatOneThemedResource a.loop.Highlighted = true
repeatOneThemedResource.ColorName = theme.ColorNamePrimary a.loop.SetIcon(myTheme.RepeatOneIcon)
case backend.LoopNone: case backend.LoopNone:
a.loop.Importance = widget.MediumImportance a.loop.Highlighted = false
a.loop.Icon = myTheme.RepeatIcon a.loop.SetIcon(myTheme.RepeatIcon)
} }
a.loop.Refresh()
} }
type volumeSlider struct { type volumeSlider struct {
@@ -114,9 +91,7 @@ func (v *volumeSlider) Tapped(e *fyne.PointEvent) {
} }
func (v *volumeSlider) MinSize() fyne.Size { func (v *volumeSlider) MinSize() fyne.Size {
h := v.Slider.MinSize().Height return fyne.NewSize(v.Width, v.Slider.MinSize().Height)
// give extra space for height to hack around mini button covering part of focus circle
return fyne.NewSize(v.Width, h+theme.Padding()*3)
} }
func (v *volumeSlider) Scrolled(e *fyne.ScrollEvent) { func (v *volumeSlider) Scrolled(e *fyne.ScrollEvent) {
@@ -126,7 +101,7 @@ func (v *volumeSlider) Scrolled(e *fyne.ScrollEvent) {
type VolumeControl struct { type VolumeControl struct {
widget.BaseWidget widget.BaseWidget
icon *TappableIcon icon *IconButton
slider *volumeSlider slider *volumeSlider
OnSetVolume func(int) OnSetVolume func(int)
@@ -140,8 +115,8 @@ type VolumeControl struct {
func NewVolumeControl(initialVol int) *VolumeControl { func NewVolumeControl(initialVol int) *VolumeControl {
v := &VolumeControl{} v := &VolumeControl{}
v.ExtendBaseWidget(v) v.ExtendBaseWidget(v)
v.icon = NewTappableIcon(theme.VolumeUpIcon()) v.icon = NewIconButton(theme.VolumeUpIcon(), v.toggleMute)
v.icon.OnTapped = v.toggleMute v.icon.IconSize = IconButtonSizeSmaller
v.slider = NewVolumeSlider(100) v.slider = NewVolumeSlider(100)
v.lastVol = initialVol v.lastVol = initialVol
v.slider.Step = 1 v.slider.Step = 1
@@ -203,11 +178,10 @@ func (v *VolumeControl) invokeOnVolumeChange(vol int) {
func (v *VolumeControl) updateIconForVolume(vol int) { func (v *VolumeControl) updateIconForVolume(vol int) {
if vol <= 0 { if vol <= 0 {
v.icon.Resource = theme.VolumeMuteIcon() v.icon.SetIcon(theme.VolumeMuteIcon())
} else if vol < 50 { } else if vol < 50 {
v.icon.Resource = theme.VolumeDownIcon() v.icon.SetIcon(theme.VolumeDownIcon())
} else { } else {
v.icon.Resource = theme.VolumeUpIcon() v.icon.SetIcon(theme.VolumeUpIcon())
} }
v.icon.Refresh()
} }
+142
View File
@@ -0,0 +1,142 @@
package widgets
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
myTheme "github.com/dweymouth/supersonic/ui/theme"
)
type IconButtonSize int
const (
IconButtonSizeNormal IconButtonSize = iota
IconButtonSizeBigger
IconButtonSizeSmaller
)
type IconButton struct {
widget.BaseWidget
Highlighted bool
IconSize IconButtonSize
OnTapped func()
icon fyne.Resource
focused bool
hovered bool
themed *theme.ThemedResource
img *canvas.Image
}
var (
_ fyne.Tappable = (*IconButton)(nil)
_ fyne.Focusable = (*IconButton)(nil)
_ desktop.Hoverable = (*IconButton)(nil)
)
func NewIconButton(icon fyne.Resource, onTapped func()) *IconButton {
i := &IconButton{icon: icon, OnTapped: onTapped}
i.ExtendBaseWidget(i)
return i
}
func (i *IconButton) SetIcon(icon fyne.Resource) {
i.icon = icon
if i.img != nil {
i.themed = theme.NewThemedResource(icon)
i.img.Resource = i.themed
i.Refresh()
}
}
func (i *IconButton) Tapped(*fyne.PointEvent) {
if i.OnTapped != nil {
i.OnTapped()
}
}
func (i *IconButton) FocusGained() {
if !i.focused {
defer i.Refresh()
}
i.focused = true
}
func (i *IconButton) FocusLost() {
if i.focused {
defer i.Refresh()
}
i.focused = false
}
func (i *IconButton) TypedKey(e *fyne.KeyEvent) {
if e.Name == fyne.KeySpace {
i.Tapped(nil)
}
}
func (i *IconButton) TypedRune(r rune) {
}
func (i *IconButton) MouseIn(*desktop.MouseEvent) {
if !i.hovered {
defer i.Refresh()
}
i.hovered = true
}
func (i *IconButton) MouseOut() {
if i.hovered {
defer i.Refresh()
}
i.hovered = false
}
func (i *IconButton) MouseMoved(*desktop.MouseEvent) {
}
func (i *IconButton) MinSize() fyne.Size {
return i.iconSize()
}
func (i *IconButton) iconSize() fyne.Size {
switch i.IconSize {
case IconButtonSizeBigger:
return fyne.NewSquareSize(theme.IconInlineSize() * 2)
case IconButtonSizeSmaller:
return fyne.NewSquareSize(theme.IconInlineSize())
default:
return fyne.NewSquareSize(theme.IconInlineSize() * 1.3333)
}
}
func (i *IconButton) Refresh() {
if i.img == nil {
return
}
if i.Highlighted || i.focused {
i.themed.ColorName = theme.ColorNamePrimary
} else if i.hovered {
i.themed.ColorName = myTheme.ColorNameHoveredIconButton
} else {
i.themed.ColorName = myTheme.ColorNameIconButton
}
i.img.SetMinSize(i.iconSize())
i.img.Refresh()
}
func (i *IconButton) CreateRenderer() fyne.WidgetRenderer {
if i.img == nil {
i.themed = theme.NewThemedResource(i.icon)
i.img = canvas.NewImageFromResource(i.themed)
i.img.FillMode = canvas.ImageFillContain
i.img.SetMinSize(i.iconSize())
}
return widget.NewSimpleRenderer(container.NewCenter(i.img))
}
+9 -9
View File
@@ -83,9 +83,9 @@ type PlayerControls struct {
slider *TrackPosSlider slider *TrackPosSlider
curTimeLabel *labelMinSize curTimeLabel *labelMinSize
totalTimeLabel *labelMinSize totalTimeLabel *labelMinSize
prev *widget.Button prev *IconButton
playpause *widget.Button playpause *IconButton
next *widget.Button next *IconButton
container *fyne.Container container *fyne.Container
totalTime float64 totalTime float64
@@ -127,15 +127,15 @@ func NewPlayerControls() *PlayerControls {
} }
} }
pc.prev = widget.NewButtonWithIcon("", theme.MediaSkipPreviousIcon(), func() {}) pc.prev = NewIconButton(theme.MediaSkipPreviousIcon(), func() {})
pc.next = widget.NewButtonWithIcon("", theme.MediaSkipNextIcon(), func() {}) pc.next = NewIconButton(theme.MediaSkipNextIcon(), func() {})
pc.playpause = widget.NewButtonWithIcon("", theme.MediaPlayIcon(), func() {}) pc.playpause = NewIconButton(theme.MediaPlayIcon(), func() {})
pc.playpause.IconSize = IconButtonSizeBigger
buttons := container.NewHBox(pc.prev, pc.playpause, pc.next) buttons := container.NewHBox(layout.NewSpacer(), pc.prev, pc.playpause, pc.next, layout.NewSpacer())
b := container.New(layout.NewCenterLayout(), buttons)
c := container.NewBorder(nil, nil, pc.curTimeLabel, pc.totalTimeLabel, pc.slider) c := container.NewBorder(nil, nil, pc.curTimeLabel, pc.totalTimeLabel, pc.slider)
pc.container = container.NewVBox(c, b) pc.container = container.New(layout.NewCustomPaddedVBoxLayout(0), c, buttons)
return pc return pc
} }