radio_item.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. package widget
  2. import (
  3. "image/color"
  4. "fyne.io/fyne/v2"
  5. "fyne.io/fyne/v2/canvas"
  6. "fyne.io/fyne/v2/driver/desktop"
  7. "fyne.io/fyne/v2/internal/widget"
  8. "fyne.io/fyne/v2/theme"
  9. )
  10. var _ fyne.Widget = (*radioItem)(nil)
  11. var _ desktop.Hoverable = (*radioItem)(nil)
  12. var _ fyne.Tappable = (*radioItem)(nil)
  13. var _ fyne.Focusable = (*radioItem)(nil)
  14. func newRadioItem(label string, onTap func(*radioItem)) *radioItem {
  15. i := &radioItem{Label: label, onTap: onTap}
  16. i.ExtendBaseWidget(i)
  17. return i
  18. }
  19. // radioItem is a single radio item to be used by RadioGroup.
  20. type radioItem struct {
  21. DisableableWidget
  22. Label string
  23. Selected bool
  24. focused bool
  25. hovered bool
  26. onTap func(item *radioItem)
  27. }
  28. // CreateRenderer is a private method to Fyne which links this widget to its renderer.
  29. //
  30. // Implements: fyne.Widget
  31. func (i *radioItem) CreateRenderer() fyne.WidgetRenderer {
  32. focusIndicator := canvas.NewCircle(color.Transparent)
  33. // TODO move to `theme.RadioButtonFillIcon()` when we add it in 2.4
  34. icon := canvas.NewImageFromResource(fyne.CurrentApp().Settings().Theme().Icon("iconNameRadioButtonFill"))
  35. over := canvas.NewImageFromResource(theme.NewThemedResource(theme.RadioButtonIcon()))
  36. label := canvas.NewText(i.Label, theme.ForegroundColor())
  37. label.Alignment = fyne.TextAlignLeading
  38. r := &radioItemRenderer{
  39. BaseRenderer: widget.NewBaseRenderer([]fyne.CanvasObject{focusIndicator, icon, over, label}),
  40. focusIndicator: focusIndicator,
  41. icon: icon,
  42. over: over,
  43. item: i,
  44. label: label,
  45. }
  46. r.update()
  47. return r
  48. }
  49. // FocusGained is called when this item gained the focus.
  50. //
  51. // Implements: fyne.Focusable
  52. func (i *radioItem) FocusGained() {
  53. i.focused = true
  54. i.Refresh()
  55. }
  56. // FocusLost is called when this item lost the focus.
  57. //
  58. // Implements: fyne.Focusable
  59. func (i *radioItem) FocusLost() {
  60. i.focused = false
  61. i.Refresh()
  62. }
  63. // MouseIn is called when a desktop pointer enters the widget.
  64. //
  65. // Implements: desktop.Hoverable
  66. func (i *radioItem) MouseIn(_ *desktop.MouseEvent) {
  67. if i.Disabled() {
  68. return
  69. }
  70. i.hovered = true
  71. i.Refresh()
  72. }
  73. // MouseMoved is called when a desktop pointer hovers over the widget.
  74. //
  75. // Implements: desktop.Hoverable
  76. func (i *radioItem) MouseMoved(_ *desktop.MouseEvent) {
  77. }
  78. // MouseOut is called when a desktop pointer exits the widget
  79. //
  80. // Implements: desktop.Hoverable
  81. func (i *radioItem) MouseOut() {
  82. if i.Disabled() {
  83. return
  84. }
  85. i.hovered = false
  86. i.Refresh()
  87. }
  88. // SetSelected sets whether this radio item is selected or not.
  89. func (i *radioItem) SetSelected(selected bool) {
  90. if i.Disabled() || i.Selected == selected {
  91. return
  92. }
  93. i.Selected = selected
  94. i.Refresh()
  95. }
  96. // Tapped is called when a pointer tapped event is captured and triggers any change handler
  97. //
  98. // Implements: fyne.Tappable
  99. func (i *radioItem) Tapped(_ *fyne.PointEvent) {
  100. if !i.focused && !fyne.CurrentDevice().IsMobile() {
  101. impl := i.super()
  102. if c := fyne.CurrentApp().Driver().CanvasForObject(impl); c != nil {
  103. c.Focus(impl.(fyne.Focusable))
  104. }
  105. }
  106. i.toggle()
  107. }
  108. // TypedKey is called when this item receives a key event.
  109. //
  110. // Implements: fyne.Focusable
  111. func (i *radioItem) TypedKey(_ *fyne.KeyEvent) {
  112. }
  113. // TypedRune is called when this item receives a char event.
  114. //
  115. // Implements: fyne.Focusable
  116. func (i *radioItem) TypedRune(r rune) {
  117. if r == ' ' {
  118. i.toggle()
  119. }
  120. }
  121. func (i *radioItem) toggle() {
  122. if i.Disabled() || i.onTap == nil {
  123. return
  124. }
  125. i.onTap(i)
  126. }
  127. type radioItemRenderer struct {
  128. widget.BaseRenderer
  129. focusIndicator *canvas.Circle
  130. icon, over *canvas.Image
  131. item *radioItem
  132. label *canvas.Text
  133. }
  134. func (r *radioItemRenderer) Layout(size fyne.Size) {
  135. focusIndicatorSize := fyne.NewSquareSize(theme.IconInlineSize() + theme.InnerPadding())
  136. r.focusIndicator.Resize(focusIndicatorSize)
  137. r.focusIndicator.Move(fyne.NewPos(theme.InputBorderSize(), (size.Height-focusIndicatorSize.Height)/2))
  138. labelSize := fyne.NewSize(size.Width, size.Height)
  139. r.label.Resize(labelSize)
  140. r.label.Move(fyne.NewPos(focusIndicatorSize.Width+theme.Padding(), 0))
  141. iconPos := fyne.NewPos(theme.InnerPadding()/2+theme.InputBorderSize(), (size.Height-theme.IconInlineSize())/2)
  142. iconSize := fyne.NewSquareSize(theme.IconInlineSize())
  143. r.icon.Resize(iconSize)
  144. r.icon.Move(iconPos)
  145. r.over.Resize(iconSize)
  146. r.over.Move(iconPos)
  147. }
  148. func (r *radioItemRenderer) MinSize() fyne.Size {
  149. inPad := theme.InnerPadding() * 2
  150. return r.label.MinSize().
  151. Add(fyne.NewSize(inPad, inPad)).
  152. Add(fyne.NewSize(theme.IconInlineSize()+theme.Padding(), 0))
  153. }
  154. func (r *radioItemRenderer) Refresh() {
  155. r.update()
  156. canvas.Refresh(r.item.super())
  157. }
  158. func (r *radioItemRenderer) update() {
  159. r.label.Text = r.item.Label
  160. r.label.Color = theme.ForegroundColor()
  161. r.label.TextSize = theme.TextSize()
  162. if r.item.Disabled() {
  163. r.label.Color = theme.DisabledColor()
  164. }
  165. out := theme.NewThemedResource(theme.RadioButtonIcon())
  166. out.ColorName = theme.ColorNameInputBorder
  167. // TODO move to `theme.RadioButtonFillIcon()` when we add it in 2.4
  168. in := theme.NewThemedResource(fyne.CurrentApp().Settings().Theme().Icon("iconNameRadioButtonFill"))
  169. in.ColorName = theme.ColorNameInputBackground
  170. if r.item.Selected {
  171. in.ColorName = theme.ColorNamePrimary
  172. out.ColorName = theme.ColorNameForeground
  173. }
  174. if r.item.Disabled() {
  175. if r.item.Selected {
  176. in.ColorName = theme.ColorNameDisabled
  177. } else {
  178. in.ColorName = theme.ColorNameBackground
  179. }
  180. out.ColorName = theme.ColorNameDisabled
  181. }
  182. r.icon.Resource = in
  183. r.over.Resource = out
  184. if r.item.Disabled() {
  185. r.focusIndicator.FillColor = color.Transparent
  186. } else if r.item.focused {
  187. r.focusIndicator.FillColor = theme.FocusColor()
  188. } else if r.item.hovered {
  189. r.focusIndicator.FillColor = theme.HoverColor()
  190. } else {
  191. r.focusIndicator.FillColor = color.Transparent
  192. }
  193. }