| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- // Copyright 2016 The G3N Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package gui
- import (
- "github.com/g3n/engine/math32"
- "github.com/g3n/engine/window"
- "math"
- )
- type Scroller struct {
- Panel // Embedded panel
- vert bool // vertical/horizontal scroller flag
- styles *ScrollerStyles // pointer to current styles
- items []IPanel // list of panels in the scroller
- hscroll *ScrollBar // horizontal scroll bar
- vscroll *ScrollBar // vertical scroll bar
- maxAutoWidth float32 // maximum auto width (if 0, auto width disabled)
- maxAutoHeight float32 // maximum auto height (if 0, auto width disabled)
- first int // first visible item position
- adjustItem bool // adjust item to width or height
- focus bool // has keyboard focus
- cursorOver bool // mouse is over the list
- autoButtonSize bool // scroll button size is adjusted relative to content/view
- scrollBarEvent bool
- }
- type ScrollerStyle struct {
- Border BorderSizes
- Paddings BorderSizes
- BorderColor math32.Color4
- BgColor math32.Color
- FgColor math32.Color
- }
- type ScrollerStyles struct {
- Normal ScrollerStyle
- Over ScrollerStyle
- Focus ScrollerStyle
- Disabled ScrollerStyle
- }
- // NewVScroller creates and returns a pointer to a new vertical scroller panel
- // with the specified dimensions.
- func NewVScroller(width, height float32) *Scroller {
- return newScroller(true, width, height)
- }
- // NewHScroller creates and returns a pointer to a new horizontal scroller panel
- // with the specified dimensions.
- func NewHScroller(width, height float32) *Scroller {
- return newScroller(false, width, height)
- }
- // newScroller creates and returns a pointer to a new Scroller panel
- // with the specified layout orientation and initial dimensions
- func newScroller(vert bool, width, height float32) *Scroller {
- s := new(Scroller)
- s.initialize(vert, width, height)
- return s
- }
- // Clear removes and disposes of all the scroller children
- func (s *Scroller) Clear() {
- s.Panel.DisposeChildren(true)
- s.first = 0
- s.hscroll = nil
- s.vscroll = nil
- s.items = s.items[0:0]
- s.update()
- s.recalc()
- }
- // Len return the number of items in the scroller
- func (s *Scroller) Len() int {
- return len(s.items)
- }
- // Add appends the specified item to the end of the scroller
- func (s *Scroller) Add(item IPanel) {
- s.InsertAt(len(s.items), item)
- }
- // InsertAt inserts an item at the specified position
- func (s *Scroller) InsertAt(pos int, item IPanel) {
- // Validates position
- if pos < 0 || pos > len(s.items) {
- panic("Scroller.InsertAt(): Invalid position")
- }
- item.GetPanel().SetVisible(false)
- // Insert item in the items array
- s.items = append(s.items, nil)
- copy(s.items[pos+1:], s.items[pos:])
- s.items[pos] = item
- // Insert item in the scroller
- s.Panel.Add(item)
- s.autoSize()
- s.recalc()
- // Scroll bar should be on the foreground,
- // in relation of all the other child panels.
- if s.vscroll != nil {
- s.Panel.SetTopChild(s.vscroll)
- }
- if s.hscroll != nil {
- s.Panel.SetTopChild(s.hscroll)
- }
- }
- // RemoveAt removes item from the specified position
- func (s *Scroller) RemoveAt(pos int) IPanel {
- // Validates position
- if pos < 0 || pos >= len(s.items) {
- panic("Scroller.RemoveAt(): Invalid position")
- }
- // Remove event listener
- item := s.items[pos]
- // Remove item from the items array
- copy(s.items[pos:], s.items[pos+1:])
- s.items[len(s.items)-1] = nil
- s.items = s.items[:len(s.items)-1]
- // Remove item from the scroller children
- s.Panel.Remove(item)
- s.autoSize()
- s.recalc()
- return item
- }
- // Remove removes the specified item from the Scroller
- func (s *Scroller) Remove(item IPanel) {
- for p, curr := range s.items {
- if curr == item {
- s.RemoveAt(p)
- return
- }
- }
- }
- // GetItem returns the item at the specified position.
- // Returns nil if the position is invalid.
- func (s *Scroller) ItemAt(pos int) IPanel {
- if pos < 0 || pos >= len(s.items) {
- return nil
- }
- return s.items[pos]
- }
- // ItemPosition returns the position of the specified item in
- // the scroller of -1 if not found
- func (s *Scroller) ItemPosition(item IPanel) int {
- for pos := 0; pos < len(s.items); pos++ {
- if s.items[pos] == item {
- return pos
- }
- }
- return -1
- }
- // First returns the position of the first visible item
- func (s *Scroller) First() int {
- return s.first
- }
- // SetFirst set the position of first visible if possible
- func (s *Scroller) SetFirst(pos int) {
- if pos >= 0 && pos <= s.maxFirst() {
- s.first = pos
- s.recalc()
- }
- }
- // ScrollDown scrolls the list down one item if possible
- func (s *Scroller) ScrollDown() {
- max := s.maxFirst()
- if s.first >= max {
- return
- }
- s.first++
- s.recalc()
- }
- // ScrollUp scrolls the list up one item if possible
- func (s *Scroller) ScrollUp() {
- if s.first == 0 {
- return
- }
- s.first--
- s.recalc()
- }
- // ItemVisible returns indication if the item at the specified
- // position is completely visible or not
- func (s *Scroller) ItemVisible(pos int) bool {
- if pos < s.first {
- return false
- }
- // Vertical scroller
- if s.vert {
- var height float32 = 0
- for i := s.first; i < len(s.items); i++ {
- item := s.items[pos]
- height += item.GetPanel().height
- if height > s.height {
- return false
- }
- if pos == i {
- return true
- }
- }
- return false
- // Horizontal scroller
- } else {
- var width float32 = 0
- for i := s.first; i < len(s.items); i++ {
- item := s.items[pos]
- width += item.GetPanel().width
- if width > s.width {
- return false
- }
- if pos == i {
- return true
- }
- }
- return false
- }
- }
- // SetStyles set the scroller styles overriding the default style
- func (s *Scroller) SetStyles(ss *ScrollerStyles) {
- s.styles = ss
- s.update()
- }
- func (s *Scroller) ApplyStyle(style int) {
- switch style {
- case StyleOver:
- s.applyStyle(&s.styles.Over)
- case StyleFocus:
- s.applyStyle(&s.styles.Focus)
- case StyleNormal:
- s.applyStyle(&s.styles.Normal)
- case StyleDef:
- s.update()
- }
- }
- func (s *Scroller) SetAutoWidth(maxWidth float32) {
- s.maxAutoWidth = maxWidth
- }
- func (s *Scroller) SetAutoHeight(maxHeight float32) {
- s.maxAutoHeight = maxHeight
- }
- func (s *Scroller) SetAutoButtonSize(autoButtonSize bool) {
- s.autoButtonSize = autoButtonSize
- }
- // initialize initializes this scroller and is normally used by other types which contains a scroller
- func (s *Scroller) initialize(vert bool, width, height float32) {
- s.vert = vert
- s.autoButtonSize = true
- s.Panel.Initialize(width, height)
- s.styles = &StyleDefault().Scroller
- s.Panel.Subscribe(OnCursorEnter, s.onCursor)
- s.Panel.Subscribe(OnCursorLeave, s.onCursor)
- s.Panel.Subscribe(OnScroll, s.onScroll)
- s.Panel.Subscribe(OnResize, s.onResize)
- s.update()
- s.recalc()
- }
- // onCursor receives subscribed cursor events over the panel
- func (s *Scroller) onCursor(evname string, ev interface{}) {
- switch evname {
- case OnCursorEnter:
- s.root.SetScrollFocus(s)
- s.cursorOver = true
- s.update()
- case OnCursorLeave:
- s.root.SetScrollFocus(nil)
- s.cursorOver = false
- s.update()
- }
- s.root.StopPropagation(Stop3D)
- }
- // onScroll receives subscriber mouse scroll events when this scroller has
- // the scroll focus (set by OnMouseEnter)
- func (s *Scroller) onScroll(evname string, ev interface{}) {
- sev := ev.(*window.ScrollEvent)
- if sev.Yoffset > 0 {
- s.ScrollUp()
- } else if sev.Yoffset < 0 {
- s.ScrollDown()
- }
- s.root.StopPropagation(Stop3D)
- }
- // onScroll receives resize events
- func (s *Scroller) onResize(evname string, ev interface{}) {
- s.recalc()
- }
- // autoSize resizes the scroller if necessary
- func (s *Scroller) autoSize() {
- if s.maxAutoWidth == 0 && s.maxAutoHeight == 0 {
- return
- }
- var width float32 = 0
- var height float32 = 0
- for _, item := range s.items {
- panel := item.GetPanel()
- if panel.Width() > width {
- width = panel.Width()
- }
- height += panel.TotalHeight()
- }
- // If auto maximum width enabled
- if s.maxAutoWidth > 0 {
- if width <= s.maxAutoWidth {
- s.SetContentWidth(width)
- }
- }
- // If auto maximum height enabled
- if s.maxAutoHeight > 0 {
- if height <= s.maxAutoHeight {
- s.SetContentHeight(height)
- }
- }
- }
- // recalc recalculates the positions and visibilities of all the items
- func (s *Scroller) recalc() {
- if s.vert {
- s.vRecalc()
- } else {
- s.hRecalc()
- }
- }
- // vRecalc recalculates for the vertical scroller
- func (s *Scroller) vRecalc() {
- // Checks if scroll bar should be visible or not
- scroll := false
- if s.first > 0 {
- scroll = true
- } else {
- var posY float32 = 0
- for _, item := range s.items[s.first:] {
- posY += item.TotalHeight()
- if posY >= s.height {
- scroll = true
- break
- }
- }
- }
- s.setVScrollBar(scroll)
- // Compute size of scroll button
- if scroll && s.autoButtonSize {
- var totalHeight float32 = 0
- for _, item := range s.items {
- // TODO OPTIMIZATION
- // Break when the view/content proportion becomes smaller than the minimum button size
- totalHeight += item.TotalHeight()
- }
- s.vscroll.SetButtonSize(s.height * s.height/totalHeight)
- }
- // Items width
- width := s.ContentWidth()
- if scroll {
- width -= s.vscroll.Width()
- }
- var posY float32 = 0
- // Sets positions of all items
- for pos, ipan := range s.items {
- item := ipan.GetPanel()
- if pos < s.first {
- item.SetVisible(false)
- continue
- }
- // If item is after last visible, sets not visible
- if posY > s.height {
- item.SetVisible(false)
- continue
- }
- // Sets item position
- item.SetVisible(true)
- item.SetPosition(0, posY)
- if s.adjustItem {
- item.SetWidth(width)
- }
- posY += ipan.TotalHeight()
- }
- // Set scroll bar value if recalc was not due by scroll event
- if scroll && !s.scrollBarEvent {
- s.vscroll.SetValue(float32(s.first) / float32(s.maxFirst()))
- }
- s.scrollBarEvent = false
- }
- // hRecalc recalculates for the horizontal scroller
- func (s *Scroller) hRecalc() {
- // Checks if scroll bar should be visible or not
- scroll := false
- if s.first > 0 {
- scroll = true
- } else {
- var posX float32 = 0
- for _, item := range s.items[s.first:] {
- posX += item.GetPanel().Width()
- if posX >= s.width {
- scroll = true
- break
- }
- }
- }
- s.setHScrollBar(scroll)
- // Compute size of scroll button
- if scroll && s.autoButtonSize {
- var totalWidth float32 = 0
- for _, item := range s.items {
- // TODO OPTIMIZATION
- // Break when the view/content proportion becomes smaller than the minimum button size
- totalWidth += item.GetPanel().Width()
- }
- s.hscroll.SetButtonSize(s.width * s.width/totalWidth)
- }
- // Items height
- height := s.ContentHeight()
- if scroll {
- height -= s.hscroll.Height()
- }
- var posX float32 = 0
- // Sets positions of all items
- for pos, ipan := range s.items {
- item := ipan.GetPanel()
- // If item is before first visible, sets not visible
- if pos < s.first {
- item.SetVisible(false)
- continue
- }
- // If item is after last visible, sets not visible
- if posX > s.width {
- item.SetVisible(false)
- continue
- }
- // Sets item position
- item.SetVisible(true)
- item.SetPosition(posX, 0)
- if s.adjustItem {
- item.SetHeight(height)
- }
- posX += item.Width()
- }
- // Set scroll bar value if recalc was not due by scroll event
- if scroll && !s.scrollBarEvent {
- s.hscroll.SetValue(float32(s.first) / float32(s.maxFirst()))
- }
- s.scrollBarEvent = false
- }
- // maxFirst returns the maximum position of the first visible item
- func (s *Scroller) maxFirst() int {
- // Vertical scroller
- if s.vert {
- var height float32 = 0
- pos := len(s.items) - 1
- if pos < 0 {
- return 0
- }
- for {
- item := s.items[pos]
- height += item.GetPanel().Height()
- if height > s.Height() {
- break
- }
- pos--
- if pos < 0 {
- break
- }
- }
- return pos + 1
- // Horizontal scroller
- } else {
- var width float32 = 0
- pos := len(s.items) - 1
- if pos < 0 {
- return 0
- }
- for {
- item := s.items[pos]
- width += item.GetPanel().Width()
- if width > s.Width() {
- break
- }
- pos--
- if pos < 0 {
- break
- }
- }
- return pos + 1
- }
- }
- // setVScrollBar sets the visibility state of the vertical scrollbar
- func (s *Scroller) setVScrollBar(state bool) {
- // Visible
- if state {
- var scrollWidth float32 = 20
- if s.vscroll == nil {
- s.vscroll = NewVScrollBar(0, 0)
- s.vscroll.SetBorders(0, 0, 0, 1)
- s.vscroll.Subscribe(OnChange, s.onScrollBarEvent)
- s.Panel.Add(s.vscroll)
- }
- s.vscroll.SetSize(scrollWidth, s.ContentHeight())
- s.vscroll.SetPositionX(s.ContentWidth() - scrollWidth)
- s.vscroll.SetPositionY(0)
- s.vscroll.recalc()
- s.vscroll.SetVisible(true)
- // Not visible
- } else {
- if s.vscroll != nil {
- s.vscroll.SetVisible(false)
- }
- }
- }
- // setHScrollBar sets the visibility state of the horizontal scrollbar
- func (s *Scroller) setHScrollBar(state bool) {
- // Visible
- if state {
- var scrollHeight float32 = 20
- if s.hscroll == nil {
- s.hscroll = NewHScrollBar(0, 0)
- s.hscroll.SetBorders(1, 0, 0, 0)
- s.hscroll.Subscribe(OnChange, s.onScrollBarEvent)
- s.Panel.Add(s.hscroll)
- }
- s.hscroll.SetSize(s.ContentWidth(), scrollHeight)
- s.hscroll.SetPositionX(0)
- s.hscroll.SetPositionY(s.ContentHeight() - scrollHeight)
- s.hscroll.recalc()
- s.hscroll.SetVisible(true)
- // Not visible
- } else {
- if s.hscroll != nil {
- s.hscroll.SetVisible(false)
- }
- }
- }
- // onScrollEvent is called when the list scrollbar value changes
- func (s *Scroller) onScrollBarEvent(evname string, ev interface{}) {
- var pos float64
- if s.vert {
- pos = s.vscroll.Value()
- } else {
- pos = s.hscroll.Value()
- }
- first := int(math.Floor((float64(s.maxFirst()) * pos) + 0.5))
- if first == s.first {
- return
- }
- s.scrollBarEvent = true
- s.first = first
- s.recalc()
- }
- // update updates the visual state the list and its items
- func (s *Scroller) update() {
- if s.cursorOver {
- s.applyStyle(&s.styles.Over)
- return
- }
- if s.focus {
- s.applyStyle(&s.styles.Focus)
- return
- }
- s.applyStyle(&s.styles.Normal)
- }
- // applyStyle sets the specified style
- func (s *Scroller) applyStyle(st *ScrollerStyle) {
- s.SetBordersFrom(&st.Border)
- s.SetBordersColor4(&st.BorderColor)
- s.SetPaddingsFrom(&st.Paddings)
- s.SetColor(&st.BgColor)
- }
|