list.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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/window"
  7. )
  8. // List represents a list GUI element
  9. type List struct {
  10. Scroller // Embedded scroller
  11. styles *ListStyles // Pointer to styles
  12. single bool // Single selection flag (default is true)
  13. focus bool // has keyboard focus
  14. dropdown bool // this is used as dropdown
  15. keyNext window.Key // Code of key to select next item
  16. keyPrev window.Key // Code of key to select previous item
  17. }
  18. // ListItem encapsulates each item inserted into the list
  19. type ListItem struct {
  20. Panel // Container panel
  21. item IPanel // Original item
  22. selected bool // Item selected flag
  23. highlighted bool // Item highlighted flag
  24. padLeft float32 // Additional left padding
  25. list *List // Pointer to list
  26. }
  27. // ListStyles
  28. type ListStyles struct {
  29. Scroller *ScrollerStyles
  30. Item *ListItemStyles
  31. }
  32. // ListItemStyles
  33. type ListItemStyles struct {
  34. Normal ListItemStyle
  35. Over ListItemStyle
  36. Selected ListItemStyle
  37. Highlighted ListItemStyle
  38. SelHigh ListItemStyle
  39. }
  40. // ListItemStyle
  41. type ListItemStyle BasicStyle
  42. // OnListItemResize is the identifier of the event dispatched when a ListItem's child panel is resized
  43. const OnListItemResize = "gui.OnListItemResize"
  44. // NewVList creates and returns a pointer to a new vertical list panel
  45. // with the specified dimensions
  46. func NewVList(width, height float32) *List {
  47. return newList(true, width, height)
  48. }
  49. // NewHList creates and returns a pointer to a new horizontal list panel
  50. // with the specified dimensions
  51. func NewHList(width, height float32) *List {
  52. return newList(false, width, height)
  53. }
  54. // newList creates and returns a pointer to a new list panel
  55. // with the specified orientation and dimensions
  56. func newList(vert bool, width, height float32) *List {
  57. li := new(List)
  58. li.initialize(vert, width, height)
  59. return li
  60. }
  61. func (li *List) initialize(vert bool, width, height float32) {
  62. li.styles = &StyleDefault().List
  63. li.single = true
  64. li.Scroller.initialize(vert, width, height)
  65. li.Scroller.SetStyles(li.styles.Scroller)
  66. li.Scroller.adjustItem = true
  67. li.Scroller.Subscribe(OnMouseDown, li.onMouseEvent)
  68. li.Scroller.Subscribe(OnKeyDown, li.onKeyEvent)
  69. li.Scroller.Subscribe(OnKeyRepeat, li.onKeyEvent)
  70. if vert {
  71. li.keyNext = window.KeyDown
  72. li.keyPrev = window.KeyUp
  73. } else {
  74. li.keyNext = window.KeyRight
  75. li.keyPrev = window.KeyLeft
  76. }
  77. li.update()
  78. }
  79. // SetSingle sets the single/multiple selection flag of the list
  80. func (li *List) SetSingle(state bool) {
  81. li.single = state
  82. }
  83. // Single returns the current state of the single/multiple selection flag
  84. func (li *List) Single() bool {
  85. return li.single
  86. }
  87. // SetStyles set the listr styles overriding the default style
  88. func (li *List) SetStyles(s *ListStyles) {
  89. li.styles = s
  90. li.Scroller.SetStyles(li.styles.Scroller)
  91. li.update()
  92. }
  93. // Add add a list item at the end of the list
  94. func (li *List) Add(item IPanel) {
  95. li.InsertAt(len(li.items), item)
  96. }
  97. // InsertAt inserts a list item at the specified position
  98. // Returs true if the item was successfully inserted
  99. func (li *List) InsertAt(pos int, item IPanel) {
  100. litem := newListItem(li, item)
  101. li.Scroller.InsertAt(pos, litem)
  102. litem.Panel.Subscribe(OnMouseDown, litem.onMouse)
  103. litem.Panel.Subscribe(OnCursorEnter, litem.onCursor)
  104. }
  105. // RemoveAt removes the list item from the specified position
  106. func (li *List) RemoveAt(pos int) IPanel {
  107. // Remove the list item from the internal scroller
  108. pan := li.Scroller.RemoveAt(pos)
  109. litem := pan.(*ListItem)
  110. // Remove item from the list item children and disposes of the list item panel
  111. item := litem.item
  112. litem.Remove(item)
  113. litem.Dispose()
  114. return item
  115. }
  116. // Remove removes the specified item from the list
  117. func (li *List) Remove(item IPanel) {
  118. for p, curr := range li.items {
  119. if curr.(*ListItem).item == item {
  120. li.RemoveAt(p)
  121. return
  122. }
  123. }
  124. }
  125. // ItemAt returns the list item at the specified position
  126. func (li *List) ItemAt(pos int) IPanel {
  127. item := li.Scroller.ItemAt(pos)
  128. if item == nil {
  129. return nil
  130. }
  131. litem := item.(*ListItem)
  132. return litem.item
  133. }
  134. // ItemPosition returns the position of the specified item in
  135. // the list or -1 if not found
  136. func (li *List) ItemPosition(item IPanel) int {
  137. for pos := 0; pos < len(li.items); pos++ {
  138. if li.items[pos].(*ListItem).item == item {
  139. return pos
  140. }
  141. }
  142. return -1
  143. }
  144. // Selected returns list with the currently selected items
  145. func (li *List) Selected() []IPanel {
  146. sel := []IPanel{}
  147. for _, item := range li.items {
  148. litem := item.(*ListItem)
  149. if litem.selected {
  150. sel = append(sel, litem.item)
  151. }
  152. }
  153. return sel
  154. }
  155. // SetSelected selects or unselects the specified item
  156. func (li *List) SetSelected(item IPanel, state bool) {
  157. for _, curr := range li.items {
  158. litem := curr.(*ListItem)
  159. if litem.item == item {
  160. litem.SetSelected(state)
  161. li.update()
  162. li.Dispatch(OnChange, nil)
  163. return
  164. }
  165. }
  166. }
  167. // SelectPos selects or unselects the item at the specified position
  168. func (li *List) SelectPos(pos int, state bool) {
  169. if pos < 0 || pos >= len(li.items) {
  170. return
  171. }
  172. litem := li.items[pos].(*ListItem)
  173. if litem.selected == state {
  174. return
  175. }
  176. litem.SetSelected(state)
  177. li.update()
  178. li.Dispatch(OnChange, nil)
  179. }
  180. // SetItemPadLeftAt sets the additional left padding for this item
  181. // It is used mainly by the tree control
  182. func (li *List) SetItemPadLeftAt(pos int, pad float32) {
  183. if pos < 0 || pos >= len(li.items) {
  184. return
  185. }
  186. litem := li.items[pos].(*ListItem)
  187. litem.padLeft = pad
  188. litem.update()
  189. }
  190. // selNext selects or highlights the next item, if possible
  191. func (li *List) selNext(sel bool, update bool) *ListItem {
  192. // Checks for empty list
  193. if len(li.items) == 0 {
  194. return nil
  195. }
  196. // Find currently selected item
  197. var pos int
  198. if sel {
  199. pos = li.selected()
  200. } else {
  201. pos = li.highlighted()
  202. }
  203. var newItem *ListItem
  204. newSel := true
  205. // If no item found, returns first.
  206. if pos < 0 {
  207. newItem = li.items[0].(*ListItem)
  208. if sel {
  209. newItem.SetSelected(true)
  210. } else {
  211. newItem.SetHighlighted(true)
  212. }
  213. } else {
  214. item := li.items[pos].(*ListItem)
  215. // Item is not the last, get next
  216. if pos < len(li.items)-1 {
  217. newItem = li.items[pos+1].(*ListItem)
  218. if sel {
  219. item.SetSelected(false)
  220. newItem.SetSelected(true)
  221. } else {
  222. item.SetHighlighted(false)
  223. newItem.SetHighlighted(true)
  224. }
  225. if !li.ItemVisible(pos + 1) {
  226. li.ScrollDown()
  227. }
  228. // Item is the last, don't change
  229. } else {
  230. newItem = item
  231. newSel = false
  232. }
  233. }
  234. if update {
  235. li.update()
  236. }
  237. if sel && newSel {
  238. li.Dispatch(OnChange, nil)
  239. }
  240. return newItem
  241. }
  242. // selPrev selects or highlights the next item, if possible
  243. func (li *List) selPrev(sel bool, update bool) *ListItem {
  244. // Check for empty list
  245. if len(li.items) == 0 {
  246. return nil
  247. }
  248. // Find first selected item
  249. var pos int
  250. if sel {
  251. pos = li.selected()
  252. } else {
  253. pos = li.highlighted()
  254. }
  255. var newItem *ListItem
  256. newSel := true
  257. // If no item found, returns first.
  258. if pos < 0 {
  259. newItem = li.items[0].(*ListItem)
  260. if sel {
  261. newItem.SetSelected(true)
  262. } else {
  263. newItem.SetHighlighted(true)
  264. }
  265. } else {
  266. item := li.items[pos].(*ListItem)
  267. if pos == 0 {
  268. newItem = item
  269. newSel = false
  270. } else {
  271. newItem = li.items[pos-1].(*ListItem)
  272. if sel {
  273. item.SetSelected(false)
  274. newItem.SetSelected(true)
  275. } else {
  276. item.SetHighlighted(false)
  277. newItem.SetHighlighted(true)
  278. }
  279. if (pos - 1) < li.first {
  280. li.ScrollUp()
  281. }
  282. }
  283. }
  284. if update {
  285. li.update()
  286. }
  287. if sel && newSel {
  288. li.Dispatch(OnChange, nil)
  289. }
  290. return newItem
  291. }
  292. // selected returns the position of first selected item
  293. func (li *List) selected() (pos int) {
  294. for pos, item := range li.items {
  295. if item.(*ListItem).selected {
  296. return pos
  297. }
  298. }
  299. return -1
  300. }
  301. // highlighted returns the position of first highlighted item
  302. func (li *List) highlighted() (pos int) {
  303. for pos, item := range li.items {
  304. if item.(*ListItem).highlighted {
  305. return pos
  306. }
  307. }
  308. return -1
  309. }
  310. // onMouseEvent receives subscribed mouse events for the list
  311. func (li *List) onMouseEvent(evname string, ev interface{}) {
  312. li.root.SetKeyFocus(li)
  313. li.root.StopPropagation(StopAll)
  314. }
  315. // onKeyEvent receives subscribed key events for the list
  316. func (li *List) onKeyEvent(evname string, ev interface{}) {
  317. kev := ev.(*window.KeyEvent)
  318. // Dropdown mode
  319. if li.dropdown {
  320. switch kev.Keycode {
  321. case li.keyNext:
  322. li.selNext(true, true)
  323. case li.keyPrev:
  324. li.selPrev(true, true)
  325. case window.KeyEnter:
  326. li.SetVisible(false)
  327. default:
  328. return
  329. }
  330. li.root.StopPropagation(Stop3D)
  331. return
  332. }
  333. // Listbox mode single selection
  334. if li.single {
  335. switch kev.Keycode {
  336. case li.keyNext:
  337. li.selNext(true, true)
  338. case li.keyPrev:
  339. li.selPrev(true, true)
  340. default:
  341. return
  342. }
  343. li.root.StopPropagation(Stop3D)
  344. return
  345. }
  346. // Listbox mode multiple selection
  347. switch kev.Keycode {
  348. case li.keyNext:
  349. li.selNext(false, true)
  350. case li.keyPrev:
  351. li.selPrev(false, true)
  352. case window.KeySpace:
  353. pos := li.highlighted()
  354. if pos >= 0 {
  355. litem := li.items[pos].(*ListItem)
  356. li.setSelection(litem, !litem.selected, true, true)
  357. }
  358. default:
  359. return
  360. }
  361. li.root.StopPropagation(Stop3D)
  362. }
  363. // setSelection sets the selected state of the specified item
  364. // updating the visual appearance of the list if necessary
  365. func (li *List) setSelection(litem *ListItem, state bool, force bool, dispatch bool) {
  366. // If already at this state, nothing to do
  367. if litem.selected == state && !force {
  368. return
  369. }
  370. litem.SetSelected(state)
  371. // If single selection, deselects all other items
  372. if li.single {
  373. for _, curr := range li.items {
  374. if curr.(*ListItem) != litem {
  375. curr.(*ListItem).SetSelected(false)
  376. }
  377. }
  378. }
  379. li.update()
  380. if dispatch {
  381. li.Dispatch(OnChange, nil)
  382. }
  383. }
  384. // update updates the visual state the list and its items
  385. func (li *List) update() {
  386. // Update the list items styles
  387. for _, item := range li.items {
  388. item.(*ListItem).update()
  389. }
  390. }
  391. //
  392. // ListItem methods
  393. //
  394. func newListItem(list *List, item IPanel) *ListItem {
  395. litem := new(ListItem)
  396. litem.Panel.Initialize(0, 0)
  397. litem.item = item
  398. litem.list = list
  399. litem.Panel.Add(item)
  400. litem.SetContentWidth(item.GetPanel().Width())
  401. litem.SetContentHeight(item.GetPanel().Height())
  402. // If this list item is resized, sends event to its child panel
  403. litem.Subscribe(OnResize, func(evname string, ev interface{}) {
  404. item.GetPanel().Dispatch(OnListItemResize, nil)
  405. })
  406. litem.update()
  407. return litem
  408. }
  409. // onMouse receives mouse button events over the list item
  410. func (litem *ListItem) onMouse(evname string, ev interface{}) {
  411. if litem.list.single {
  412. litem.list.setSelection(litem, true, true, true)
  413. } else {
  414. litem.list.setSelection(litem, !litem.selected, true, true)
  415. }
  416. }
  417. // onCursor receives subscribed cursor events over the list item
  418. func (litem *ListItem) onCursor(evname string, ev interface{}) {
  419. if litem.list.dropdown {
  420. litem.list.setSelection(litem, true, true, false)
  421. return
  422. }
  423. }
  424. // SetSelected sets this item selected state
  425. func (litem *ListItem) SetSelected(state bool) {
  426. litem.selected = state
  427. //litem.item.SetSelected2(state)
  428. }
  429. // SetHighlighted sets this item selected state
  430. func (litem *ListItem) SetHighlighted(state bool) {
  431. litem.highlighted = state
  432. //litem.item.SetHighlighted2(state)
  433. }
  434. // updates the list item visual style accordingly to its current state
  435. func (litem *ListItem) update() {
  436. list := litem.list
  437. if litem.selected && !litem.highlighted {
  438. litem.applyStyle(&list.styles.Item.Selected)
  439. return
  440. }
  441. if !litem.selected && litem.highlighted {
  442. litem.applyStyle(&list.styles.Item.Highlighted)
  443. return
  444. }
  445. if litem.selected && litem.highlighted {
  446. litem.applyStyle(&list.styles.Item.SelHigh)
  447. return
  448. }
  449. litem.applyStyle(&list.styles.Item.Normal)
  450. }
  451. // applyStyle applies the specified style to this ListItem
  452. func (litem *ListItem) applyStyle(s *ListItemStyle) {
  453. styleCopy := s.PanelStyle
  454. styleCopy.Padding.Left += litem.padLeft
  455. litem.Panel.ApplyStyle(&styleCopy)
  456. }