menu_bar.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package glfw
  2. import (
  3. "fyne.io/fyne/v2"
  4. "fyne.io/fyne/v2/canvas"
  5. "fyne.io/fyne/v2/container"
  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 = (*MenuBar)(nil)
  11. // MenuBar is a widget for displaying a fyne.MainMenu in a bar.
  12. type MenuBar struct {
  13. widget.Base
  14. Items []fyne.CanvasObject
  15. active bool
  16. activeItem *menuBarItem
  17. canvas fyne.Canvas
  18. }
  19. // NewMenuBar creates a menu bar populated with items from the passed main menu structure.
  20. func NewMenuBar(mainMenu *fyne.MainMenu, canvas fyne.Canvas) *MenuBar {
  21. items := make([]fyne.CanvasObject, len(mainMenu.Items))
  22. b := &MenuBar{Items: items, canvas: canvas}
  23. b.ExtendBaseWidget(b)
  24. for i, menu := range mainMenu.Items {
  25. barItem := &menuBarItem{Menu: menu, Parent: b}
  26. barItem.ExtendBaseWidget(barItem)
  27. items[i] = barItem
  28. }
  29. return b
  30. }
  31. // CreateRenderer returns a new renderer for the menu bar.
  32. //
  33. // Implements: fyne.Widget
  34. func (b *MenuBar) CreateRenderer() fyne.WidgetRenderer {
  35. cont := container.NewHBox(b.Items...)
  36. background := canvas.NewRectangle(theme.BackgroundColor())
  37. underlay := &menuBarUnderlay{action: b.deactivate}
  38. underlay.ExtendBaseWidget(underlay)
  39. objects := []fyne.CanvasObject{underlay, background, cont}
  40. for _, item := range b.Items {
  41. objects = append(objects, item.(*menuBarItem).Child())
  42. }
  43. return &menuBarRenderer{
  44. widget.NewShadowingRenderer(objects, widget.MenuLevel),
  45. b,
  46. background,
  47. underlay,
  48. cont,
  49. }
  50. }
  51. // IsActive returns whether the menu bar is active or not.
  52. // An active menu bar shows the current selected menu and should have the focus.
  53. func (b *MenuBar) IsActive() bool {
  54. return b.active
  55. }
  56. // Toggle changes the activation state of the menu bar.
  57. // On activation, the first item will become active.
  58. func (b *MenuBar) Toggle() {
  59. b.toggle(b.Items[0].(*menuBarItem))
  60. }
  61. func (b *MenuBar) activateChild(item *menuBarItem) {
  62. b.active = true
  63. if item.Child() != nil {
  64. item.Child().DeactivateChild()
  65. }
  66. if b.activeItem == item {
  67. return
  68. }
  69. if b.activeItem != nil {
  70. if c := b.activeItem.Child(); c != nil {
  71. c.Hide()
  72. }
  73. b.activeItem.Refresh()
  74. }
  75. b.activeItem = item
  76. if item == nil {
  77. return
  78. }
  79. item.Refresh()
  80. item.Child().Show()
  81. b.Refresh()
  82. }
  83. func (b *MenuBar) deactivate() {
  84. if !b.active {
  85. return
  86. }
  87. b.active = false
  88. if b.activeItem != nil {
  89. if c := b.activeItem.Child(); c != nil {
  90. defer c.Dismiss()
  91. c.Hide()
  92. }
  93. b.activeItem.Refresh()
  94. b.activeItem = nil
  95. }
  96. b.Refresh()
  97. }
  98. func (b *MenuBar) toggle(item *menuBarItem) {
  99. if b.active {
  100. b.canvas.Unfocus()
  101. b.deactivate()
  102. } else {
  103. b.activateChild(item)
  104. b.canvas.Focus(item)
  105. }
  106. }
  107. type menuBarRenderer struct {
  108. *widget.ShadowingRenderer
  109. b *MenuBar
  110. background *canvas.Rectangle
  111. underlay *menuBarUnderlay
  112. cont *fyne.Container
  113. }
  114. func (r *menuBarRenderer) Layout(size fyne.Size) {
  115. r.LayoutShadow(size, fyne.NewPos(0, 0))
  116. minSize := r.MinSize()
  117. if size.Height != minSize.Height || size.Width < minSize.Width {
  118. r.b.Resize(fyne.NewSize(fyne.Max(size.Width, minSize.Width), minSize.Height))
  119. return
  120. }
  121. if r.b.active {
  122. r.underlay.Resize(r.b.canvas.Size())
  123. } else {
  124. r.underlay.Resize(fyne.NewSize(0, 0))
  125. }
  126. innerPadding := theme.InnerPadding()
  127. r.cont.Resize(fyne.NewSize(size.Width-2*innerPadding, size.Height))
  128. r.cont.Move(fyne.NewPos(innerPadding, 0))
  129. if item := r.b.activeItem; item != nil {
  130. if item.Child().Size().IsZero() {
  131. item.Child().Resize(item.Child().MinSize())
  132. }
  133. item.Child().Move(fyne.NewPos(item.Position().X+innerPadding, item.Size().Height))
  134. }
  135. r.background.Resize(size)
  136. }
  137. func (r *menuBarRenderer) MinSize() fyne.Size {
  138. return r.cont.MinSize().Add(fyne.NewSize(theme.InnerPadding()*2, 0))
  139. }
  140. func (r *menuBarRenderer) Refresh() {
  141. r.Layout(r.b.Size())
  142. r.background.FillColor = theme.BackgroundColor()
  143. r.background.Refresh()
  144. r.ShadowingRenderer.RefreshShadow()
  145. canvas.Refresh(r.b)
  146. }
  147. // Transparent underlay shown as soon as menu is active.
  148. // It catches mouse events outside the menu's objects.
  149. type menuBarUnderlay struct {
  150. widget.Base
  151. action func()
  152. }
  153. var _ fyne.Widget = (*menuBarUnderlay)(nil)
  154. var _ fyne.Tappable = (*menuBarUnderlay)(nil) // deactivate menu on click outside
  155. var _ desktop.Hoverable = (*menuBarUnderlay)(nil) // block hover events on main content
  156. func (u *menuBarUnderlay) CreateRenderer() fyne.WidgetRenderer {
  157. return &menuUnderlayRenderer{}
  158. }
  159. func (u *menuBarUnderlay) MouseIn(*desktop.MouseEvent) {
  160. }
  161. func (u *menuBarUnderlay) MouseOut() {
  162. }
  163. func (u *menuBarUnderlay) MouseMoved(*desktop.MouseEvent) {
  164. }
  165. func (u *menuBarUnderlay) Tapped(*fyne.PointEvent) {
  166. u.action()
  167. }
  168. type menuUnderlayRenderer struct {
  169. widget.BaseRenderer
  170. }
  171. var _ fyne.WidgetRenderer = (*menuUnderlayRenderer)(nil)
  172. func (r *menuUnderlayRenderer) Layout(fyne.Size) {
  173. }
  174. func (r *menuUnderlayRenderer) MinSize() fyne.Size {
  175. return fyne.NewSize(0, 0)
  176. }
  177. func (r *menuUnderlayRenderer) Refresh() {
  178. }