root.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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/core"
  7. "github.com/g3n/engine/gls"
  8. "github.com/g3n/engine/window"
  9. "sort"
  10. )
  11. // Root is the container and dispatcher of panel events
  12. type Root struct {
  13. Panel // embedded panel
  14. core.TimerManager // embedded TimerManager
  15. gs *gls.GLS // OpenGL state
  16. win window.IWindow // Window
  17. stopPropagation int // stop event propagation bitmask
  18. keyFocus IPanel // current child panel with key focus
  19. mouseFocus IPanel // current child panel with mouse focus
  20. scrollFocus IPanel // current child panel with scroll focus
  21. modalPanel IPanel // current modal panel
  22. targets []IPanel // preallocated list of target panels
  23. }
  24. // Types of event propagation stopping.
  25. const (
  26. StopGUI = 0x01 // Stop event propagation to GUI
  27. Stop3D = 0x02 // Stop event propagation to 3D
  28. StopAll = StopGUI | Stop3D // Stop event propagation
  29. )
  30. // NewRoot creates and returns a pointer to a gui root panel for the specified window
  31. func NewRoot(gs *gls.GLS, win window.IWindow) *Root {
  32. r := new(Root)
  33. r.gs = gs
  34. r.win = win
  35. r.root = r
  36. r.Panel.Initialize(0, 0)
  37. r.TimerManager.Initialize()
  38. // Set the size of the root panel based on the window framebuffer size
  39. width, height := win.FramebufferSize()
  40. r.SetSize(float32(width), float32(height))
  41. // For optimization, set this root panel as not renderable as in most cases
  42. // it is used only as a container
  43. r.SetRenderable(false)
  44. // Subscribe to window events
  45. r.SubscribeWin()
  46. r.targets = []IPanel{}
  47. return r
  48. }
  49. // SubscribeWin subscribes this root panel to window events
  50. func (r *Root) SubscribeWin() {
  51. r.win.Subscribe(window.OnKeyUp, r.onKey)
  52. r.win.Subscribe(window.OnKeyDown, r.onKey)
  53. r.win.Subscribe(window.OnKeyRepeat, r.onKey)
  54. r.win.Subscribe(window.OnChar, r.onChar)
  55. r.win.Subscribe(window.OnMouseUp, r.onMouse)
  56. r.win.Subscribe(window.OnMouseDown, r.onMouse)
  57. r.win.Subscribe(window.OnCursor, r.onCursor)
  58. r.win.Subscribe(window.OnScroll, r.onScroll)
  59. r.win.Subscribe(window.OnWindowSize, r.onWindowSize)
  60. r.win.Subscribe(window.OnFrame, r.onFrame)
  61. }
  62. // Add adds the specified panel to the root container list of children
  63. // Overrides the Panel version because it needs to set the root panel field
  64. func (r *Root) Add(ipan IPanel) {
  65. // Sets the root panel field of the child to be added
  66. ipan.GetPanel().root = r
  67. // Add this panel to the root panel children.
  68. // This will also set the root panel for all the child children
  69. // and the z coordinates of all the panel tree graph.
  70. r.Panel.Add(ipan)
  71. }
  72. // Window returns the associated IWindow.
  73. func (r *Root) Window() window.IWindow {
  74. return r.win
  75. }
  76. // SetModal sets the modal panel.
  77. // If there is a modal panel, only events for this panel are dispatched
  78. // To remove the modal panel call this function with a nil panel.
  79. func (r *Root) SetModal(ipan IPanel) {
  80. r.modalPanel = ipan
  81. }
  82. // SetKeyFocus sets the panel which will receive all keyboard events
  83. // Passing nil will remove the focus (if any)
  84. func (r *Root) SetKeyFocus(ipan IPanel) {
  85. if r.keyFocus != nil {
  86. // If this panel is already in focus, nothing to do
  87. if ipan != nil {
  88. if r.keyFocus.GetPanel() == ipan.GetPanel() {
  89. return
  90. }
  91. }
  92. r.keyFocus.LostKeyFocus()
  93. }
  94. r.keyFocus = ipan
  95. }
  96. // ClearKeyFocus clears the key focus panel (if any) without
  97. // calling LostKeyFocus() for previous focused panel
  98. func (r *Root) ClearKeyFocus() {
  99. r.keyFocus = nil
  100. }
  101. // SetMouseFocus sets the panel which will receive all mouse events
  102. // Passing nil will restore the default event processing
  103. func (r *Root) SetMouseFocus(ipan IPanel) {
  104. r.mouseFocus = ipan
  105. }
  106. // SetScrollFocus sets the panel which will receive all scroll events
  107. // Passing nil will restore the default event processing
  108. func (r *Root) SetScrollFocus(ipan IPanel) {
  109. r.scrollFocus = ipan
  110. }
  111. // HasKeyFocus checks if the specified panel has the key focus
  112. func (r *Root) HasKeyFocus(ipan IPanel) bool {
  113. if r.keyFocus == nil {
  114. return false
  115. }
  116. if r.keyFocus.GetPanel() == ipan.GetPanel() {
  117. return true
  118. }
  119. return false
  120. }
  121. // HasMouseFocus checks if the specified panel has the mouse focus
  122. func (r *Root) HasMouseFocus(ipan IPanel) bool {
  123. if r.mouseFocus == nil {
  124. return false
  125. }
  126. if r.mouseFocus.GetPanel() == ipan.GetPanel() {
  127. return true
  128. }
  129. return false
  130. }
  131. // StopPropagation stops the propagation of the current event
  132. // to outside the root panel (for example the 3D camera)
  133. func (r *Root) StopPropagation(events int) {
  134. r.stopPropagation |= events
  135. }
  136. // SetCursorNormal sets the cursor over the associated window to the standard type.
  137. func (r *Root) SetCursorNormal() {
  138. r.win.SetStandardCursor(window.ArrowCursor)
  139. }
  140. // SetCursorText sets the cursor over the associated window to the I-Beam type.
  141. func (r *Root) SetCursorText() {
  142. r.win.SetStandardCursor(window.IBeamCursor)
  143. }
  144. // SetCursorCrosshair sets the cursor over the associated window to the crosshair type.
  145. func (r *Root) SetCursorCrosshair() {
  146. r.win.SetStandardCursor(window.CrosshairCursor)
  147. }
  148. // SetCursorHand sets the cursor over the associated window to the hand type.
  149. func (r *Root) SetCursorHand() {
  150. r.win.SetStandardCursor(window.HandCursor)
  151. }
  152. // SetCursorHResize sets the cursor over the associated window to the horizontal resize type.
  153. func (r *Root) SetCursorHResize() {
  154. r.win.SetStandardCursor(window.HResizeCursor)
  155. }
  156. // SetCursorVResize sets the cursor over the associated window to the vertical resize type.
  157. func (r *Root) SetCursorVResize() {
  158. r.win.SetStandardCursor(window.VResizeCursor)
  159. }
  160. // SetCursorDiag1 sets the cursor over the associated window to the diagonal (/) resize type.
  161. func (r *Root) SetCursorDiagResize1() {
  162. r.win.SetStandardCursor(window.DiagResize1Cursor)
  163. }
  164. // SetCursorDiag2 sets the cursor over the associated window to the diagonal (\) resize type.
  165. func (r *Root) SetCursorDiagResize2() {
  166. r.win.SetStandardCursor(window.DiagResize2Cursor)
  167. }
  168. // TODO allow setting a custom cursor
  169. // onKey is called when key events are received
  170. func (r *Root) onKey(evname string, ev interface{}) {
  171. // If no panel has the key focus, nothing to do
  172. if r.keyFocus == nil {
  173. return
  174. }
  175. // Checks modal panel
  176. if !r.canDispatch(r.keyFocus) {
  177. return
  178. }
  179. // Dispatch window.KeyEvent to focused panel subscribers
  180. r.stopPropagation = 0
  181. r.keyFocus.GetPanel().Dispatch(evname, ev)
  182. // If requested, stop propagation of event outside the root gui
  183. if (r.stopPropagation & Stop3D) != 0 {
  184. r.win.CancelDispatch()
  185. }
  186. }
  187. // onChar is called when char events are received
  188. func (r *Root) onChar(evname string, ev interface{}) {
  189. // If no panel has the key focus, nothing to do
  190. if r.keyFocus == nil {
  191. return
  192. }
  193. // Checks modal panel
  194. if !r.canDispatch(r.keyFocus) {
  195. return
  196. }
  197. // Dispatch window.CharEvent to focused panel subscribers
  198. r.stopPropagation = 0
  199. r.keyFocus.GetPanel().Dispatch(evname, ev)
  200. // If requested, stopj propagation of event outside the root gui
  201. if (r.stopPropagation & Stop3D) != 0 {
  202. r.win.CancelDispatch()
  203. }
  204. }
  205. // onMouse is called when mouse button events are received
  206. func (r *Root) onMouse(evname string, ev interface{}) {
  207. mev := ev.(*window.MouseEvent)
  208. r.sendPanels(mev.Xpos, mev.Ypos, evname, ev)
  209. }
  210. // onCursor is called when (mouse) cursor events are received
  211. func (r *Root) onCursor(evname string, ev interface{}) {
  212. cev := ev.(*window.CursorEvent)
  213. r.sendPanels(cev.Xpos, cev.Ypos, evname, ev)
  214. }
  215. // sendPanel sends a mouse or cursor event to focused panel or panels
  216. // which contain the specified screen position
  217. func (r *Root) sendPanels(x, y float32, evname string, ev interface{}) {
  218. // Apply scale of window (for HiDPI support)
  219. sX64, sY64 := r.Window().Scale()
  220. x /= float32(sX64)
  221. y /= float32(sY64)
  222. // If there is panel with MouseFocus send only to this panel
  223. if r.mouseFocus != nil {
  224. // Checks modal panel
  225. if !r.canDispatch(r.mouseFocus) {
  226. return
  227. }
  228. r.mouseFocus.GetPanel().Dispatch(evname, ev)
  229. if (r.stopPropagation & Stop3D) != 0 {
  230. r.win.CancelDispatch()
  231. }
  232. return
  233. }
  234. // Clear list of panels which contains the mouse position
  235. r.targets = r.targets[0:0]
  236. // checkPanel checks recursively if the specified panel and
  237. // any of its children contain the mouse position
  238. var checkPanel func(ipan IPanel)
  239. checkPanel = func(ipan IPanel) {
  240. pan := ipan.GetPanel()
  241. // If panel not visible or not enabled, ignore
  242. if !pan.Visible() || !pan.Enabled() {
  243. return
  244. }
  245. // Checks if this panel contains the mouse position
  246. found := pan.InsideBorders(x, y)
  247. if found {
  248. r.targets = append(r.targets, ipan)
  249. } else {
  250. // If OnCursorEnter previously sent, sends OnCursorLeave with a nil event
  251. if pan.cursorEnter {
  252. pan.Dispatch(OnCursorLeave, nil)
  253. pan.cursorEnter = false
  254. }
  255. // If mouse button was pressed, sends event informing mouse down outside of the panel
  256. if evname == OnMouseDown {
  257. pan.Dispatch(OnMouseOut, ev)
  258. }
  259. }
  260. // Checks if any of its children also contains the position
  261. for _, child := range pan.Children() {
  262. ipan, ok := child.(IPanel)
  263. if ok {
  264. checkPanel(ipan)
  265. }
  266. }
  267. }
  268. // Checks all children of this root node
  269. for _, iobj := range r.Node.Children() {
  270. ipan, ok := iobj.(IPanel)
  271. if !ok {
  272. continue
  273. }
  274. checkPanel(ipan)
  275. }
  276. // No panels found
  277. if len(r.targets) == 0 {
  278. // If event is mouse click, removes the keyboard focus
  279. if evname == OnMouseDown {
  280. r.SetKeyFocus(nil)
  281. }
  282. return
  283. }
  284. // Sorts panels by absolute Z with the most foreground panels first
  285. // and sends event to all panels or until a stop is requested
  286. sort.Slice(r.targets, func(i, j int) bool {
  287. iz := r.targets[i].GetPanel().Position().Z
  288. jz := r.targets[j].GetPanel().Position().Z
  289. return iz < jz
  290. })
  291. r.stopPropagation = 0
  292. // Send events to panels
  293. for _, ipan := range r.targets {
  294. // Checks modal panel
  295. if !r.canDispatch(ipan) {
  296. continue
  297. }
  298. pan := ipan.GetPanel()
  299. // Cursor position event
  300. if evname == OnCursor {
  301. pan.Dispatch(evname, ev)
  302. if !pan.cursorEnter {
  303. pan.Dispatch(OnCursorEnter, ev)
  304. pan.cursorEnter = true
  305. }
  306. // Mouse button event
  307. } else {
  308. pan.Dispatch(evname, ev)
  309. }
  310. if (r.stopPropagation & StopGUI) != 0 {
  311. break
  312. }
  313. }
  314. // Stops propagation of event outside the root gui
  315. if (r.stopPropagation & Stop3D) != 0 {
  316. r.win.CancelDispatch()
  317. }
  318. }
  319. // onScroll is called when scroll events are received and
  320. // is responsible to dispatch them to child panels.
  321. func (r *Root) onScroll(evname string, ev interface{}) {
  322. // If no panel with the scroll focus, nothing to do
  323. if r.scrollFocus == nil {
  324. return
  325. }
  326. // Dispatch event to panel with scroll focus
  327. r.scrollFocus.GetPanel().Dispatch(evname, ev)
  328. // Stops propagation of event outside the root gui
  329. if (r.stopPropagation & Stop3D) != 0 {
  330. r.win.CancelDispatch()
  331. }
  332. }
  333. // onSize is called when window size events are received
  334. func (r *Root) onWindowSize(evname string, ev interface{}) {
  335. // Sends event only to immediate children
  336. for _, ipan := range r.Children() {
  337. ipan.(IPanel).GetPanel().Dispatch(evname, ev)
  338. }
  339. }
  340. // onFrame is called when window finished swapping frame buffers
  341. func (r *Root) onFrame(evname string, ev interface{}) {
  342. r.TimerManager.ProcessTimers()
  343. }
  344. // canDispatch returns if event can be dispatched to the specified panel
  345. // An event cannot be dispatched if there is a modal panel and the specified
  346. // panel is not the modal panel or any of its children.
  347. func (r *Root) canDispatch(ipan IPanel) bool {
  348. if r.modalPanel == nil {
  349. return true
  350. }
  351. if r.modalPanel == ipan {
  352. return true
  353. }
  354. // Internal function to check panel children recursively
  355. var checkChildren func(iparent IPanel) bool
  356. checkChildren = func(iparent IPanel) bool {
  357. parent := iparent.GetPanel()
  358. for _, child := range parent.Children() {
  359. if child == ipan {
  360. return true
  361. }
  362. res := checkChildren(child.(IPanel))
  363. if res {
  364. return res
  365. }
  366. }
  367. return false
  368. }
  369. return checkChildren(r.modalPanel)
  370. }
  371. //func (r *Root) applyStyleRecursively(s *Style) {
  372. // // TODO
  373. // // This should probably be in Panel ?
  374. //}