add equalizer UI in settings dialog
This commit is contained in:
@@ -185,6 +185,12 @@ func (a *App) setupMPV() error {
|
|||||||
})
|
})
|
||||||
a.Player.SetAudioExclusive(a.Config.LocalPlayback.AudioExclusive)
|
a.Player.SetAudioExclusive(a.Config.LocalPlayback.AudioExclusive)
|
||||||
|
|
||||||
|
eq := &player.ISO15BandEqualizer{
|
||||||
|
Disabled: !a.Config.LocalPlayback.EqualizerEnabled,
|
||||||
|
}
|
||||||
|
copy(eq.BandGains[:], a.Config.LocalPlayback.GraphicEqualizerBands)
|
||||||
|
a.Player.SetEqualizer(eq)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-8
@@ -70,10 +70,12 @@ type TracksPageConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LocalPlaybackConfig struct {
|
type LocalPlaybackConfig struct {
|
||||||
AudioDeviceName string
|
AudioDeviceName string
|
||||||
AudioExclusive bool
|
AudioExclusive bool
|
||||||
InMemoryCacheSizeMB int
|
InMemoryCacheSizeMB int
|
||||||
Volume int
|
Volume int
|
||||||
|
EqualizerEnabled bool
|
||||||
|
GraphicEqualizerBands []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScrobbleConfig struct {
|
type ScrobbleConfig struct {
|
||||||
@@ -151,10 +153,12 @@ func DefaultConfig(appVersionTag string) *Config {
|
|||||||
},
|
},
|
||||||
LocalPlayback: LocalPlaybackConfig{
|
LocalPlayback: LocalPlaybackConfig{
|
||||||
// "auto" is the name to pass to MPV for autoselecting the output device
|
// "auto" is the name to pass to MPV for autoselecting the output device
|
||||||
AudioDeviceName: "auto",
|
AudioDeviceName: "auto",
|
||||||
AudioExclusive: false,
|
AudioExclusive: false,
|
||||||
InMemoryCacheSizeMB: 30,
|
InMemoryCacheSizeMB: 30,
|
||||||
Volume: 100,
|
Volume: 100,
|
||||||
|
EqualizerEnabled: false,
|
||||||
|
GraphicEqualizerBands: make([]float64, 15),
|
||||||
},
|
},
|
||||||
Scrobbling: ScrobbleConfig{
|
Scrobbling: ScrobbleConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
|||||||
+11
-4
@@ -78,16 +78,23 @@ type EqualizerCurve []EqualizerBand
|
|||||||
|
|
||||||
func (e EqualizerCurve) String() string {
|
func (e EqualizerCurve) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for i, band := range e {
|
first := true
|
||||||
if i > 0 {
|
for _, band := range e {
|
||||||
sb.WriteString(",")
|
if s := band.String(); s != "" {
|
||||||
|
if !first {
|
||||||
|
sb.WriteString(",")
|
||||||
|
}
|
||||||
|
sb.WriteString(s)
|
||||||
|
first = false
|
||||||
}
|
}
|
||||||
sb.WriteString(band.String())
|
|
||||||
}
|
}
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e EqualizerBand) String() string {
|
func (e EqualizerBand) String() string {
|
||||||
|
if math.Abs(e.Gain) < 0.02 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return fmt.Sprintf("equalizer=f=%d:g=%0.2f:t=%s:w=%0.2f",
|
return fmt.Sprintf("equalizer=f=%d:g=%0.2f:t=%s:w=%0.2f",
|
||||||
e.Frequency, e.Gain, e.WidthType.String(), e.Width)
|
e.Frequency, e.Gain, e.WidthType.String(), e.Width)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -510,6 +510,10 @@ func (p *Player) SetEqualizer(eq Equalizer) error {
|
|||||||
return p.mpv.SetPropertyString("af", eq.Curve().String())
|
return p.mpv.SetPropertyString("af", eq.Curve().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Player) Equalizer() Equalizer {
|
||||||
|
return p.equalizer
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Player) GetMediaInfo() (MediaInfo, error) {
|
func (p *Player) GetMediaInfo() (MediaInfo, error) {
|
||||||
var info MediaInfo
|
var info MediaInfo
|
||||||
n, err := p.mpv.GetProperty("audio-params", mpv.FORMAT_NODE)
|
n, err := p.mpv.GetProperty("audio-params", mpv.FORMAT_NODE)
|
||||||
|
|||||||
@@ -448,7 +448,8 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map
|
|||||||
devs = []player.AudioDevice{{Name: "auto", Description: "Autoselect device"}}
|
devs = []player.AudioDevice{{Name: "auto", Description: "Autoselect device"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
dlg := dialogs.NewSettingsDialog(c.App.Config, devs, themeFiles, c.MainWindow)
|
bands := c.App.Player.Equalizer().BandFrequencies()
|
||||||
|
dlg := dialogs.NewSettingsDialog(c.App.Config, devs, themeFiles, bands, c.MainWindow)
|
||||||
dlg.OnReplayGainSettingsChanged = func() {
|
dlg.OnReplayGainSettingsChanged = func() {
|
||||||
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
||||||
}
|
}
|
||||||
@@ -459,6 +460,13 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map
|
|||||||
c.App.Player.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName)
|
c.App.Player.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName)
|
||||||
}
|
}
|
||||||
dlg.OnThemeSettingChanged = themeUpdateCallbk
|
dlg.OnThemeSettingChanged = themeUpdateCallbk
|
||||||
|
dlg.OnEqualizerSettingsChanged = func() {
|
||||||
|
// currently we only have one equalizer type
|
||||||
|
eq := c.App.Player.Equalizer().(*player.ISO15BandEqualizer)
|
||||||
|
eq.Disabled = !c.App.Config.LocalPlayback.EqualizerEnabled
|
||||||
|
copy(eq.BandGains[:], c.App.Config.LocalPlayback.GraphicEqualizerBands)
|
||||||
|
c.App.Player.SetEqualizer(eq)
|
||||||
|
}
|
||||||
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
|
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
|
||||||
dlg.OnDismiss = func() {
|
dlg.OnDismiss = func() {
|
||||||
pop.Hide()
|
pop.Hide()
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package dialogs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
"github.com/dweymouth/supersonic/ui/layouts"
|
||||||
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GraphicEqualizer struct {
|
||||||
|
widget.BaseWidget
|
||||||
|
|
||||||
|
OnChanged func(band int, gain float64)
|
||||||
|
|
||||||
|
container *fyne.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphicEqualizer(bandFreqs []string, bandGains []float64) *GraphicEqualizer {
|
||||||
|
g := &GraphicEqualizer{}
|
||||||
|
g.ExtendBaseWidget(g)
|
||||||
|
g.buildSliders(bandFreqs, bandGains)
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GraphicEqualizer) buildSliders(bands []string, bandGains []float64) {
|
||||||
|
rng := container.NewVBox(
|
||||||
|
newCaptionTextSizeLabel("+12", fyne.TextAlignTrailing),
|
||||||
|
layout.NewSpacer(),
|
||||||
|
newCaptionTextSizeLabel("0", fyne.TextAlignTrailing),
|
||||||
|
layout.NewSpacer(),
|
||||||
|
newCaptionTextSizeLabel("-12", fyne.TextAlignTrailing),
|
||||||
|
)
|
||||||
|
bandSliders := container.New(layouts.NewGridLayoutWithColumnsAndPadding(16, -16))
|
||||||
|
bandSliders.Add(container.NewBorder(nil, widget.NewLabel(""), nil, nil, rng))
|
||||||
|
for i, band := range bands {
|
||||||
|
s := newEQSlider()
|
||||||
|
if i < len(bandGains) {
|
||||||
|
s.SetValue(bandGains[i])
|
||||||
|
}
|
||||||
|
s.OnChanged = func(i int) func(float64) {
|
||||||
|
return func(f float64) {
|
||||||
|
if g.OnChanged != nil {
|
||||||
|
g.OnChanged(i, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
l := newCaptionTextSizeLabel(band, fyne.TextAlignCenter)
|
||||||
|
c := container.NewBorder(nil, l, nil, nil, s)
|
||||||
|
bandSliders.Add(c)
|
||||||
|
}
|
||||||
|
g.container = container.NewMax(
|
||||||
|
container.NewBorder(nil, widget.NewLabel(""), nil, nil,
|
||||||
|
container.NewBorder(nil, nil, util.NewHSpace(35), util.NewHSpace(5),
|
||||||
|
container.NewVBox(
|
||||||
|
layout.NewSpacer(),
|
||||||
|
myTheme.NewThemedRectangle(theme.ColorNameInputBackground),
|
||||||
|
layout.NewSpacer(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bandSliders,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCaptionTextSizeLabel(text string, alignment fyne.TextAlign) *widget.RichText {
|
||||||
|
l := widget.NewRichTextWithText(text)
|
||||||
|
ts := l.Segments[0].(*widget.TextSegment)
|
||||||
|
ts.Style.SizeName = theme.SizeNameCaptionText
|
||||||
|
ts.Style.Alignment = alignment
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GraphicEqualizer) CreateRenderer() fyne.WidgetRenderer {
|
||||||
|
return widget.NewSimpleRenderer(g.container)
|
||||||
|
}
|
||||||
|
|
||||||
|
type eqSlider struct {
|
||||||
|
widget.Slider
|
||||||
|
tappedAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEQSlider() *eqSlider {
|
||||||
|
s := &eqSlider{
|
||||||
|
Slider: widget.Slider{
|
||||||
|
Orientation: widget.Vertical,
|
||||||
|
Min: -12,
|
||||||
|
Max: 12,
|
||||||
|
Step: 0.1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
s.ExtendBaseWidget(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// We implement our own double tapping so that the Tapped behavior
|
||||||
|
// can be triggered instantly.
|
||||||
|
func (s *eqSlider) DoubleTapped(e *fyne.PointEvent) {
|
||||||
|
s.SetValue(0)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
@@ -35,6 +36,7 @@ type SettingsDialog struct {
|
|||||||
OnAudioDeviceSettingChanged func()
|
OnAudioDeviceSettingChanged func()
|
||||||
OnThemeSettingChanged func()
|
OnThemeSettingChanged func()
|
||||||
OnDismiss func()
|
OnDismiss func()
|
||||||
|
OnEqualizerSettingsChanged func()
|
||||||
|
|
||||||
config *backend.Config
|
config *backend.Config
|
||||||
audioDevices []player.AudioDevice
|
audioDevices []player.AudioDevice
|
||||||
@@ -45,13 +47,20 @@ type SettingsDialog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: having this depend on the player package for the AudioDevice type is kinda gross. Refactor.
|
// TODO: having this depend on the player package for the AudioDevice type is kinda gross. Refactor.
|
||||||
func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDevice, themeFileList map[string]string, window fyne.Window) *SettingsDialog {
|
func NewSettingsDialog(
|
||||||
|
config *backend.Config,
|
||||||
|
audioDeviceList []player.AudioDevice,
|
||||||
|
themeFileList map[string]string,
|
||||||
|
equalizerBands []string,
|
||||||
|
window fyne.Window,
|
||||||
|
) *SettingsDialog {
|
||||||
s := &SettingsDialog{config: config, audioDevices: audioDeviceList, themeFiles: themeFileList}
|
s := &SettingsDialog{config: config, audioDevices: audioDeviceList, themeFiles: themeFileList}
|
||||||
s.ExtendBaseWidget(s)
|
s.ExtendBaseWidget(s)
|
||||||
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
s.createGeneralTab(),
|
s.createGeneralTab(),
|
||||||
s.createPlaybackTab(),
|
s.createPlaybackTab(),
|
||||||
|
s.createEqualizerTab(equalizerBands),
|
||||||
s.createExperimentalTab(window),
|
s.createExperimentalTab(window),
|
||||||
)
|
)
|
||||||
// workaround issue where inactivated tabs don't fully update when theme setting is changed
|
// workaround issue where inactivated tabs don't fully update when theme setting is changed
|
||||||
@@ -320,6 +329,28 @@ func (s *SettingsDialog) createPlaybackTab() *container.TabItem {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingsDialog) createEqualizerTab(eqBands []string) *container.TabItem {
|
||||||
|
enabled := widget.NewCheck("Enabled", func(b bool) {
|
||||||
|
s.config.LocalPlayback.EqualizerEnabled = b
|
||||||
|
if s.OnEqualizerSettingsChanged != nil {
|
||||||
|
s.OnEqualizerSettingsChanged()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
enabled.Checked = s.config.LocalPlayback.EqualizerEnabled
|
||||||
|
geq := NewGraphicEqualizer(eqBands, s.config.LocalPlayback.GraphicEqualizerBands) // TODO: This should probably be an argument?
|
||||||
|
debouncer := util.NewDebouncer(200*time.Millisecond, func() {
|
||||||
|
if s.OnEqualizerSettingsChanged != nil {
|
||||||
|
s.OnEqualizerSettingsChanged()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
geq.OnChanged = func(b int, g float64) {
|
||||||
|
s.config.LocalPlayback.GraphicEqualizerBands[b] = g
|
||||||
|
debouncer()
|
||||||
|
}
|
||||||
|
cont := container.NewBorder(container.NewHBox(enabled), nil, nil, nil, geq)
|
||||||
|
return container.NewTabItem("Equalizer", cont)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.TabItem {
|
func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.TabItem {
|
||||||
warningLabel := widget.NewLabel("WARNING: these settings are experimental and may " +
|
warningLabel := widget.NewLabel("WARNING: these settings are experimental and may " +
|
||||||
"make the application buggy or increase system resource use. " +
|
"make the application buggy or increase system resource use. " +
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package layouts
|
||||||
|
|
||||||
|
// Forked from fyne.io/fyne/v2/layout/gridlayout.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Declare conformity with Layout interface
|
||||||
|
var _ fyne.Layout = (*gridLayout)(nil)
|
||||||
|
|
||||||
|
type gridLayout struct {
|
||||||
|
Cols int
|
||||||
|
Padding float32
|
||||||
|
vertical, adapt bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAdaptiveGridLayout returns a new grid layout which uses columns when horizontal but rows when vertical.
|
||||||
|
func NewAdaptiveGridLayout(rowcols int) fyne.Layout {
|
||||||
|
return &gridLayout{Cols: rowcols, adapt: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGridLayout returns a grid layout arranged in a specified number of columns.
|
||||||
|
// The number of rows will depend on how many children are in the container that uses this layout.
|
||||||
|
func NewGridLayout(cols int) fyne.Layout {
|
||||||
|
return NewGridLayoutWithColumns(cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGridLayoutWithColumns returns a new grid layout that specifies a column count and wrap to new rows when needed.
|
||||||
|
func NewGridLayoutWithColumns(cols int) fyne.Layout {
|
||||||
|
return &gridLayout{Cols: cols}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new rows as required.
|
||||||
|
func NewGridLayoutWithRows(rows int) fyne.Layout {
|
||||||
|
return &gridLayout{Cols: rows, vertical: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGridLayoutWithColumnsAndPadding(cols int, padding float32) fyne.Layout {
|
||||||
|
return &gridLayout{Cols: cols, Padding: padding}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gridLayout) horizontal() bool {
|
||||||
|
if g.adapt {
|
||||||
|
return fyne.IsHorizontal(fyne.CurrentDevice().Orientation())
|
||||||
|
}
|
||||||
|
|
||||||
|
return !g.vertical
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gridLayout) countRows(objects []fyne.CanvasObject) int {
|
||||||
|
if g.Cols < 1 {
|
||||||
|
g.Cols = 1
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
for _, child := range objects {
|
||||||
|
if child.Visible() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(math.Ceil(float64(count) / float64(g.Cols)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the leading (top or left) edge of a grid cell.
|
||||||
|
// size is the ideal cell size and the offset is which col or row its on.
|
||||||
|
func getLeading(size float64, padding float32, offset int) float32 {
|
||||||
|
ret := (size + float64(padding)) * float64(offset)
|
||||||
|
|
||||||
|
return float32(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the trailing (bottom or right) edge of a grid cell.
|
||||||
|
// size is the ideal cell size and the offset is which col or row its on.
|
||||||
|
func getTrailing(size float64, padding float32, offset int) float32 {
|
||||||
|
return getLeading(size, padding, offset+1) - padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout is called to pack all child objects into a specified size.
|
||||||
|
// For a GridLayout this will pack objects into a table format with the number
|
||||||
|
// of columns specified in our constructor.
|
||||||
|
func (g *gridLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
||||||
|
rows := g.countRows(objects)
|
||||||
|
|
||||||
|
padding := theme.Padding() + g.Padding
|
||||||
|
padWidth := float32(g.Cols-1) * padding
|
||||||
|
padHeight := float32(rows-1) * padding
|
||||||
|
cellWidth := float64(size.Width-padWidth) / float64(g.Cols)
|
||||||
|
cellHeight := float64(size.Height-padHeight) / float64(rows)
|
||||||
|
|
||||||
|
if !g.horizontal() {
|
||||||
|
padWidth, padHeight = padHeight, padWidth
|
||||||
|
cellWidth = float64(size.Width-padWidth) / float64(rows)
|
||||||
|
cellHeight = float64(size.Height-padHeight) / float64(g.Cols)
|
||||||
|
}
|
||||||
|
|
||||||
|
row, col := 0, 0
|
||||||
|
i := 0
|
||||||
|
for _, child := range objects {
|
||||||
|
if !child.Visible() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
x1 := getLeading(cellWidth, padding, col)
|
||||||
|
y1 := getLeading(cellHeight, padding, row)
|
||||||
|
x2 := getTrailing(cellWidth, padding, col)
|
||||||
|
y2 := getTrailing(cellHeight, padding, row)
|
||||||
|
|
||||||
|
child.Move(fyne.NewPos(x1, y1))
|
||||||
|
child.Resize(fyne.NewSize(x2-x1, y2-y1))
|
||||||
|
|
||||||
|
if g.horizontal() {
|
||||||
|
if (i+1)%g.Cols == 0 {
|
||||||
|
row++
|
||||||
|
col = 0
|
||||||
|
} else {
|
||||||
|
col++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (i+1)%g.Cols == 0 {
|
||||||
|
col++
|
||||||
|
row = 0
|
||||||
|
} else {
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinSize finds the smallest size that satisfies all the child objects.
|
||||||
|
// For a GridLayout this is the size of the largest child object multiplied by
|
||||||
|
// the required number of columns and rows, with appropriate padding between
|
||||||
|
// children.
|
||||||
|
func (g *gridLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
|
||||||
|
rows := g.countRows(objects)
|
||||||
|
minSize := fyne.NewSize(0, 0)
|
||||||
|
for _, child := range objects {
|
||||||
|
if !child.Visible() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
minSize = minSize.Max(child.MinSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := theme.Padding() + g.Padding
|
||||||
|
if g.horizontal() {
|
||||||
|
minContentSize := fyne.NewSize(minSize.Width*float32(g.Cols), minSize.Height*float32(rows))
|
||||||
|
return minContentSize.Add(fyne.NewSize(padding*fyne.Max(float32(g.Cols-1), 0), padding*fyne.Max(float32(rows-1), 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
minContentSize := fyne.NewSize(minSize.Width*float32(rows), minSize.Height*float32(g.Cols))
|
||||||
|
return minContentSize.Add(fyne.NewSize(padding*fyne.Max(float32(rows-1), 0), padding*fyne.Max(float32(g.Cols-1), 0)))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user