| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- // 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/core"
- "github.com/g3n/engine/window"
- )
- // manager singleton
- var gm *manager
- // manager routes GUI events to the appropriate panels.
- type manager struct {
- core.Dispatcher // Embedded Dispatcher
- core.TimerManager // Embedded TimerManager
- win window.IWindow // The current IWindow
- scene core.INode // INode containing IPanels to dispatch events to (can contain non-IPanels as well)
- modal IPanel // Panel which along its descendants will exclusively receive all events
- target IPanel // Panel immediately under the cursor
- keyFocus core.IDispatcher // IDispatcher which will exclusively receive all key and char events
- cursorFocus core.IDispatcher // IDispatcher which will exclusively receive all OnCursor events
- cev *window.CursorEvent // IDispatcher which will exclusively receive all OnCursor events
- }
- // Manager returns the GUI manager singleton (creating it the first time)
- func Manager() *manager {
- // Return singleton if already created
- if gm != nil {
- return gm
- }
- gm = new(manager)
- gm.Dispatcher.Initialize()
- gm.TimerManager.Initialize()
- // Subscribe to window events
- gm.win = window.Get()
- gm.win.Subscribe(window.OnKeyUp, gm.onKeyboard)
- gm.win.Subscribe(window.OnKeyDown, gm.onKeyboard)
- gm.win.Subscribe(window.OnKeyRepeat, gm.onKeyboard)
- gm.win.Subscribe(window.OnChar, gm.onKeyboard)
- gm.win.Subscribe(window.OnCursor, gm.onCursor)
- gm.win.Subscribe(window.OnMouseUp, gm.onMouse)
- gm.win.Subscribe(window.OnMouseDown, gm.onMouse)
- gm.win.Subscribe(window.OnScroll, gm.onScroll)
- return gm
- }
- // Set sets the INode to watch for events.
- // It's usually a scene containing a hierarchy of INodes.
- // The manager only cares about IPanels inside that hierarchy.
- func (gm *manager) Set(scene core.INode) {
- gm.scene = scene
- }
- // SetModal sets the specified panel and its descendants to be the exclusive receivers of events.
- func (gm *manager) SetModal(ipan IPanel) {
- gm.modal = ipan
- gm.SetKeyFocus(nil)
- gm.SetCursorFocus(nil)
- }
- // SetKeyFocus sets the key-focused IDispatcher, which will exclusively receive key and char events.
- func (gm *manager) SetKeyFocus(disp core.IDispatcher) {
- if gm.keyFocus == disp {
- return
- }
- if gm.keyFocus != nil {
- gm.keyFocus.Dispatch(OnFocus, nil)
- }
- gm.keyFocus = disp
- if gm.keyFocus != nil {
- gm.keyFocus.Dispatch(OnFocusLost, nil)
- }
- }
- // SetCursorFocus sets the cursor-focused IDispatcher, which will exclusively receive OnCursor events.
- func (gm *manager) SetCursorFocus(disp core.IDispatcher) {
- if gm.cursorFocus == disp {
- return
- }
- gm.cursorFocus = disp
- if gm.cursorFocus == nil {
- gm.onCursor(OnCursor, gm.cev)
- }
- }
- // onKeyboard is called when char or key events are received.
- // The events are dispatched to the focused IDispatcher or to non-GUI.
- func (gm *manager) onKeyboard(evname string, ev interface{}) {
- if gm.keyFocus != nil {
- if gm.modal == nil {
- gm.keyFocus.Dispatch(evname, ev)
- } else if ipan, ok := gm.keyFocus.(IPanel); ok && gm.modal.IsAncestorOf(ipan) {
- gm.keyFocus.Dispatch(evname, ev)
- }
- } else {
- gm.Dispatch(evname, ev)
- }
- }
- // onMouse is called when mouse events are received.
- // OnMouseDown/OnMouseUp are dispatched to gm.target or to non-GUI, while
- // OnMouseDownOut/OnMouseUpOut are dispatched to all non-target panels.
- func (gm *manager) onMouse(evname string, ev interface{}) {
- // Check if gm.scene is nil and if so then there are no IPanels to send events to
- if gm.scene == nil {
- gm.Dispatch(evname, ev) // Dispatch event to non-GUI since event was not filtered by any GUI component
- return
- }
- // Dispatch OnMouseDownOut/OnMouseUpOut to all panels except ancestors of target
- gm.forEachIPanel(func(ipan IPanel) {
- if gm.target == nil || !ipan.IsAncestorOf(gm.target) {
- switch evname {
- case OnMouseDown:
- ipan.Dispatch(OnMouseDownOut, ev)
- case OnMouseUp:
- ipan.Dispatch(OnMouseUpOut, ev)
- }
- }
- })
- // Appropriately dispatch the event to target panel's lowest subscribed ancestor or to non-GUI or not at all
- if gm.target != nil {
- if gm.modal == nil || gm.modal.IsAncestorOf(gm.target) {
- sendAncestry(gm.target, false, nil, gm.modal, evname, ev)
- }
- } else if gm.modal == nil {
- gm.Dispatch(evname, ev)
- }
- }
- // onScroll is called when scroll events are received.
- // The events are dispatched to the target panel or to non-GUI.
- func (gm *manager) onScroll(evname string, ev interface{}) {
- // Check if gm.scene is nil and if so then there are no IPanels to send events to
- if gm.scene == nil {
- gm.Dispatch(evname, ev) // Dispatch event to non-GUI since event was not filtered by any GUI component
- return
- }
- // Appropriately dispatch the event to target panel's lowest subscribed ancestor or to non-GUI or not at all
- if gm.target != nil {
- if gm.modal == nil || gm.modal.IsAncestorOf(gm.target) {
- sendAncestry(gm.target, false, nil, gm.modal, evname, ev)
- }
- } else if gm.modal == nil {
- gm.Dispatch(evname, ev)
- }
- }
- // onCursor is called when (mouse) cursor events are received.
- // Updates the target/click panels and dispatches OnCursor, OnCursorEnter, OnCursorLeave events.
- func (gm *manager) onCursor(evname string, ev interface{}) {
- // If an IDispatcher is capturing cursor events dispatch to it and return
- if gm.cursorFocus != nil {
- gm.cursorFocus.Dispatch(evname, ev)
- return
- }
- // If gm.scene is nil then there are no IPanels to send events to
- if gm.scene == nil {
- gm.Dispatch(evname, ev) // Dispatch event to non-GUI since event was not filtered by any GUI component
- return
- }
- // Get and store CursorEvent
- gm.cev = ev.(*window.CursorEvent)
- // Temporarily store last target and clear current one
- oldTarget := gm.target
- gm.target = nil
- // Find IPanel immediately under the cursor and store it in gm.target
- gm.forEachIPanel(func(ipan IPanel) {
- if ipan.InsideBorders(gm.cev.Xpos, gm.cev.Ypos) && (gm.target == nil || ipan.Position().Z < gm.target.GetPanel().Position().Z) {
- gm.target = ipan
- }
- })
- // If the cursor is now over a different panel, dispatch OnCursorLeave/OnCursorEnter
- if gm.target != oldTarget {
- // We are only interested in sending events up to the lowest common ancestor of target and oldTarget
- var commonAnc IPanel
- if gm.target != nil && oldTarget != nil {
- commonAnc, _ = gm.target.LowestCommonAncestor(oldTarget).(IPanel)
- }
- // If just left a panel and the new panel is not a descendant of the old panel
- if oldTarget != nil && !oldTarget.IsAncestorOf(gm.target) && (gm.modal == nil || gm.modal.IsAncestorOf(oldTarget)) {
- sendAncestry(oldTarget, true, commonAnc, gm.modal, OnCursorLeave, ev)
- }
- // If just entered a panel and it's not an ancestor of the old panel
- if gm.target != nil && !gm.target.IsAncestorOf(oldTarget) && (gm.modal == nil || gm.modal.IsAncestorOf(gm.target)) {
- sendAncestry(gm.target, true, commonAnc, gm.modal, OnCursorEnter, ev)
- }
- }
- // Appropriately dispatch the event to target panel's lowest subscribed ancestor or to non-GUI or not at all
- if gm.target != nil {
- if gm.modal == nil || gm.modal.IsAncestorOf(gm.target) {
- sendAncestry(gm.target, false, nil, gm.modal, evname, ev)
- }
- } else if gm.modal == nil {
- gm.Dispatch(evname, ev)
- }
- }
- // sendAncestry sends the specified event (evname/ev) to the specified target panel and its ancestors.
- // If all is false, then the event is only sent to the lowest subscribed ancestor.
- // If uptoEx (i.e. excluding) is not nil then the event will not be dispatched to that ancestor nor any higher ancestors.
- // If uptoIn (i.e. including) is not nil then the event will be dispatched to that ancestor but not to any higher ancestors.
- // uptoEx and uptoIn can both be defined.
- func sendAncestry(ipan IPanel, all bool, uptoEx IPanel, uptoIn IPanel, evname string, ev interface{}) {
- var ok bool
- for ipan != nil {
- if uptoEx != nil && ipan == uptoEx {
- break
- }
- count := ipan.Dispatch(evname, ev)
- if (uptoIn != nil && ipan == uptoIn) || (!all && count > 0) {
- break
- }
- ipan, ok = ipan.Parent().(IPanel)
- if !ok {
- break
- }
- }
- }
- // traverseIPanel traverses the descendants of the provided IPanel,
- // executing the specified function for each IPanel.
- func traverseIPanel(ipan IPanel, f func(ipan IPanel)) {
- // If panel not visible or not enabled, ignore entire hierarchy below this point
- if !ipan.Visible() || !ipan.Enabled() {
- return
- }
- f(ipan) // Call specified function
- // Check descendants (can assume they are IPanels)
- for _, child := range ipan.Children() {
- traverseIPanel(child.(IPanel), f)
- }
- }
- // traverseINode traverses the descendants of the specified INode,
- // executing the specified function for each IPanel.
- func traverseINode(inode core.INode, f func(ipan IPanel)) {
- if ipan, ok := inode.(IPanel); ok {
- traverseIPanel(ipan, f)
- } else {
- for _, child := range inode.Children() {
- traverseINode(child, f)
- }
- }
- }
- // forEachIPanel executes the specified function for each enabled and visible IPanel in gm.scene.
- func (gm *manager) forEachIPanel(f func(ipan IPanel)) {
- traverseINode(gm.scene, f)
- }
|