forked from g3n/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager.go
288 lines (246 loc) · 9.31 KB
/
manager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// 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/Cyberselves/engine/core"
"github.com/Cyberselves/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(OnFocusLost, nil)
}
gm.keyFocus = disp
if gm.keyFocus != nil {
gm.keyFocus.Dispatch(OnFocus, 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{}) {
// To fix #299
if gm.cev == nil {
mev := ev.(*window.MouseEvent)
gm.cev = &window.CursorEvent{
Xpos: mev.Xpos,
Ypos: mev.Ypos,
Mods: mev.Mods,
}
}
// 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, ignore entire hierarchy below this point
if !ipan.Visible() {
return
}
if ipan.Enabled() {
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)
}