dropdown.go 6.7 KB

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