scroller.go 14 KB

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