menu_bar.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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. if !b.active {
  63. b.active = true
  64. }
  65. if item.Child() != nil {
  66. item.Child().DeactivateChild()
  67. }
  68. if b.activeItem == item {
  69. return
  70. }
  71. if b.activeItem != nil {
  72. if c := b.activeItem.Child(); c != nil {
  73. c.Hide()
  74. }
  75. b.activeItem.Refresh()
  76. }
  77. b.activeItem = item
  78. if item == nil {
  79. return
  80. }
  81. item.Refresh()
  82. item.Child().Show()
  83. b.Refresh()
  84. }
  85. func (b *MenuBar) deactivate() {
  86. if !b.active {
  87. return
  88. }
  89. b.active = false
  90. if b.activeItem != nil {
  91. if c := b.activeItem.Child(); c != nil {
  92. defer c.Dismiss()
  93. c.Hide()
  94. }
  95. b.activeItem.Refresh()
  96. b.activeItem = nil
  97. }
  98. b.Refresh()
  99. }
  100. func (b *MenuBar) toggle(item *menuBarItem) {
  101. if b.active {
  102. b.canvas.Unfocus()
  103. b.deactivate()
  104. } else {
  105. b.activateChild(item)
  106. b.canvas.Focus(item)
  107. }
  108. }
  109. type menuBarRenderer struct {
  110. *widget.ShadowingRenderer
  111. b *MenuBar
  112. background *canvas.Rectangle
  113. underlay *menuBarUnderlay
  114. cont *fyne.Container
  115. }
  116. func (r *menuBarRenderer) Layout(size fyne.Size) {
  117. r.LayoutShadow(size, fyne.NewPos(0, 0))
  118. minSize := r.MinSize()
  119. if size.Height != minSize.Height || size.Width < minSize.Width {
  120. r.b.Resize(fyne.NewSize(fyne.Max(size.Width, minSize.Width), minSize.Height))
  121. return
  122. }
  123. if r.b.active {
  124. r.underlay.Resize(r.b.canvas.Size())
  125. } else {
  126. r.underlay.Resize(fyne.NewSize(0, 0))
  127. }
  128. r.cont.Resize(fyne.NewSize(size.Width-2*theme.InnerPadding(), size.Height))
  129. r.cont.Move(fyne.NewPos(theme.InnerPadding(), 0))
  130. if item := r.b.activeItem; item != nil {
  131. if item.Child().Size().IsZero() {
  132. item.Child().Resize(item.Child().MinSize())
  133. }
  134. item.Child().Move(fyne.NewPos(item.Position().X+theme.InnerPadding(), item.Size().Height))
  135. }
  136. r.background.Resize(size)
  137. }
  138. func (r *menuBarRenderer) MinSize() fyne.Size {
  139. return r.cont.MinSize().Add(fyne.NewSize(theme.InnerPadding()*2, 0))
  140. }
  141. func (r *menuBarRenderer) Refresh() {
  142. r.Layout(r.b.Size())
  143. r.background.FillColor = theme.BackgroundColor()
  144. r.background.Refresh()
  145. r.ShadowingRenderer.RefreshShadow()
  146. canvas.Refresh(r.b)
  147. }
  148. // Transparent underlay shown as soon as menu is active.
  149. // It catches mouse events outside the menu's objects.
  150. type menuBarUnderlay struct {
  151. widget.Base
  152. action func()
  153. }
  154. var _ fyne.Widget = (*menuBarUnderlay)(nil)
  155. var _ fyne.Tappable = (*menuBarUnderlay)(nil) // deactivate menu on click outside
  156. var _ desktop.Hoverable = (*menuBarUnderlay)(nil) // block hover events on main content
  157. func (u *menuBarUnderlay) CreateRenderer() fyne.WidgetRenderer {
  158. return &menuUnderlayRenderer{}
  159. }
  160. func (u *menuBarUnderlay) MouseIn(*desktop.MouseEvent) {
  161. }
  162. func (u *menuBarUnderlay) MouseOut() {
  163. }
  164. func (u *menuBarUnderlay) MouseMoved(*desktop.MouseEvent) {
  165. }
  166. func (u *menuBarUnderlay) Tapped(*fyne.PointEvent) {
  167. u.action()
  168. }
  169. type menuUnderlayRenderer struct {
  170. widget.BaseRenderer
  171. }
  172. var _ fyne.WidgetRenderer = (*menuUnderlayRenderer)(nil)
  173. func (r *menuUnderlayRenderer) Layout(fyne.Size) {
  174. }
  175. func (r *menuUnderlayRenderer) MinSize() fyne.Size {
  176. return fyne.NewSize(0, 0)
  177. }
  178. func (r *menuUnderlayRenderer) Refresh() {
  179. }