scroller.go 13 KB

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