splitter.go 6.0 KB

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