radio_group.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package widget
  2. import (
  3. "fyne.io/fyne/v2"
  4. "fyne.io/fyne/v2/canvas"
  5. "fyne.io/fyne/v2/internal/widget"
  6. )
  7. // RadioGroup widget has a list of text labels and checks check icons next to each.
  8. // Changing the selection (only one can be selected) will trigger the changed func.
  9. //
  10. // Since: 1.4
  11. type RadioGroup struct {
  12. DisableableWidget
  13. Horizontal bool
  14. Required bool
  15. OnChanged func(string) `json:"-"`
  16. Options []string
  17. Selected string
  18. items []*radioItem
  19. }
  20. var _ fyne.Widget = (*RadioGroup)(nil)
  21. // NewRadioGroup creates a new radio group widget with the set options and change handler
  22. //
  23. // Since: 1.4
  24. func NewRadioGroup(options []string, changed func(string)) *RadioGroup {
  25. r := &RadioGroup{
  26. Options: options,
  27. OnChanged: changed,
  28. }
  29. r.ExtendBaseWidget(r)
  30. r.update()
  31. return r
  32. }
  33. // Append adds a new option to the end of a RadioGroup widget.
  34. func (r *RadioGroup) Append(option string) {
  35. r.Options = append(r.Options, option)
  36. r.Refresh()
  37. }
  38. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  39. func (r *RadioGroup) CreateRenderer() fyne.WidgetRenderer {
  40. r.ExtendBaseWidget(r)
  41. r.propertyLock.Lock()
  42. defer r.propertyLock.Unlock()
  43. r.update()
  44. objects := make([]fyne.CanvasObject, len(r.items))
  45. for i, item := range r.items {
  46. objects[i] = item
  47. }
  48. return &radioGroupRenderer{widget.NewBaseRenderer(objects), r.items, r}
  49. }
  50. // MinSize returns the size that this widget should not shrink below
  51. func (r *RadioGroup) MinSize() fyne.Size {
  52. r.ExtendBaseWidget(r)
  53. return r.BaseWidget.MinSize()
  54. }
  55. // Refresh causes this widget to be redrawn in it's current state.
  56. //
  57. // Implements: fyne.CanvasObject
  58. func (r *RadioGroup) Refresh() {
  59. r.propertyLock.Lock()
  60. r.update()
  61. r.propertyLock.Unlock()
  62. r.BaseWidget.Refresh()
  63. }
  64. // SetSelected sets the radio option, it can be used to set a default option.
  65. func (r *RadioGroup) SetSelected(option string) {
  66. if r.Selected == option {
  67. return
  68. }
  69. r.Selected = option
  70. if r.OnChanged != nil {
  71. r.OnChanged(option)
  72. }
  73. r.Refresh()
  74. }
  75. func (r *RadioGroup) itemTapped(item *radioItem) {
  76. if r.Disabled() {
  77. return
  78. }
  79. if r.Selected == item.Label {
  80. if r.Required {
  81. return
  82. }
  83. r.Selected = ""
  84. item.SetSelected(false)
  85. } else {
  86. r.Selected = item.Label
  87. item.SetSelected(true)
  88. }
  89. if r.OnChanged != nil {
  90. r.OnChanged(r.Selected)
  91. }
  92. r.Refresh()
  93. }
  94. func (r *RadioGroup) update() {
  95. r.Options = removeDuplicates(r.Options)
  96. if len(r.items) < len(r.Options) {
  97. for i := len(r.items); i < len(r.Options); i++ {
  98. item := newRadioItem(r.Options[i], r.itemTapped)
  99. r.items = append(r.items, item)
  100. }
  101. } else if len(r.items) > len(r.Options) {
  102. r.items = r.items[:len(r.Options)]
  103. }
  104. for i, item := range r.items {
  105. item.Label = r.Options[i]
  106. item.Selected = item.Label == r.Selected
  107. item.DisableableWidget.disabled = r.disabled
  108. item.Refresh()
  109. }
  110. }
  111. type radioGroupRenderer struct {
  112. widget.BaseRenderer
  113. items []*radioItem
  114. radio *RadioGroup
  115. }
  116. // Layout the components of the radio widget
  117. func (r *radioGroupRenderer) Layout(_ fyne.Size) {
  118. count := 1
  119. if r.items != nil && len(r.items) > 0 {
  120. count = len(r.items)
  121. }
  122. var itemHeight, itemWidth float32
  123. minSize := r.radio.MinSize()
  124. if r.radio.Horizontal {
  125. itemHeight = minSize.Height
  126. itemWidth = minSize.Width / float32(count)
  127. } else {
  128. itemHeight = minSize.Height / float32(count)
  129. itemWidth = minSize.Width
  130. }
  131. itemSize := fyne.NewSize(itemWidth, itemHeight)
  132. x, y := float32(0), float32(0)
  133. for _, item := range r.items {
  134. item.Resize(itemSize)
  135. item.Move(fyne.NewPos(x, y))
  136. if r.radio.Horizontal {
  137. x += itemWidth
  138. } else {
  139. y += itemHeight
  140. }
  141. }
  142. }
  143. // MinSize calculates the minimum size of a radio item.
  144. // This is based on the contained text, the radio icon and a standard amount of padding
  145. // between each item.
  146. func (r *radioGroupRenderer) MinSize() fyne.Size {
  147. width := float32(0)
  148. height := float32(0)
  149. for _, item := range r.items {
  150. itemMin := item.MinSize()
  151. width = fyne.Max(width, itemMin.Width)
  152. height = fyne.Max(height, itemMin.Height)
  153. }
  154. if r.radio.Horizontal {
  155. width = width * float32(len(r.items))
  156. } else {
  157. height = height * float32(len(r.items))
  158. }
  159. return fyne.NewSize(width, height)
  160. }
  161. func (r *radioGroupRenderer) Refresh() {
  162. r.updateItems()
  163. canvas.Refresh(r.radio.super())
  164. }
  165. func (r *radioGroupRenderer) updateItems() {
  166. if len(r.items) < len(r.radio.Options) {
  167. for i := len(r.items); i < len(r.radio.Options); i++ {
  168. item := newRadioItem(r.radio.Options[i], r.radio.itemTapped)
  169. r.SetObjects(append(r.Objects(), item))
  170. r.items = append(r.items, item)
  171. }
  172. r.Layout(r.radio.Size())
  173. } else if len(r.items) > len(r.radio.Options) {
  174. total := len(r.radio.Options)
  175. r.items = r.items[:total]
  176. r.SetObjects(r.Objects()[:total])
  177. }
  178. for i, item := range r.items {
  179. item.Label = r.radio.Options[i]
  180. item.Selected = item.Label == r.radio.Selected
  181. item.disabled = r.radio.disabled
  182. item.Refresh()
  183. }
  184. }
  185. func removeDuplicates(options []string) []string {
  186. var result []string
  187. found := make(map[string]bool)
  188. for _, option := range options {
  189. if _, ok := found[option]; !ok {
  190. found[option] = true
  191. result = append(result, option)
  192. }
  193. }
  194. return result
  195. }