splitter.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. )
  9. // Splitter is a GUI element that splits two panels and can be adjusted
  10. type Splitter struct {
  11. Panel // Embedded panel
  12. P0 Panel // Left/Top panel
  13. P1 Panel // Right/Bottom panel
  14. styles *SplitterStyles // pointer to current styles
  15. spacer Panel // spacer panel
  16. horiz bool // horizontal or vertical splitter
  17. pos float32 // relative position of the center of the spacer panel (0 to 1)
  18. posLast float32 // last position in pixels of the mouse cursor when dragging
  19. pressed bool // mouse button is pressed and dragging
  20. mouseOver bool // mouse is over the spacer panel
  21. }
  22. // SplitterStyle contains the styling of a Splitter
  23. type SplitterStyle struct {
  24. SpacerBorderColor math32.Color4
  25. SpacerColor math32.Color4
  26. SpacerSize float32
  27. }
  28. // SplitterStyles contains a SplitterStyle for each valid GUI state
  29. type SplitterStyles struct {
  30. Normal SplitterStyle
  31. Over SplitterStyle
  32. Drag SplitterStyle
  33. }
  34. // NewHSplitter creates and returns a pointer to a new horizontal splitter
  35. // widget with the specified initial dimensions
  36. func NewHSplitter(width, height float32) *Splitter {
  37. return newSplitter(true, width, height)
  38. }
  39. // NewVSplitter creates and returns a pointer to a new vertical splitter
  40. // widget with the specified initial dimensions
  41. func NewVSplitter(width, height float32) *Splitter {
  42. return newSplitter(false, width, height)
  43. }
  44. // newSpliter creates and returns a pointer of a new splitter with
  45. // the specified orientation and initial dimensions.
  46. func newSplitter(horiz bool, width, height float32) *Splitter {
  47. s := new(Splitter)
  48. s.horiz = horiz
  49. s.styles = &StyleDefault().Splitter
  50. s.Panel.Initialize(width, height)
  51. // Initialize left/top panel
  52. s.P0.Initialize(0, 0)
  53. s.Panel.Add(&s.P0)
  54. // Initialize right/bottom panel
  55. s.P1.Initialize(0, 0)
  56. s.Panel.Add(&s.P1)
  57. // Initialize spacer panel
  58. s.spacer.Initialize(0, 0)
  59. s.Panel.Add(&s.spacer)
  60. if horiz {
  61. s.spacer.SetBorders(0, 1, 0, 1)
  62. s.pos = 0.5
  63. } else {
  64. s.spacer.SetBorders(1, 0, 1, 0)
  65. s.pos = 0.5
  66. }
  67. s.Subscribe(OnResize, s.onResize)
  68. s.spacer.Subscribe(OnMouseDown, s.onMouse)
  69. s.spacer.Subscribe(OnMouseUp, s.onMouse)
  70. s.spacer.Subscribe(OnCursor, s.onCursor)
  71. s.spacer.Subscribe(OnCursorEnter, s.onCursor)
  72. s.spacer.Subscribe(OnCursorLeave, s.onCursor)
  73. s.update()
  74. s.recalc()
  75. return s
  76. }
  77. // SetSplit sets the position of the splitter bar.
  78. // It accepts a value from 0.0 to 1.0
  79. func (s *Splitter) SetSplit(pos float32) {
  80. s.setSplit(pos)
  81. s.recalc()
  82. }
  83. // Split returns the current position of the splitter bar.
  84. // It returns a value from 0.0 to 1.0
  85. func (s *Splitter) Split() float32 {
  86. return s.pos
  87. }
  88. // onResize receives subscribed resize events for the whole splitter panel
  89. func (s *Splitter) onResize(evname string, ev interface{}) {
  90. s.recalc()
  91. }
  92. // onMouse receives subscribed mouse events over the spacer panel
  93. func (s *Splitter) onMouse(evname string, ev interface{}) {
  94. mev := ev.(*window.MouseEvent)
  95. switch evname {
  96. case OnMouseDown:
  97. s.pressed = true
  98. if s.horiz {
  99. s.posLast = mev.Xpos
  100. } else {
  101. s.posLast = mev.Ypos
  102. }
  103. s.root.SetMouseFocus(&s.spacer)
  104. case OnMouseUp:
  105. s.pressed = false
  106. s.root.SetCursorNormal()
  107. s.root.SetMouseFocus(nil)
  108. default:
  109. }
  110. s.root.StopPropagation(Stop3D)
  111. }
  112. // onCursor receives subscribed cursor events over the spacer panel
  113. func (s *Splitter) onCursor(evname string, ev interface{}) {
  114. if evname == OnCursorEnter {
  115. if s.horiz {
  116. s.root.SetCursorHResize()
  117. } else {
  118. s.root.SetCursorVResize()
  119. }
  120. s.mouseOver = true
  121. s.update()
  122. } else if evname == OnCursorLeave {
  123. s.root.SetCursorNormal()
  124. s.mouseOver = false
  125. s.update()
  126. } else if evname == OnCursor {
  127. if !s.pressed {
  128. return
  129. }
  130. cev := ev.(*window.CursorEvent)
  131. var delta float32
  132. pos := s.pos
  133. if s.horiz {
  134. delta = cev.Xpos - s.posLast
  135. s.posLast = cev.Xpos
  136. pos += delta / s.ContentWidth()
  137. } else {
  138. delta = cev.Ypos - s.posLast
  139. s.posLast = cev.Ypos
  140. pos += delta / s.ContentHeight()
  141. }
  142. s.setSplit(pos)
  143. s.recalc()
  144. }
  145. s.root.StopPropagation(Stop3D)
  146. }
  147. // setSplit sets the validated and clamped split position from the received value.
  148. func (s *Splitter) setSplit(pos float32) {
  149. if pos < 0 {
  150. s.pos = 0
  151. } else if pos > 1 {
  152. s.pos = 1
  153. } else {
  154. s.pos = pos
  155. }
  156. }
  157. // update updates the splitter visual state
  158. func (s *Splitter) update() {
  159. if s.pressed {
  160. s.applyStyle(&s.styles.Drag)
  161. return
  162. }
  163. if s.mouseOver {
  164. s.applyStyle(&s.styles.Over)
  165. return
  166. }
  167. s.applyStyle(&s.styles.Normal)
  168. }
  169. // applyStyle applies the specified splitter style
  170. func (s *Splitter) applyStyle(ss *SplitterStyle) {
  171. s.spacer.SetBordersColor4(&ss.SpacerBorderColor)
  172. s.spacer.SetColor4(&ss.SpacerColor)
  173. if s.horiz {
  174. s.spacer.SetWidth(ss.SpacerSize)
  175. } else {
  176. s.spacer.SetHeight(ss.SpacerSize)
  177. }
  178. }
  179. // recalc relcalculates the position and sizes of the internal panels
  180. func (s *Splitter) recalc() {
  181. width := s.ContentWidth()
  182. height := s.ContentHeight()
  183. if s.horiz {
  184. // Calculate x position for spacer panel
  185. spx := width*s.pos - s.spacer.Width()/2
  186. if spx < 0 {
  187. spx = 0
  188. } else if spx > width-s.spacer.Width() {
  189. spx = width - s.spacer.Width()
  190. }
  191. // Left panel
  192. s.P0.SetPosition(0, 0)
  193. s.P0.SetSize(spx, height)
  194. // Spacer panel
  195. s.spacer.SetPosition(spx, 0)
  196. s.spacer.SetHeight(height)
  197. // Right panel
  198. s.P1.SetPosition(spx+s.spacer.Width(), 0)
  199. s.P1.SetSize(width-spx-s.spacer.Width(), height)
  200. } else {
  201. // Calculate y position for spacer panel
  202. spy := height*s.pos - s.spacer.Height()/2
  203. if spy < 0 {
  204. spy = 0
  205. } else if spy > height-s.spacer.Height() {
  206. spy = height - s.spacer.Height()
  207. }
  208. // Top panel
  209. s.P0.SetPosition(0, 0)
  210. s.P0.SetSize(width, spy)
  211. // Spacer panel
  212. s.spacer.SetPosition(0, spy)
  213. s.spacer.SetWidth(width)
  214. // Bottom panel
  215. s.P1.SetPosition(0, spy+s.spacer.Height())
  216. s.P1.SetSize(width, height-spy-s.spacer.Height())
  217. }
  218. }