dropdown.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. import (
  6. "github.com/g3n/engine/gui/assets/icon"
  7. "github.com/g3n/engine/window"
  8. )
  9. // DropDown represents a dropdown GUI element.
  10. type DropDown struct {
  11. Panel // Embedded panel
  12. icon *Label // internal label with icon
  13. list *List // internal list
  14. styles *DropDownStyles // pointer to dropdown styles
  15. litem *ImageLabel // Item shown in drop box (copy of selected)
  16. selItem *ImageLabel // selected item from list
  17. overDropdown bool
  18. overList bool
  19. focus bool
  20. clickOut bool
  21. }
  22. // DropDownStyle contains the styling of a DropDown.
  23. type DropDownStyle BasicStyle
  24. // DropDownStyles contains a DropDownStyle for each valid GUI state.
  25. type DropDownStyles struct {
  26. Normal DropDownStyle
  27. Over DropDownStyle
  28. Focus DropDownStyle
  29. Disabled DropDownStyle
  30. }
  31. // NewDropDown creates and returns a pointer to a new drop down widget with the specified width.
  32. func NewDropDown(width float32, item *ImageLabel) *DropDown {
  33. dd := new(DropDown)
  34. dd.styles = &StyleDefault().DropDown
  35. dd.litem = item
  36. dd.Panel.Initialize(width, 0)
  37. dd.Panel.Subscribe(OnKeyDown, dd.onKeyEvent)
  38. dd.Panel.Subscribe(OnMouseDown, dd.onMouse)
  39. dd.Panel.Subscribe(OnCursorEnter, dd.onCursor)
  40. dd.Panel.Subscribe(OnCursorLeave, dd.onCursor)
  41. dd.Panel.Subscribe(OnResize, func(name string, ev interface{}) { dd.recalc() })
  42. // ListItem
  43. dd.Panel.Add(dd.litem)
  44. // Create icon
  45. dd.icon = NewIcon(" ")
  46. dd.icon.SetFontSize(StyleDefault().Label.PointSize * 1.3)
  47. dd.icon.SetText(string(icon.ArrowDropDown))
  48. dd.Panel.Add(dd.icon)
  49. /// Create list
  50. dd.list = NewVList(0, 0)
  51. dd.list.bounded = false
  52. dd.list.dropdown = true
  53. dd.list.SetVisible(false)
  54. dd.list.Subscribe(OnMouseDown, dd.onListMouse)
  55. dd.list.Subscribe(OnMouseOut, dd.onListMouse)
  56. dd.list.Subscribe(OnChange, dd.onListChangeEvent)
  57. dd.list.Subscribe(OnCursor, func(evname string, ev interface{}) { dd.root.StopPropagation(StopAll) })
  58. dd.Panel.Add(dd.list)
  59. dd.update()
  60. // This will trigger recalc()
  61. dd.Panel.SetContentHeight(item.Height())
  62. return dd
  63. }
  64. // Add adds a list item at the end of the list
  65. func (dd *DropDown) Add(item *ImageLabel) {
  66. dd.list.Add(item)
  67. }
  68. // InsertAt inserts a list item at the specified position
  69. // Returs true if the item was successfully inserted
  70. func (dd *DropDown) InsertAt(pos int, item *ImageLabel) {
  71. dd.list.InsertAt(pos, item)
  72. }
  73. // RemoveAt removes the list item from the specified position
  74. // Returs true if the item was successfully removed
  75. func (dd *DropDown) RemoveAt(pos int) {
  76. dd.list.RemoveAt(pos)
  77. }
  78. // ItemAt returns the list item at the specified position
  79. func (dd *DropDown) ItemAt(pos int) *ImageLabel {
  80. return dd.list.ItemAt(pos).(*ImageLabel)
  81. }
  82. // Len returns the number of items in the dropdown's list.
  83. func (dd *DropDown) Len() int {
  84. return dd.list.Len()
  85. }
  86. // Selected returns the currently selected item or nil if no item was selected
  87. func (dd *DropDown) Selected() *ImageLabel {
  88. return dd.selItem
  89. }
  90. // SelectedPos returns the currently selected position or -1 if no item was selected
  91. func (dd *DropDown) SelectedPos() int {
  92. return dd.list.selected()
  93. }
  94. // SetSelected sets the selected item
  95. func (dd *DropDown) SetSelected(item *ImageLabel) {
  96. dd.list.SetSelected(dd.selItem, false)
  97. dd.list.SetSelected(item, true)
  98. dd.copySelected()
  99. dd.update()
  100. }
  101. // SelectPos selects the item at the specified position
  102. func (dd *DropDown) SelectPos(pos int) {
  103. dd.list.SetSelected(dd.selItem, false)
  104. dd.list.SelectPos(pos, true)
  105. dd.Dispatch(OnChange, nil)
  106. }
  107. // SetStyles sets the drop down styles overriding the default style
  108. func (dd *DropDown) SetStyles(dds *DropDownStyles) {
  109. dd.styles = dds
  110. dd.update()
  111. }
  112. // onKeyEvent is called when key event is received when this dropdown has the key focus.
  113. func (dd *DropDown) onKeyEvent(evname string, ev interface{}) {
  114. kev := ev.(*window.KeyEvent)
  115. switch kev.Keycode {
  116. case window.KeyF1:
  117. if dd.list.Visible() {
  118. dd.list.SetVisible(false)
  119. }
  120. default:
  121. return
  122. }
  123. }
  124. // onMouse receives subscribed mouse events over the dropdown
  125. func (dd *DropDown) onMouse(evname string, ev interface{}) {
  126. if evname == OnMouseDown {
  127. // If clickOut list already closed
  128. if dd.clickOut {
  129. dd.clickOut = false
  130. return
  131. }
  132. dd.list.SetVisible(true)
  133. dd.root.SetKeyFocus(dd.list)
  134. return
  135. }
  136. }
  137. // onCursor receives subscribed cursor events over the dropdown
  138. func (dd *DropDown) onCursor(evname string, ev interface{}) {
  139. if evname == OnCursorEnter {
  140. dd.overDropdown = true
  141. }
  142. if evname == OnCursorLeave {
  143. dd.overDropdown = false
  144. }
  145. dd.update()
  146. dd.root.StopPropagation(StopAll)
  147. }
  148. // onListMouseEvent receives mouse events over the list
  149. func (dd *DropDown) onListMouse(evname string, ev interface{}) {
  150. mev := ev.(*window.MouseEvent)
  151. // List was clicked
  152. if evname == OnMouseDown {
  153. // If click occurred inside the list scrollbar ignore it
  154. if dd.list.vscroll != nil {
  155. if dd.list.vscroll.InsideBorders(mev.Xpos, mev.Ypos) {
  156. return
  157. }
  158. }
  159. // Otherwise, closes the list
  160. dd.list.SetVisible(false)
  161. //dd.copySelected()
  162. dd.overList = false
  163. dd.update()
  164. return
  165. }
  166. // Hide list when clicked out
  167. if evname == OnMouseOut {
  168. if dd.list.Visible() {
  169. dd.list.SetVisible(false)
  170. }
  171. // If list clickout occurred inside the dropdown, set 'clickOut' to
  172. // indicate that the list was already closed
  173. if dd.Panel.InsideBorders(mev.Xpos, mev.Ypos) {
  174. dd.clickOut = true
  175. }
  176. }
  177. }
  178. // onListCursor receives subscribed events over the list
  179. //func (dd *DropDown) onListCursor(evname string, ev interface{}) {
  180. //
  181. // if evname == OnCursorEnter {
  182. // dd.overList = true
  183. // dd.update()
  184. // return
  185. // }
  186. // if evname == OnCursorLeave {
  187. // dd.overList = false
  188. // dd.update()
  189. // return
  190. // }
  191. // dd.root.StopPropagation(StopAll)
  192. //}
  193. // copySelected copy to the dropdown panel the selected item
  194. // from the list.
  195. func (dd *DropDown) copySelected() {
  196. selected := dd.list.Selected()
  197. if len(selected) > 0 {
  198. dd.selItem = selected[0].(*ImageLabel)
  199. dd.litem.CopyFields(dd.selItem)
  200. dd.litem.SetWidth(dd.selItem.Width())
  201. dd.recalc()
  202. dd.Dispatch(OnChange, nil)
  203. } else {
  204. return
  205. }
  206. }
  207. // onListChangeEvent is called when an item in the list is selected
  208. func (dd *DropDown) onListChangeEvent(evname string, ev interface{}) {
  209. dd.copySelected()
  210. }
  211. // recalc recalculates the dimensions and positions of the dropdown
  212. // panel, children and list
  213. func (dd *DropDown) recalc() {
  214. // Dropdown icon position
  215. posx := dd.Panel.ContentWidth() - dd.icon.Width()
  216. dd.icon.SetPosition(posx, 0)
  217. // List item position and width
  218. ipan := dd.litem.GetPanel()
  219. ipan.SetPosition(0, 0)
  220. height := ipan.Height()
  221. // List position
  222. dd.list.SetWidth(dd.Panel.Width())
  223. dd.list.SetHeight(6*height + 1)
  224. dd.list.SetPositionX(0)
  225. dd.list.SetPositionY(dd.Panel.Height())
  226. }
  227. // update updates the visual state
  228. func (dd *DropDown) update() {
  229. if dd.overDropdown || dd.overList {
  230. dd.applyStyle(&dd.styles.Over)
  231. dd.list.ApplyStyle(StyleOver)
  232. return
  233. }
  234. if dd.focus {
  235. dd.applyStyle(&dd.styles.Focus)
  236. dd.list.ApplyStyle(StyleFocus)
  237. return
  238. }
  239. dd.applyStyle(&dd.styles.Normal)
  240. dd.list.ApplyStyle(StyleNormal)
  241. }
  242. // applyStyle applies the specified style
  243. func (dd *DropDown) applyStyle(s *DropDownStyle) {
  244. dd.Panel.ApplyStyle(&s.PanelStyle)
  245. }