scroller.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  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. "math"
  8. )
  9. type Scroller struct {
  10. Panel // Embedded panel
  11. vert bool // vertical/horizontal scroller flag
  12. styles *ScrollerStyles // pointer to current styles
  13. items []IPanel // list of panels in the scroller
  14. hscroll *ScrollBar // horizontal scroll bar
  15. vscroll *ScrollBar // vertical scroll bar
  16. maxAutoWidth float32 // maximum auto width (if 0, auto width disabled)
  17. maxAutoHeight float32 // maximum auto height (if 0, auto width disabled)
  18. first int // first visible item position
  19. adjustItem bool // adjust item to width or height
  20. focus bool // has keyboard focus
  21. cursorOver bool // mouse is over the list
  22. autoButtonSize bool // scroll button size is adjusted relative to content/view
  23. scrollBarEvent bool
  24. }
  25. type ScrollerStyle BasicStyle
  26. type ScrollerStyles struct {
  27. Normal ScrollerStyle
  28. Over ScrollerStyle
  29. Focus ScrollerStyle
  30. Disabled ScrollerStyle
  31. }
  32. // NewVScroller creates and returns a pointer to a new vertical scroller panel
  33. // with the specified dimensions.
  34. func NewVScroller(width, height float32) *Scroller {
  35. return newScroller(true, width, height)
  36. }
  37. // NewHScroller creates and returns a pointer to a new horizontal scroller panel
  38. // with the specified dimensions.
  39. func NewHScroller(width, height float32) *Scroller {
  40. return newScroller(false, width, height)
  41. }
  42. // newScroller creates and returns a pointer to a new Scroller panel
  43. // with the specified layout orientation and initial dimensions
  44. func newScroller(vert bool, width, height float32) *Scroller {
  45. s := new(Scroller)
  46. s.initialize(vert, width, height)
  47. return s
  48. }
  49. // Clear removes and disposes of all the scroller children
  50. func (s *Scroller) Clear() {
  51. s.Panel.DisposeChildren(true)
  52. s.first = 0
  53. s.hscroll = nil
  54. s.vscroll = nil
  55. s.items = s.items[0:0]
  56. s.update()
  57. s.recalc()
  58. }
  59. // Len return the number of items in the scroller
  60. func (s *Scroller) Len() int {
  61. return len(s.items)
  62. }
  63. // Add appends the specified item to the end of the scroller
  64. func (s *Scroller) Add(item IPanel) {
  65. s.InsertAt(len(s.items), item)
  66. }
  67. // InsertAt inserts an item at the specified position
  68. func (s *Scroller) InsertAt(pos int, item IPanel) {
  69. // Validates position
  70. if pos < 0 || pos > len(s.items) {
  71. panic("Scroller.InsertAt(): Invalid position")
  72. }
  73. item.GetPanel().SetVisible(false)
  74. // Insert item in the items array
  75. s.items = append(s.items, nil)
  76. copy(s.items[pos+1:], s.items[pos:])
  77. s.items[pos] = item
  78. // Insert item in the scroller
  79. s.Panel.Add(item)
  80. s.autoSize()
  81. s.recalc()
  82. // Scroll bar should be on the foreground,
  83. // in relation of all the other child panels.
  84. if s.vscroll != nil {
  85. s.Panel.SetTopChild(s.vscroll)
  86. }
  87. if s.hscroll != nil {
  88. s.Panel.SetTopChild(s.hscroll)
  89. }
  90. }
  91. // RemoveAt removes item from the specified position
  92. func (s *Scroller) RemoveAt(pos int) IPanel {
  93. // Validates position
  94. if pos < 0 || pos >= len(s.items) {
  95. panic("Scroller.RemoveAt(): Invalid position")
  96. }
  97. // Remove event listener
  98. item := s.items[pos]
  99. // Remove item from the items array
  100. copy(s.items[pos:], s.items[pos+1:])
  101. s.items[len(s.items)-1] = nil
  102. s.items = s.items[:len(s.items)-1]
  103. // Remove item from the scroller children
  104. s.Panel.Remove(item)
  105. s.autoSize()
  106. s.recalc()
  107. return item
  108. }
  109. // Remove removes the specified item from the Scroller
  110. func (s *Scroller) Remove(item IPanel) {
  111. for p, curr := range s.items {
  112. if curr == item {
  113. s.RemoveAt(p)
  114. return
  115. }
  116. }
  117. }
  118. // GetItem returns the item at the specified position.
  119. // Returns nil if the position is invalid.
  120. func (s *Scroller) ItemAt(pos int) IPanel {
  121. if pos < 0 || pos >= len(s.items) {
  122. return nil
  123. }
  124. return s.items[pos]
  125. }
  126. // ItemPosition returns the position of the specified item in
  127. // the scroller of -1 if not found
  128. func (s *Scroller) ItemPosition(item IPanel) int {
  129. for pos := 0; pos < len(s.items); pos++ {
  130. if s.items[pos] == item {
  131. return pos
  132. }
  133. }
  134. return -1
  135. }
  136. // First returns the position of the first visible item
  137. func (s *Scroller) First() int {
  138. return s.first
  139. }
  140. // SetFirst set the position of first visible if possible
  141. func (s *Scroller) SetFirst(pos int) {
  142. if pos >= 0 && pos <= s.maxFirst() {
  143. s.first = pos
  144. s.recalc()
  145. }
  146. }
  147. // ScrollDown scrolls the list down one item if possible
  148. func (s *Scroller) ScrollDown() {
  149. max := s.maxFirst()
  150. if s.first >= max {
  151. return
  152. }
  153. s.first++
  154. s.recalc()
  155. }
  156. // ScrollUp scrolls the list up one item if possible
  157. func (s *Scroller) ScrollUp() {
  158. if s.first == 0 {
  159. return
  160. }
  161. s.first--
  162. s.recalc()
  163. }
  164. // ItemVisible returns indication if the item at the specified
  165. // position is completely visible or not
  166. func (s *Scroller) ItemVisible(pos int) bool {
  167. if pos < s.first {
  168. return false
  169. }
  170. // Vertical scroller
  171. if s.vert {
  172. var height float32 = 0
  173. for i := s.first; i < len(s.items); i++ {
  174. item := s.items[pos]
  175. height += item.GetPanel().height
  176. if height > s.height {
  177. return false
  178. }
  179. if pos == i {
  180. return true
  181. }
  182. }
  183. return false
  184. // Horizontal scroller
  185. } else {
  186. var width float32 = 0
  187. for i := s.first; i < len(s.items); i++ {
  188. item := s.items[pos]
  189. width += item.GetPanel().width
  190. if width > s.width {
  191. return false
  192. }
  193. if pos == i {
  194. return true
  195. }
  196. }
  197. return false
  198. }
  199. }
  200. // SetStyles set the scroller styles overriding the default style
  201. func (s *Scroller) SetStyles(ss *ScrollerStyles) {
  202. s.styles = ss
  203. s.update()
  204. }
  205. func (s *Scroller) ApplyStyle(style int) {
  206. switch style {
  207. case StyleOver:
  208. s.applyStyle(&s.styles.Over)
  209. case StyleFocus:
  210. s.applyStyle(&s.styles.Focus)
  211. case StyleNormal:
  212. s.applyStyle(&s.styles.Normal)
  213. case StyleDef:
  214. s.update()
  215. }
  216. }
  217. func (s *Scroller) SetAutoWidth(maxWidth float32) {
  218. s.maxAutoWidth = maxWidth
  219. }
  220. func (s *Scroller) SetAutoHeight(maxHeight float32) {
  221. s.maxAutoHeight = maxHeight
  222. }
  223. func (s *Scroller) SetAutoButtonSize(autoButtonSize bool) {
  224. s.autoButtonSize = autoButtonSize
  225. }
  226. // initialize initializes this scroller and is normally used by other types which contains a scroller
  227. func (s *Scroller) initialize(vert bool, width, height float32) {
  228. s.vert = vert
  229. s.autoButtonSize = true
  230. s.Panel.Initialize(width, height)
  231. s.styles = &StyleDefault().Scroller
  232. s.Panel.Subscribe(OnCursorEnter, s.onCursor)
  233. s.Panel.Subscribe(OnCursorLeave, s.onCursor)
  234. s.Panel.Subscribe(OnScroll, s.onScroll)
  235. s.Panel.Subscribe(OnResize, s.onResize)
  236. s.update()
  237. s.recalc()
  238. }
  239. // onCursor receives subscribed cursor events over the panel
  240. func (s *Scroller) onCursor(evname string, ev interface{}) {
  241. switch evname {
  242. case OnCursorEnter:
  243. s.root.SetScrollFocus(s)
  244. s.cursorOver = true
  245. s.update()
  246. case OnCursorLeave:
  247. s.root.SetScrollFocus(nil)
  248. s.cursorOver = false
  249. s.update()
  250. }
  251. s.root.StopPropagation(Stop3D)
  252. }
  253. // onScroll receives subscriber mouse scroll events when this scroller has
  254. // the scroll focus (set by OnMouseEnter)
  255. func (s *Scroller) onScroll(evname string, ev interface{}) {
  256. sev := ev.(*window.ScrollEvent)
  257. if sev.Yoffset > 0 {
  258. s.ScrollUp()
  259. } else if sev.Yoffset < 0 {
  260. s.ScrollDown()
  261. }
  262. s.root.StopPropagation(Stop3D)
  263. }
  264. // onScroll receives resize events
  265. func (s *Scroller) onResize(evname string, ev interface{}) {
  266. s.recalc()
  267. }
  268. // autoSize resizes the scroller if necessary
  269. func (s *Scroller) autoSize() {
  270. if s.maxAutoWidth == 0 && s.maxAutoHeight == 0 {
  271. return
  272. }
  273. var width float32 = 0
  274. var height float32 = 0
  275. for _, item := range s.items {
  276. panel := item.GetPanel()
  277. if panel.Width() > width {
  278. width = panel.Width()
  279. }
  280. height += panel.TotalHeight()
  281. }
  282. // If auto maximum width enabled
  283. if s.maxAutoWidth > 0 {
  284. if width <= s.maxAutoWidth {
  285. s.SetContentWidth(width)
  286. }
  287. }
  288. // If auto maximum height enabled
  289. if s.maxAutoHeight > 0 {
  290. if height <= s.maxAutoHeight {
  291. s.SetContentHeight(height)
  292. }
  293. }
  294. }
  295. // recalc recalculates the positions and visibilities of all the items
  296. func (s *Scroller) recalc() {
  297. if s.vert {
  298. s.vRecalc()
  299. } else {
  300. s.hRecalc()
  301. }
  302. }
  303. // vRecalc recalculates for the vertical scroller
  304. func (s *Scroller) vRecalc() {
  305. // Checks if scroll bar should be visible or not
  306. scroll := false
  307. if s.first > 0 {
  308. scroll = true
  309. } else {
  310. var posY float32 = 0
  311. for _, item := range s.items[s.first:] {
  312. posY += item.TotalHeight()
  313. if posY > s.height {
  314. scroll = true
  315. break
  316. }
  317. }
  318. }
  319. s.setVScrollBar(scroll)
  320. // Compute size of scroll button
  321. if scroll && s.autoButtonSize {
  322. var totalHeight float32 = 0
  323. for _, item := range s.items {
  324. // TODO OPTIMIZATION
  325. // Break when the view/content proportion becomes smaller than the minimum button size
  326. totalHeight += item.TotalHeight()
  327. }
  328. s.vscroll.SetButtonSize(s.height * s.height/totalHeight)
  329. }
  330. // Items width
  331. width := s.ContentWidth()
  332. if scroll {
  333. width -= s.vscroll.Width()
  334. }
  335. var posY float32 = 0
  336. // Sets positions of all items
  337. for pos, ipan := range s.items {
  338. item := ipan.GetPanel()
  339. if pos < s.first {
  340. item.SetVisible(false)
  341. continue
  342. }
  343. // If item is after last visible, sets not visible
  344. if posY > s.height {
  345. item.SetVisible(false)
  346. continue
  347. }
  348. // Sets item position
  349. item.SetVisible(true)
  350. item.SetPosition(0, posY)
  351. if s.adjustItem {
  352. item.SetWidth(width)
  353. }
  354. posY += ipan.TotalHeight()
  355. }
  356. // Set scroll bar value if recalc was not due by scroll event
  357. if scroll && !s.scrollBarEvent {
  358. s.vscroll.SetValue(float32(s.first) / float32(s.maxFirst()))
  359. }
  360. s.scrollBarEvent = false
  361. }
  362. // hRecalc recalculates for the horizontal scroller
  363. func (s *Scroller) hRecalc() {
  364. // Checks if scroll bar should be visible or not
  365. scroll := false
  366. if s.first > 0 {
  367. scroll = true
  368. } else {
  369. var posX float32 = 0
  370. for _, item := range s.items[s.first:] {
  371. posX += item.GetPanel().Width()
  372. if posX > s.width {
  373. scroll = true
  374. break
  375. }
  376. }
  377. }
  378. s.setHScrollBar(scroll)
  379. // Compute size of scroll button
  380. if scroll && s.autoButtonSize {
  381. var totalWidth float32 = 0
  382. for _, item := range s.items {
  383. // TODO OPTIMIZATION
  384. // Break when the view/content proportion becomes smaller than the minimum button size
  385. totalWidth += item.GetPanel().Width()
  386. }
  387. s.hscroll.SetButtonSize(s.width * s.width/totalWidth)
  388. }
  389. // Items height
  390. height := s.ContentHeight()
  391. if scroll {
  392. height -= s.hscroll.Height()
  393. }
  394. var posX float32 = 0
  395. // Sets positions of all items
  396. for pos, ipan := range s.items {
  397. item := ipan.GetPanel()
  398. // If item is before first visible, sets not visible
  399. if pos < s.first {
  400. item.SetVisible(false)
  401. continue
  402. }
  403. // If item is after last visible, sets not visible
  404. if posX > s.width {
  405. item.SetVisible(false)
  406. continue
  407. }
  408. // Sets item position
  409. item.SetVisible(true)
  410. item.SetPosition(posX, 0)
  411. if s.adjustItem {
  412. item.SetHeight(height)
  413. }
  414. posX += item.Width()
  415. }
  416. // Set scroll bar value if recalc was not due by scroll event
  417. if scroll && !s.scrollBarEvent {
  418. s.hscroll.SetValue(float32(s.first) / float32(s.maxFirst()))
  419. }
  420. s.scrollBarEvent = false
  421. }
  422. // maxFirst returns the maximum position of the first visible item
  423. func (s *Scroller) maxFirst() int {
  424. // Vertical scroller
  425. if s.vert {
  426. var height float32 = 0
  427. pos := len(s.items) - 1
  428. if pos < 0 {
  429. return 0
  430. }
  431. for {
  432. item := s.items[pos]
  433. height += item.GetPanel().Height()
  434. if height > s.Height() {
  435. break
  436. }
  437. pos--
  438. if pos < 0 {
  439. break
  440. }
  441. }
  442. return pos + 1
  443. // Horizontal scroller
  444. } else {
  445. var width float32 = 0
  446. pos := len(s.items) - 1
  447. if pos < 0 {
  448. return 0
  449. }
  450. for {
  451. item := s.items[pos]
  452. width += item.GetPanel().Width()
  453. if width > s.Width() {
  454. break
  455. }
  456. pos--
  457. if pos < 0 {
  458. break
  459. }
  460. }
  461. return pos + 1
  462. }
  463. }
  464. // setVScrollBar sets the visibility state of the vertical scrollbar
  465. func (s *Scroller) setVScrollBar(state bool) {
  466. // Visible
  467. if state {
  468. var scrollWidth float32 = 20
  469. if s.vscroll == nil {
  470. s.vscroll = NewVScrollBar(0, 0)
  471. s.vscroll.SetBorders(0, 0, 0, 1)
  472. s.vscroll.Subscribe(OnChange, s.onScrollBarEvent)
  473. s.Panel.Add(s.vscroll)
  474. }
  475. s.vscroll.SetSize(scrollWidth, s.ContentHeight())
  476. s.vscroll.SetPositionX(s.ContentWidth() - scrollWidth)
  477. s.vscroll.SetPositionY(0)
  478. s.vscroll.recalc()
  479. s.vscroll.SetVisible(true)
  480. // Not visible
  481. } else {
  482. if s.vscroll != nil {
  483. s.vscroll.SetVisible(false)
  484. }
  485. }
  486. }
  487. // setHScrollBar sets the visibility state of the horizontal scrollbar
  488. func (s *Scroller) setHScrollBar(state bool) {
  489. // Visible
  490. if state {
  491. var scrollHeight float32 = 20
  492. if s.hscroll == nil {
  493. s.hscroll = NewHScrollBar(0, 0)
  494. s.hscroll.SetBorders(1, 0, 0, 0)
  495. s.hscroll.Subscribe(OnChange, s.onScrollBarEvent)
  496. s.Panel.Add(s.hscroll)
  497. }
  498. s.hscroll.SetSize(s.ContentWidth(), scrollHeight)
  499. s.hscroll.SetPositionX(0)
  500. s.hscroll.SetPositionY(s.ContentHeight() - scrollHeight)
  501. s.hscroll.recalc()
  502. s.hscroll.SetVisible(true)
  503. // Not visible
  504. } else {
  505. if s.hscroll != nil {
  506. s.hscroll.SetVisible(false)
  507. }
  508. }
  509. }
  510. // onScrollEvent is called when the list scrollbar value changes
  511. func (s *Scroller) onScrollBarEvent(evname string, ev interface{}) {
  512. var pos float64
  513. if s.vert {
  514. pos = s.vscroll.Value()
  515. } else {
  516. pos = s.hscroll.Value()
  517. }
  518. first := int(math.Floor((float64(s.maxFirst()) * pos) + 0.5))
  519. if first == s.first {
  520. return
  521. }
  522. s.scrollBarEvent = true
  523. s.first = first
  524. s.recalc()
  525. }
  526. // update updates the visual state the list and its items
  527. func (s *Scroller) update() {
  528. if s.cursorOver {
  529. s.applyStyle(&s.styles.Over)
  530. return
  531. }
  532. if s.focus {
  533. s.applyStyle(&s.styles.Focus)
  534. return
  535. }
  536. s.applyStyle(&s.styles.Normal)
  537. }
  538. // applyStyle sets the specified style
  539. func (s *Scroller) applyStyle(st *ScrollerStyle) {
  540. s.Panel.ApplyStyle(&st.PanelStyle)
  541. }