249 lines
5.7 KiB
Go
249 lines
5.7 KiB
Go
package widget
|
|
|
|
import (
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/internal/widget"
|
|
)
|
|
|
|
// RadioGroup widget has a list of text labels and checks check icons next to each.
|
|
// Changing the selection (only one can be selected) will trigger the changed func.
|
|
//
|
|
// Since: 1.4
|
|
type RadioGroup struct {
|
|
DisableableWidget
|
|
Horizontal bool
|
|
Required bool
|
|
OnChanged func(string) `json:"-"`
|
|
Options []string
|
|
Selected string
|
|
|
|
// this index is ONE-BASED so the default zero-value is unselected
|
|
// use r.selectedIndex(), r.setSelectedIndex(int) to maniupulate this field
|
|
// as if it were a zero-based index (with -1 == nothing selected)
|
|
_selIdx int
|
|
}
|
|
|
|
var _ fyne.Widget = (*RadioGroup)(nil)
|
|
|
|
// NewRadioGroup creates a new radio group widget with the set options and change handler
|
|
//
|
|
// Since: 1.4
|
|
func NewRadioGroup(options []string, changed func(string)) *RadioGroup {
|
|
r := &RadioGroup{
|
|
Options: options,
|
|
OnChanged: changed,
|
|
}
|
|
r.ExtendBaseWidget(r)
|
|
return r
|
|
}
|
|
|
|
// Append adds a new option to the end of a RadioGroup widget.
|
|
func (r *RadioGroup) Append(option string) {
|
|
r.Options = append(r.Options, option)
|
|
|
|
r.Refresh()
|
|
}
|
|
|
|
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
|
func (r *RadioGroup) CreateRenderer() fyne.WidgetRenderer {
|
|
r.ExtendBaseWidget(r)
|
|
|
|
items := make([]fyne.CanvasObject, len(r.Options))
|
|
for i, option := range r.Options {
|
|
idx := i
|
|
items[idx] = newRadioItem(option, func(item *radioItem) {
|
|
r.itemTapped(item, idx)
|
|
})
|
|
}
|
|
|
|
render := &radioGroupRenderer{widget.NewBaseRenderer(items), items, r}
|
|
r.updateSelectedIndex()
|
|
render.updateItems(false)
|
|
return render
|
|
}
|
|
|
|
// MinSize returns the size that this widget should not shrink below
|
|
func (r *RadioGroup) MinSize() fyne.Size {
|
|
r.ExtendBaseWidget(r)
|
|
return r.BaseWidget.MinSize()
|
|
}
|
|
|
|
// SetSelected sets the radio option, it can be used to set a default option.
|
|
func (r *RadioGroup) SetSelected(option string) {
|
|
if r.Selected == option {
|
|
return
|
|
}
|
|
|
|
r.Selected = option
|
|
// selectedIndex will be updated on refresh to the first matching item
|
|
|
|
if r.OnChanged != nil {
|
|
r.OnChanged(option)
|
|
}
|
|
|
|
r.Refresh()
|
|
}
|
|
|
|
func (r *RadioGroup) itemTapped(item *radioItem, idx int) {
|
|
if r.Disabled() {
|
|
return
|
|
}
|
|
|
|
if r.selectedIndex() == idx {
|
|
if r.Required {
|
|
return
|
|
}
|
|
r.Selected = ""
|
|
r.setSelectedIndex(-1)
|
|
item.SetSelected(false)
|
|
} else {
|
|
r.Selected = item.Label
|
|
r.setSelectedIndex(idx)
|
|
item.SetSelected(true)
|
|
}
|
|
|
|
if r.OnChanged != nil {
|
|
r.OnChanged(r.Selected)
|
|
}
|
|
r.Refresh()
|
|
}
|
|
|
|
func (r *RadioGroup) Refresh() {
|
|
r.updateSelectedIndex()
|
|
r.BaseWidget.Refresh()
|
|
}
|
|
|
|
func (r *RadioGroup) selectedIndex() int {
|
|
return r._selIdx - 1
|
|
}
|
|
|
|
func (r *RadioGroup) setSelectedIndex(idx int) {
|
|
r._selIdx = idx + 1
|
|
}
|
|
|
|
// if selectedIndex does not match the public Selected property,
|
|
// set it to the index of the first radio item whose label matches Selected
|
|
func (r *RadioGroup) updateSelectedIndex() {
|
|
sel := r.Selected
|
|
sIdx := r.selectedIndex()
|
|
if sIdx >= 0 && sIdx < len(r.Options) && r.Options[sIdx] == sel {
|
|
return // selected index matches Selected
|
|
}
|
|
if sIdx == -1 && sel == "" {
|
|
return // nothing selected
|
|
}
|
|
|
|
sIdx = -1
|
|
for i, opt := range r.Options {
|
|
if sel == opt {
|
|
sIdx = i
|
|
break
|
|
}
|
|
}
|
|
r.setSelectedIndex(sIdx)
|
|
}
|
|
|
|
type radioGroupRenderer struct {
|
|
widget.BaseRenderer
|
|
|
|
// slice of *radioItem, but using fyne.CanvasObject as the type
|
|
// so we can directly set it to the BaseRenderer's objects slice
|
|
items []fyne.CanvasObject
|
|
radio *RadioGroup
|
|
}
|
|
|
|
// Layout the components of the radio widget
|
|
func (r *radioGroupRenderer) Layout(_ fyne.Size) {
|
|
count := 1
|
|
if len(r.items) > 0 {
|
|
count = len(r.items)
|
|
}
|
|
var itemHeight, itemWidth float32
|
|
minSize := r.radio.MinSize()
|
|
if r.radio.Horizontal {
|
|
itemHeight = minSize.Height
|
|
itemWidth = minSize.Width / float32(count)
|
|
} else {
|
|
itemHeight = minSize.Height / float32(count)
|
|
itemWidth = minSize.Width
|
|
}
|
|
|
|
itemSize := fyne.NewSize(itemWidth, itemHeight)
|
|
x, y := float32(0), float32(0)
|
|
for _, item := range r.items {
|
|
item.Resize(itemSize)
|
|
item.Move(fyne.NewPos(x, y))
|
|
if r.radio.Horizontal {
|
|
x += itemWidth
|
|
} else {
|
|
y += itemHeight
|
|
}
|
|
}
|
|
}
|
|
|
|
// MinSize calculates the minimum size of a radio item.
|
|
// This is based on the contained text, the radio icon and a standard amount of padding
|
|
// between each item.
|
|
func (r *radioGroupRenderer) MinSize() fyne.Size {
|
|
width := float32(0)
|
|
height := float32(0)
|
|
for _, item := range r.items {
|
|
itemMin := item.MinSize()
|
|
|
|
width = fyne.Max(width, itemMin.Width)
|
|
height = fyne.Max(height, itemMin.Height)
|
|
}
|
|
|
|
if r.radio.Horizontal {
|
|
width = width * float32(len(r.items))
|
|
} else {
|
|
height = height * float32(len(r.items))
|
|
}
|
|
|
|
return fyne.NewSize(width, height)
|
|
}
|
|
|
|
func (r *radioGroupRenderer) Refresh() {
|
|
r.updateItems(true)
|
|
canvas.Refresh(r.radio.super())
|
|
}
|
|
|
|
func (r *radioGroupRenderer) updateItems(refresh bool) {
|
|
if len(r.items) < len(r.radio.Options) {
|
|
for i := len(r.items); i < len(r.radio.Options); i++ {
|
|
idx := i
|
|
item := newRadioItem(r.radio.Options[idx], func(item *radioItem) {
|
|
r.radio.itemTapped(item, idx)
|
|
})
|
|
r.items = append(r.items, item)
|
|
}
|
|
r.Layout(r.radio.Size())
|
|
} else if len(r.items) > len(r.radio.Options) {
|
|
total := len(r.radio.Options)
|
|
r.items = r.items[:total]
|
|
}
|
|
r.SetObjects(r.items)
|
|
|
|
for i, item := range r.items {
|
|
item := item.(*radioItem)
|
|
changed := false
|
|
if l := r.radio.Options[i]; l != item.Label {
|
|
item.Label = r.radio.Options[i]
|
|
changed = true
|
|
}
|
|
if sel := i == r.radio.selectedIndex(); sel != item.Selected {
|
|
item.Selected = sel
|
|
changed = true
|
|
}
|
|
if d := r.radio.Disabled(); d != item.Disabled() {
|
|
item.disabled = d
|
|
changed = true
|
|
}
|
|
|
|
if refresh || changed {
|
|
item.Refresh()
|
|
}
|
|
}
|
|
}
|