268 lines
5.9 KiB
Go
268 lines
5.9 KiB
Go
package widget
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/internal/widget"
|
|
)
|
|
|
|
// CheckGroup widget has a list of text labels and checkbox icons next to each.
|
|
// Changing the selection (any number can be selected) will trigger the changed func.
|
|
//
|
|
// Since: 2.1
|
|
type CheckGroup struct {
|
|
DisableableWidget
|
|
Horizontal bool
|
|
Required bool
|
|
OnChanged func([]string) `json:"-"`
|
|
Options []string
|
|
Selected []string
|
|
|
|
items []*Check
|
|
}
|
|
|
|
var _ fyne.Widget = (*CheckGroup)(nil)
|
|
|
|
// NewCheckGroup creates a new check group widget with the set options and change handler
|
|
//
|
|
// Since: 2.1
|
|
func NewCheckGroup(options []string, changed func([]string)) *CheckGroup {
|
|
r := &CheckGroup{
|
|
Options: options,
|
|
OnChanged: changed,
|
|
}
|
|
r.ExtendBaseWidget(r)
|
|
r.update()
|
|
return r
|
|
}
|
|
|
|
// Append adds a new option to the end of a CheckGroup widget.
|
|
func (r *CheckGroup) 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 *CheckGroup) CreateRenderer() fyne.WidgetRenderer {
|
|
r.ExtendBaseWidget(r)
|
|
r.propertyLock.Lock()
|
|
defer r.propertyLock.Unlock()
|
|
|
|
r.update()
|
|
objects := make([]fyne.CanvasObject, len(r.items))
|
|
for i, item := range r.items {
|
|
objects[i] = item
|
|
}
|
|
|
|
return &checkGroupRenderer{widget.NewBaseRenderer(objects), r.items, r}
|
|
}
|
|
|
|
// MinSize returns the size that this widget should not shrink below
|
|
func (r *CheckGroup) MinSize() fyne.Size {
|
|
r.ExtendBaseWidget(r)
|
|
return r.BaseWidget.MinSize()
|
|
}
|
|
|
|
// Refresh causes this widget to be redrawn in it's current state.
|
|
//
|
|
// Implements: fyne.CanvasObject
|
|
func (r *CheckGroup) Refresh() {
|
|
r.propertyLock.Lock()
|
|
r.update()
|
|
r.propertyLock.Unlock()
|
|
r.BaseWidget.Refresh()
|
|
}
|
|
|
|
// Remove removes the first occurrence of the specified option found from a CheckGroup widget.
|
|
// Return true if an option was removed.
|
|
//
|
|
// Since: 2.3
|
|
func (r *CheckGroup) Remove(option string) bool {
|
|
for i, o := range r.Options {
|
|
if strings.EqualFold(option, o) {
|
|
r.Options = append(r.Options[:i], r.Options[i+1:]...)
|
|
for j, s := range r.Selected {
|
|
if strings.EqualFold(option, s) {
|
|
r.Selected = append(r.Selected[:j], r.Selected[j+1:]...)
|
|
break
|
|
}
|
|
}
|
|
r.Refresh()
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SetSelected sets the checked options, it can be used to set a default option.
|
|
func (r *CheckGroup) SetSelected(options []string) {
|
|
//if r.Selected == options {
|
|
// return
|
|
//}
|
|
|
|
r.Selected = options
|
|
|
|
if r.OnChanged != nil {
|
|
r.OnChanged(options)
|
|
}
|
|
|
|
r.Refresh()
|
|
}
|
|
|
|
func (r *CheckGroup) itemTapped(item *Check) {
|
|
if r.Disabled() {
|
|
return
|
|
}
|
|
|
|
contains := false
|
|
for i, s := range r.Selected {
|
|
if s == item.Text {
|
|
contains = true
|
|
if len(r.Selected) <= 1 {
|
|
if r.Required {
|
|
item.SetChecked(true)
|
|
return
|
|
}
|
|
r.Selected = nil
|
|
} else {
|
|
r.Selected = append(r.Selected[:i], r.Selected[i+1:]...)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if !contains {
|
|
r.Selected = append(r.Selected, item.Text)
|
|
}
|
|
|
|
if r.OnChanged != nil {
|
|
r.OnChanged(r.Selected)
|
|
}
|
|
r.Refresh()
|
|
}
|
|
|
|
func (r *CheckGroup) update() {
|
|
r.Options = removeDuplicates(r.Options)
|
|
if len(r.items) < len(r.Options) {
|
|
for i := len(r.items); i < len(r.Options); i++ {
|
|
var item *Check
|
|
item = NewCheck(r.Options[i], func(bool) {
|
|
r.itemTapped(item)
|
|
})
|
|
r.items = append(r.items, item)
|
|
}
|
|
} else if len(r.items) > len(r.Options) {
|
|
r.items = r.items[:len(r.Options)]
|
|
}
|
|
for i, item := range r.items {
|
|
contains := false
|
|
for _, s := range r.Selected {
|
|
if s == item.Text {
|
|
contains = true
|
|
break
|
|
}
|
|
}
|
|
|
|
item.Text = r.Options[i]
|
|
item.Checked = contains
|
|
item.DisableableWidget.disabled = r.disabled
|
|
item.Refresh()
|
|
}
|
|
}
|
|
|
|
type checkGroupRenderer struct {
|
|
widget.BaseRenderer
|
|
items []*Check
|
|
checks *CheckGroup
|
|
}
|
|
|
|
// Layout the components of the checks widget
|
|
func (r *checkGroupRenderer) Layout(_ fyne.Size) {
|
|
count := 1
|
|
if r.items != nil && len(r.items) > 0 {
|
|
count = len(r.items)
|
|
}
|
|
var itemHeight, itemWidth float32
|
|
minSize := r.checks.MinSize()
|
|
if r.checks.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.checks.Horizontal {
|
|
x += itemWidth
|
|
} else {
|
|
y += itemHeight
|
|
}
|
|
}
|
|
}
|
|
|
|
// MinSize calculates the minimum size of a checks item.
|
|
// This is based on the contained text, the checks icon and a standard amount of padding
|
|
// between each item.
|
|
func (r *checkGroupRenderer) 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.checks.Horizontal {
|
|
width = width * float32(len(r.items))
|
|
} else {
|
|
height = height * float32(len(r.items))
|
|
}
|
|
|
|
return fyne.NewSize(width, height)
|
|
}
|
|
|
|
func (r *checkGroupRenderer) Refresh() {
|
|
r.updateItems()
|
|
canvas.Refresh(r.checks.super())
|
|
}
|
|
|
|
func (r *checkGroupRenderer) updateItems() {
|
|
if len(r.items) < len(r.checks.Options) {
|
|
for i := len(r.items); i < len(r.checks.Options); i++ {
|
|
var item *Check
|
|
item = NewCheck(r.checks.Options[i], func(bool) {
|
|
r.checks.itemTapped(item)
|
|
})
|
|
r.SetObjects(append(r.Objects(), item))
|
|
r.items = append(r.items, item)
|
|
}
|
|
r.Layout(r.checks.Size())
|
|
} else if len(r.items) > len(r.checks.Options) {
|
|
total := len(r.checks.Options)
|
|
r.items = r.items[:total]
|
|
r.SetObjects(r.Objects()[:total])
|
|
}
|
|
for i, item := range r.items {
|
|
contains := false
|
|
for _, s := range r.checks.Selected {
|
|
if s == item.Text {
|
|
contains = true
|
|
break
|
|
}
|
|
}
|
|
item.Text = r.checks.Options[i]
|
|
item.Checked = contains
|
|
item.disabled = r.checks.disabled
|
|
item.Refresh()
|
|
}
|
|
}
|