radio_group.go 5.1 KB

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