add IconButton and use in bottom panel
This commit is contained in:
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user