forked from g3n/engine
/
glfw.go
502 lines (443 loc) · 15.8 KB
/
glfw.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
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
// 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.
// +build !wasm
package window
import (
"bytes"
"fmt"
"github.com/hecatetech/engine/gui/assets"
"runtime"
"github.com/hecatetech/engine/core"
"github.com/hecatetech/engine/gls"
"github.com/go-gl/glfw/v3.2/glfw"
"image"
_ "image/png"
"os"
)
// Keycodes
const (
KeyUnknown = Key(glfw.KeyUnknown)
KeySpace = Key(glfw.KeySpace)
KeyApostrophe = Key(glfw.KeyApostrophe)
KeyComma = Key(glfw.KeyComma)
KeyMinus = Key(glfw.KeyMinus)
KeyPeriod = Key(glfw.KeyPeriod)
KeySlash = Key(glfw.KeySlash)
Key0 = Key(glfw.Key0)
Key1 = Key(glfw.Key1)
Key2 = Key(glfw.Key2)
Key3 = Key(glfw.Key3)
Key4 = Key(glfw.Key4)
Key5 = Key(glfw.Key5)
Key6 = Key(glfw.Key6)
Key7 = Key(glfw.Key7)
Key8 = Key(glfw.Key8)
Key9 = Key(glfw.Key9)
KeySemicolon = Key(glfw.KeySemicolon)
KeyEqual = Key(glfw.KeyEqual)
KeyA = Key(glfw.KeyA)
KeyB = Key(glfw.KeyB)
KeyC = Key(glfw.KeyC)
KeyD = Key(glfw.KeyD)
KeyE = Key(glfw.KeyE)
KeyF = Key(glfw.KeyF)
KeyG = Key(glfw.KeyG)
KeyH = Key(glfw.KeyH)
KeyI = Key(glfw.KeyI)
KeyJ = Key(glfw.KeyJ)
KeyK = Key(glfw.KeyK)
KeyL = Key(glfw.KeyL)
KeyM = Key(glfw.KeyM)
KeyN = Key(glfw.KeyN)
KeyO = Key(glfw.KeyO)
KeyP = Key(glfw.KeyP)
KeyQ = Key(glfw.KeyQ)
KeyR = Key(glfw.KeyR)
KeyS = Key(glfw.KeyS)
KeyT = Key(glfw.KeyT)
KeyU = Key(glfw.KeyU)
KeyV = Key(glfw.KeyV)
KeyW = Key(glfw.KeyW)
KeyX = Key(glfw.KeyX)
KeyY = Key(glfw.KeyY)
KeyZ = Key(glfw.KeyZ)
KeyLeftBracket = Key(glfw.KeyLeftBracket)
KeyBackslash = Key(glfw.KeyBackslash)
KeyRightBracket = Key(glfw.KeyRightBracket)
KeyGraveAccent = Key(glfw.KeyGraveAccent)
KeyWorld1 = Key(glfw.KeyWorld1)
KeyWorld2 = Key(glfw.KeyWorld2)
KeyEscape = Key(glfw.KeyEscape)
KeyEnter = Key(glfw.KeyEnter)
KeyTab = Key(glfw.KeyTab)
KeyBackspace = Key(glfw.KeyBackspace)
KeyInsert = Key(glfw.KeyInsert)
KeyDelete = Key(glfw.KeyDelete)
KeyRight = Key(glfw.KeyRight)
KeyLeft = Key(glfw.KeyLeft)
KeyDown = Key(glfw.KeyDown)
KeyUp = Key(glfw.KeyUp)
KeyPageUp = Key(glfw.KeyPageUp)
KeyPageDown = Key(glfw.KeyPageDown)
KeyHome = Key(glfw.KeyHome)
KeyEnd = Key(glfw.KeyEnd)
KeyCapsLock = Key(glfw.KeyCapsLock)
KeyScrollLock = Key(glfw.KeyScrollLock)
KeyNumLock = Key(glfw.KeyNumLock)
KeyPrintScreen = Key(glfw.KeyPrintScreen)
KeyPause = Key(glfw.KeyPause)
KeyF1 = Key(glfw.KeyF1)
KeyF2 = Key(glfw.KeyF2)
KeyF3 = Key(glfw.KeyF3)
KeyF4 = Key(glfw.KeyF4)
KeyF5 = Key(glfw.KeyF5)
KeyF6 = Key(glfw.KeyF6)
KeyF7 = Key(glfw.KeyF7)
KeyF8 = Key(glfw.KeyF8)
KeyF9 = Key(glfw.KeyF9)
KeyF10 = Key(glfw.KeyF10)
KeyF11 = Key(glfw.KeyF11)
KeyF12 = Key(glfw.KeyF12)
KeyF13 = Key(glfw.KeyF13)
KeyF14 = Key(glfw.KeyF14)
KeyF15 = Key(glfw.KeyF15)
KeyF16 = Key(glfw.KeyF16)
KeyF17 = Key(glfw.KeyF17)
KeyF18 = Key(glfw.KeyF18)
KeyF19 = Key(glfw.KeyF19)
KeyF20 = Key(glfw.KeyF20)
KeyF21 = Key(glfw.KeyF21)
KeyF22 = Key(glfw.KeyF22)
KeyF23 = Key(glfw.KeyF23)
KeyF24 = Key(glfw.KeyF24)
KeyF25 = Key(glfw.KeyF25)
KeyKP0 = Key(glfw.KeyKP0)
KeyKP1 = Key(glfw.KeyKP1)
KeyKP2 = Key(glfw.KeyKP2)
KeyKP3 = Key(glfw.KeyKP3)
KeyKP4 = Key(glfw.KeyKP4)
KeyKP5 = Key(glfw.KeyKP5)
KeyKP6 = Key(glfw.KeyKP6)
KeyKP7 = Key(glfw.KeyKP7)
KeyKP8 = Key(glfw.KeyKP8)
KeyKP9 = Key(glfw.KeyKP9)
KeyKPDecimal = Key(glfw.KeyKPDecimal)
KeyKPDivide = Key(glfw.KeyKPDivide)
KeyKPMultiply = Key(glfw.KeyKPMultiply)
KeyKPSubtract = Key(glfw.KeyKPSubtract)
KeyKPAdd = Key(glfw.KeyKPAdd)
KeyKPEnter = Key(glfw.KeyKPEnter)
KeyKPEqual = Key(glfw.KeyKPEqual)
KeyLeftShift = Key(glfw.KeyLeftShift)
KeyLeftControl = Key(glfw.KeyLeftControl)
KeyLeftAlt = Key(glfw.KeyLeftAlt)
KeyLeftSuper = Key(glfw.KeyLeftSuper)
KeyRightShift = Key(glfw.KeyRightShift)
KeyRightControl = Key(glfw.KeyRightControl)
KeyRightAlt = Key(glfw.KeyRightAlt)
KeyRightSuper = Key(glfw.KeyRightSuper)
KeyMenu = Key(glfw.KeyMenu)
KeyLast = Key(glfw.KeyLast)
)
// Modifier keys
const (
ModShift = ModifierKey(glfw.ModShift)
ModControl = ModifierKey(glfw.ModControl)
ModAlt = ModifierKey(glfw.ModAlt)
ModSuper = ModifierKey(glfw.ModSuper)
)
// Mouse buttons
const (
MouseButton1 = MouseButton(glfw.MouseButton1)
MouseButton2 = MouseButton(glfw.MouseButton2)
MouseButton3 = MouseButton(glfw.MouseButton3)
MouseButton4 = MouseButton(glfw.MouseButton4)
MouseButton5 = MouseButton(glfw.MouseButton5)
MouseButton6 = MouseButton(glfw.MouseButton6)
MouseButton7 = MouseButton(glfw.MouseButton7)
MouseButton8 = MouseButton(glfw.MouseButton8)
MouseButtonLast = MouseButton(glfw.MouseButtonLast)
MouseButtonLeft = MouseButton(glfw.MouseButtonLeft)
MouseButtonRight = MouseButton(glfw.MouseButtonRight)
MouseButtonMiddle = MouseButton(glfw.MouseButtonMiddle)
)
// Input modes
const (
CursorInputMode = InputMode(glfw.CursorMode) // See Cursor mode values
StickyKeysInputMode = InputMode(glfw.StickyKeysMode) // Value can be either 1 or 0
StickyMouseButtonsInputMode = InputMode(glfw.StickyMouseButtonsMode) // Value can be either 1 or 0
)
// Cursor mode values
const (
CursorNormal = CursorMode(glfw.CursorNormal)
CursorHidden = CursorMode(glfw.CursorHidden)
CursorDisabled = CursorMode(glfw.CursorDisabled)
)
// GlfwWindow describes one glfw window
type GlfwWindow struct {
*glfw.Window // Embedded GLFW window
core.Dispatcher // Embedded event dispatcher
gls *gls.GLS // Associated OpenGL State
fullscreen bool
lastX int
lastY int
lastWidth int
lastHeight int
scaleX float64
scaleY float64
// Events
keyEv KeyEvent
charEv CharEvent
mouseEv MouseEvent
posEv PosEvent
sizeEv SizeEvent
cursorEv CursorEvent
scrollEv ScrollEvent
mods ModifierKey // Current modifier keys
// Cursors
cursors map[Cursor]*glfw.Cursor
lastCursorKey Cursor
}
// Init initializes the GlfwWindow singleton with the specified width, height, and title.
func Init(width, height int, title string) error {
// Panic if already created
if win != nil {
panic(fmt.Errorf("can only call window.Init() once"))
}
// OpenGL functions must be executed in the same thread where
// the context was created (by wmgr.CreateWindow())
runtime.LockOSThread()
// Create wrapper window with dispatcher
w := new(GlfwWindow)
w.Dispatcher.Initialize()
var err error
// Initialize GLFW
err = glfw.Init()
if err != nil {
return err
}
// Set window hints
glfw.WindowHint(glfw.ContextVersionMajor, 3)
glfw.WindowHint(glfw.ContextVersionMinor, 3)
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
glfw.WindowHint(glfw.Samples, 8)
// Set OpenGL forward compatible context only for OSX because it is required for OSX.
// When this is set, glLineWidth(width) only accepts width=1.0 and generates an error
// for any other values although the spec says it should ignore unsupported widths
// and generate an error only when width <= 0.
if runtime.GOOS == "darwin" {
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
}
// Create window and set it as the current context.
// The window is created always as not full screen because if it is
// created as full screen it not possible to revert it to windowed mode.
// At the end of this function, the window will be set to full screen if requested.
w.Window, err = glfw.CreateWindow(width, height, title, nil, nil)
if err != nil {
return err
}
w.MakeContextCurrent()
// Create OpenGL state
w.gls, err = gls.New()
if err != nil {
return err
}
// Compute and store scale
fbw, fbh := w.GetFramebufferSize()
w.scaleX = float64(fbw) / float64(width)
w.scaleY = float64(fbh) / float64(height)
// Create map for cursors
w.cursors = make(map[Cursor]*glfw.Cursor)
w.lastCursorKey = CursorLast
// Preallocate GLFW standard cursors
w.cursors[ArrowCursor] = glfw.CreateStandardCursor(glfw.ArrowCursor)
w.cursors[IBeamCursor] = glfw.CreateStandardCursor(glfw.IBeamCursor)
w.cursors[CrosshairCursor] = glfw.CreateStandardCursor(glfw.CrosshairCursor)
w.cursors[HandCursor] = glfw.CreateStandardCursor(glfw.HandCursor)
w.cursors[HResizeCursor] = glfw.CreateStandardCursor(glfw.HResizeCursor)
w.cursors[VResizeCursor] = glfw.CreateStandardCursor(glfw.VResizeCursor)
// Preallocate extra G3N standard cursors (diagonal resize cursors)
cursorDiag1Png := assets.MustAsset("cursors/diag1.png") // [/]
cursorDiag2Png := assets.MustAsset("cursors/diag2.png") // [\]
diag1Img, _, err := image.Decode(bytes.NewReader(cursorDiag1Png))
diag2Img, _, err := image.Decode(bytes.NewReader(cursorDiag2Png))
if err != nil {
return err
}
w.cursors[DiagResize1Cursor] = glfw.CreateCursor(diag1Img, 8, 8) // [/]
w.cursors[DiagResize2Cursor] = glfw.CreateCursor(diag2Img, 8, 8) // [\]
// Set up key callback to dispatch event
w.SetKeyCallback(func(x *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
w.keyEv.Key = Key(key)
w.keyEv.Mods = ModifierKey(mods)
w.mods = w.keyEv.Mods
if action == glfw.Press {
w.Dispatch(OnKeyDown, &w.keyEv)
} else if action == glfw.Release {
w.Dispatch(OnKeyUp, &w.keyEv)
} else if action == glfw.Repeat {
w.Dispatch(OnKeyRepeat, &w.keyEv)
}
})
// Set up char callback to dispatch event
w.SetCharModsCallback(func(x *glfw.Window, char rune, mods glfw.ModifierKey) {
w.charEv.Char = char
w.charEv.Mods = ModifierKey(mods)
w.Dispatch(OnChar, &w.charEv)
})
// Set up mouse button callback to dispatch event
w.SetMouseButtonCallback(func(x *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
xpos, ypos := x.GetCursorPos()
w.mouseEv.Button = MouseButton(button)
w.mouseEv.Mods = ModifierKey(mods)
w.mouseEv.Xpos = float32(xpos * w.scaleX)
w.mouseEv.Ypos = float32(ypos * w.scaleY)
if action == glfw.Press {
w.Dispatch(OnMouseDown, &w.mouseEv)
} else if action == glfw.Release {
w.Dispatch(OnMouseUp, &w.mouseEv)
}
})
// Set up window size callback to dispatch event
w.SetSizeCallback(func(x *glfw.Window, width int, height int) {
fbw, fbh := x.GetFramebufferSize()
w.sizeEv.Width = width
w.sizeEv.Height = height
w.scaleX = float64(fbw) / float64(width)
w.scaleY = float64(fbh) / float64(height)
w.Dispatch(OnWindowSize, &w.sizeEv)
})
// Set up window position callback to dispatch event
w.SetPosCallback(func(x *glfw.Window, xpos int, ypos int) {
w.posEv.Xpos = xpos
w.posEv.Ypos = ypos
w.Dispatch(OnWindowPos, &w.posEv)
})
// Set up window cursor position callback to dispatch event
w.SetCursorPosCallback(func(x *glfw.Window, xpos float64, ypos float64) {
w.cursorEv.Xpos = float32(xpos)
w.cursorEv.Ypos = float32(ypos)
w.cursorEv.Mods = w.mods
w.Dispatch(OnCursor, &w.cursorEv)
})
// Set up mouse wheel scroll callback to dispatch event
w.SetScrollCallback(func(x *glfw.Window, xoff float64, yoff float64) {
w.scrollEv.Xoffset = float32(xoff)
w.scrollEv.Yoffset = float32(yoff)
w.scrollEv.Mods = w.mods
w.Dispatch(OnScroll, &w.scrollEv)
})
win = w // Set singleton
return nil
}
// Gls returns the associated OpenGL state.
func (w *GlfwWindow) Gls() *gls.GLS {
return w.gls
}
// Fullscreen returns whether this windows is currently fullscreen.
func (w *GlfwWindow) Fullscreen() bool {
return w.fullscreen
}
// SetFullscreen sets this window as fullscreen on the primary monitor
// TODO allow for fullscreen with resolutions different than the monitor's
func (w *GlfwWindow) SetFullscreen(full bool) {
// If already in the desired state, nothing to do
if w.fullscreen == full {
return
}
// Set window fullscreen on the primary monitor
if full {
// Get size of primary monitor
mon := glfw.GetPrimaryMonitor()
vmode := mon.GetVideoMode()
width := vmode.Width
height := vmode.Height
// Set as fullscreen on the primary monitor
w.SetMonitor(mon, 0, 0, width, height, vmode.RefreshRate)
w.fullscreen = true
// Save current position and size of the window
w.lastX, w.lastY = w.GetPos()
w.lastWidth, w.lastHeight = w.GetSize()
} else {
// Restore window to previous position and size
w.SetMonitor(nil, w.lastX, w.lastY, w.lastWidth, w.lastHeight, glfw.DontCare)
w.fullscreen = false
}
}
// Destroy destroys this window and its context
func (w *GlfwWindow) Destroy() {
w.Window.Destroy()
glfw.Terminate()
runtime.UnlockOSThread() // Important when using the execution tracer
}
// Scale returns this window's DPI scale factor (FramebufferSize / Size)
func (w *GlfwWindow) GetScale() (x float64, y float64) {
return w.scaleX, w.scaleY
}
// ScreenResolution returns the screen resolution
func (w *GlfwWindow) ScreenResolution(p interface{}) (width, height int) {
mon := glfw.GetPrimaryMonitor()
vmode := mon.GetVideoMode()
return vmode.Width, vmode.Height
}
// PollEvents process events in the event queue
func (w *GlfwWindow) PollEvents() {
glfw.PollEvents()
}
// SetSwapInterval sets the number of screen updates to wait from the time SwapBuffer()
// is called before swapping the buffers and returning.
func (w *GlfwWindow) SetSwapInterval(interval int) {
glfw.SwapInterval(interval)
}
// SetCursor sets the window's cursor.
func (w *GlfwWindow) SetCursor(cursor Cursor) {
cur, ok := w.cursors[cursor]
if !ok {
panic("Invalid cursor")
}
w.Window.SetCursor(cur)
}
// CreateCursor creates a new custom cursor and returns an int handle.
func (w *GlfwWindow) CreateCursor(imgFile string, xhot, yhot int) (Cursor, error) {
// Open image file
file, err := os.Open(imgFile)
if err != nil {
return 0, err
}
defer file.Close()
// Decode image
img, _, err := image.Decode(file)
if err != nil {
return 0, err
}
// Create and store cursor
w.lastCursorKey += 1
w.cursors[Cursor(w.lastCursorKey)] = glfw.CreateCursor(img, xhot, yhot)
return w.lastCursorKey, nil
}
// DisposeCursor deletes the existing custom cursor with the provided int handle.
func (w *GlfwWindow) DisposeCursor(cursor Cursor) {
if cursor <= CursorLast {
panic("Can't dispose standard cursor")
}
w.cursors[cursor].Destroy()
delete(w.cursors, cursor)
}
// DisposeAllCursors deletes all existing custom cursors.
func (w *GlfwWindow) DisposeAllCustomCursors() {
// Destroy and delete all custom cursors
for key := range w.cursors {
if key > CursorLast {
w.cursors[key].Destroy()
delete(w.cursors, key)
}
}
// Set the next cursor key as the last standard cursor key + 1
w.lastCursorKey = CursorLast
}
// Center centers the window on the screen.
//func (w *GlfwWindow) Center() {
//
// // TODO
//}