From 93d30a1ff7efe841cea20b9340570266caf8e539 Mon Sep 17 00:00:00 2001 From: Thomas Friedel Date: Sat, 20 Oct 2018 14:42:29 +0200 Subject: [PATCH] added a glfwcanvas subpackage --- README.md | 6 +- examples/glfw/glfw.go | 2 +- glfwcanvas/glfwcanvas.go | 167 ++++++++++++++++++++++++++++++ glfwcanvas/keynames.go | 216 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 387 insertions(+), 4 deletions(-) create mode 100644 glfwcanvas/glfwcanvas.go create mode 100644 glfwcanvas/keynames.go diff --git a/README.md b/README.md index 8a2130c..5e1be19 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ Whereas the Javascript API uses a context that all draw calls go to, here all dr The library is intended to provide decent performance. Obviously it will not be able to rival hand coded OpenGL for a given purpose, but for many purposes it will be enough. It can also be combined with hand coded OpenGL. -# SDL convenience package +# SDL/GLFW convenience packages -The sdlcanvas subpackage provides a very simple way to get started with just a few lines of code. As the name implies it is based on the SDL library. It creates a window for you and gives you a canvas to draw with. +The sdlcanvas and glfwcanvas subpackages provide a very simple way to get started with just a few lines of code. As the names imply they are based on the SDL library and the GLFW library respectively. They create a window for you and give you a canvas to draw with. # OS support - Linux - Windows -- OSX +- macOS - Android - iOS diff --git a/examples/glfw/glfw.go b/examples/glfw/glfw.go index 2044042..4cf03e2 100644 --- a/examples/glfw/glfw.go +++ b/examples/glfw/glfw.go @@ -13,7 +13,7 @@ import ( func main() { runtime.LockOSThread() - // init SDL + // init GLFW err := glfw.Init() if err != nil { log.Fatalf("Error initializing GLFW: %v", err) diff --git a/glfwcanvas/glfwcanvas.go b/glfwcanvas/glfwcanvas.go new file mode 100644 index 0000000..d6ed270 --- /dev/null +++ b/glfwcanvas/glfwcanvas.go @@ -0,0 +1,167 @@ +package glfwcanvas + +import ( + "fmt" + _ "image/gif" // Imported here so that applications based on this package support these formats by default + _ "image/jpeg" + _ "image/png" + "log" + "math" + "runtime" + "time" + + "github.com/go-gl/gl/v3.2-core/gl" + "github.com/go-gl/glfw/v3.2/glfw" + "github.com/tfriedel6/canvas" + "github.com/tfriedel6/canvas/glimpl/gogl" +) + +// Window represents the opened window with GL context. The Mouse* and Key* +// functions can be set for callbacks +type Window struct { + Window *glfw.Window + canvas *canvas.Canvas + frameTimes [10]time.Time + frameIndex int + frameCount int + fps float32 + close bool + MouseDown func(button, x, y int) + MouseMove func(x, y int) + MouseUp func(button, x, y int) + MouseWheel func(x, y int) + KeyDown func(scancode int, rn rune, name string) + KeyUp func(scancode int, rn rune, name string) + KeyChar func(rn rune) + SizeChange func(w, h int) +} + +// CreateWindow creates a window using SDL and initializes the OpenGL context +func CreateWindow(w, h int, title string) (*Window, *canvas.Canvas, error) { + runtime.LockOSThread() + + // init GLFW + err := glfw.Init() + if err != nil { + log.Fatalf("Error initializing GLFW: %v", err) + } + + // the stencil size setting is required for the canvas to work + glfw.WindowHint(glfw.StencilBits, 8) + glfw.WindowHint(glfw.DepthBits, 0) + + // create window + window, err := glfw.CreateWindow(w, h, title, nil, nil) + if err != nil { + log.Fatalf("Error creating window: %v", err) + } + window.MakeContextCurrent() + + // init GL + err = gl.Init() + if err != nil { + log.Fatalf("Error initializing GL: %v", err) + } + + // set vsync on, enable multisample (if available) + glfw.SwapInterval(1) + gl.Enable(gl.MULTISAMPLE) + + // load canvas GL assets + err = canvas.LoadGL(glimplgogl.GLImpl{}) + if err != nil { + log.Fatalf("Error loading canvas GL assets: %v", err) + } + + err = canvas.LoadGL(glimplgogl.GLImpl{}) + if err != nil { + return nil, nil, fmt.Errorf("Error loading canvas GL assets: %v", err) + } + + cv := canvas.New(0, 0, w, h) + wnd := &Window{ + Window: window, + canvas: cv, + } + + var mx, my int + + window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { + if action == glfw.Press && wnd.MouseDown != nil { + wnd.MouseDown(int(button), mx, my) + } else if action == glfw.Release && wnd.MouseUp != nil { + wnd.MouseUp(int(button), mx, my) + } + }) + window.SetCursorPosCallback(func(w *glfw.Window, xpos float64, ypos float64) { + mx, my = int(math.Round(xpos)), int(math.Round(ypos)) + if wnd.MouseMove != nil { + wnd.MouseMove(mx, my) + } + }) + window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { + if wnd.MouseWheel != nil { + wnd.MouseWheel(int(math.Round(xoff)), int(math.Round(yoff))) + } + }) + window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + if action == glfw.Press && wnd.KeyDown != nil { + wnd.KeyDown(scancode, keyRune(key), keyName(key)) + } else if action == glfw.Release && wnd.KeyUp != nil { + wnd.KeyUp(scancode, keyRune(key), keyName(key)) + } + }) + window.SetCharCallback(func(w *glfw.Window, char rune) { + if wnd.KeyChar != nil { + wnd.KeyChar(char) + } + }) + window.SetSizeCallback(func(w *glfw.Window, width int, height int) { + if wnd.SizeChange != nil { + wnd.SizeChange(width, height) + } + }) + + return wnd, cv, nil +} + +// FPS returns the frames per second (averaged over 10 frames) +func (wnd *Window) FPS() float32 { + return wnd.fps +} + +// Close can be used to end a call to MainLoop +func (wnd *Window) Close() { + wnd.close = true +} + +// StartFrame handles events and gets the window ready for rendering +func (wnd *Window) StartFrame() { + wnd.Window.MakeContextCurrent() + glfw.PollEvents() +} + +// FinishFrame updates the FPS count and displays the frame +func (wnd *Window) FinishFrame() { + now := time.Now() + wnd.frameTimes[wnd.frameIndex] = now + wnd.frameIndex++ + wnd.frameIndex %= len(wnd.frameTimes) + if wnd.frameCount < len(wnd.frameTimes) { + wnd.frameCount++ + } else { + diff := now.Sub(wnd.frameTimes[wnd.frameIndex]).Seconds() + wnd.fps = float32(wnd.frameCount-1) / float32(diff) + } + + wnd.Window.SwapBuffers() +} + +// MainLoop runs a main loop and calls run on every frame +func (wnd *Window) MainLoop(run func()) { + for !wnd.close { + wnd.StartFrame() + run() + wnd.FinishFrame() + } +} diff --git a/glfwcanvas/keynames.go b/glfwcanvas/keynames.go new file mode 100644 index 0000000..93e928a --- /dev/null +++ b/glfwcanvas/keynames.go @@ -0,0 +1,216 @@ +package glfwcanvas + +import "github.com/go-gl/glfw/v3.2/glfw" + +var keyNameMap [347]string +var keyRuneMap [347]rune + +func init() { + keyNameMap[glfw.KeyEscape] = "Escape" + keyNameMap[glfw.Key0] = "Digit0" + keyNameMap[glfw.Key1] = "Digit1" + keyNameMap[glfw.Key2] = "Digit2" + keyNameMap[glfw.Key3] = "Digit3" + keyNameMap[glfw.Key4] = "Digit4" + keyNameMap[glfw.Key5] = "Digit5" + keyNameMap[glfw.Key6] = "Digit6" + keyNameMap[glfw.Key7] = "Digit7" + keyNameMap[glfw.Key8] = "Digit8" + keyNameMap[glfw.Key9] = "Digit9" + keyNameMap[glfw.KeyMinus] = "Minus" + keyNameMap[glfw.KeyEqual] = "Equal" + keyNameMap[glfw.KeyBackspace] = "Backspace" + keyNameMap[glfw.KeyTab] = "Tab" + keyNameMap[glfw.KeyQ] = "KeyQ" + keyNameMap[glfw.KeyW] = "KeyW" + keyNameMap[glfw.KeyE] = "KeyE" + keyNameMap[glfw.KeyR] = "KeyR" + keyNameMap[glfw.KeyT] = "KeyT" + keyNameMap[glfw.KeyY] = "KeyY" + keyNameMap[glfw.KeyU] = "KeyU" + keyNameMap[glfw.KeyI] = "KeyI" + keyNameMap[glfw.KeyO] = "KeyO" + keyNameMap[glfw.KeyP] = "KeyP" + keyNameMap[glfw.KeyLeftBracket] = "BracketLeft" + keyNameMap[glfw.KeyRightBracket] = "BracketRight" + keyNameMap[glfw.KeyEnter] = "Enter" + keyNameMap[glfw.KeyLeftControl] = "ControlLeft" + keyNameMap[glfw.KeyA] = "KeyA" + keyNameMap[glfw.KeyS] = "KeyS" + keyNameMap[glfw.KeyD] = "KeyD" + keyNameMap[glfw.KeyF] = "KeyF" + keyNameMap[glfw.KeyG] = "KeyG" + keyNameMap[glfw.KeyH] = "KeyH" + keyNameMap[glfw.KeyJ] = "KeyJ" + keyNameMap[glfw.KeyK] = "KeyK" + keyNameMap[glfw.KeyL] = "KeyL" + keyNameMap[glfw.KeySemicolon] = "Semicolon" + keyNameMap[glfw.KeyApostrophe] = "Quote" + keyNameMap[glfw.KeyGraveAccent] = "Backquote" + keyNameMap[glfw.KeyLeftShift] = "ShiftLeft" + keyNameMap[glfw.KeyBackslash] = "Backslash" + keyNameMap[glfw.KeyZ] = "KeyZ" + keyNameMap[glfw.KeyX] = "KeyX" + keyNameMap[glfw.KeyC] = "KeyC" + keyNameMap[glfw.KeyV] = "KeyV" + keyNameMap[glfw.KeyB] = "KeyB" + keyNameMap[glfw.KeyN] = "KeyN" + keyNameMap[glfw.KeyM] = "KeyM" + keyNameMap[glfw.KeyComma] = "Comma" + keyNameMap[glfw.KeyPeriod] = "Period" + keyNameMap[glfw.KeySlash] = "Slash" + keyNameMap[glfw.KeyRightShift] = "RightShift" + keyNameMap[glfw.KeyKPMultiply] = "NumpadMultiply" + keyNameMap[glfw.KeyLeftAlt] = "AltLeft" + keyNameMap[glfw.KeySpace] = "Space" + keyNameMap[glfw.KeyCapsLock] = "CapsLock" + keyNameMap[glfw.KeyF1] = "F1" + keyNameMap[glfw.KeyF2] = "F2" + keyNameMap[glfw.KeyF3] = "F3" + keyNameMap[glfw.KeyF4] = "F4" + keyNameMap[glfw.KeyF5] = "F5" + keyNameMap[glfw.KeyF6] = "F6" + keyNameMap[glfw.KeyF7] = "F7" + keyNameMap[glfw.KeyF8] = "F8" + keyNameMap[glfw.KeyF9] = "F9" + keyNameMap[glfw.KeyF10] = "F10" + keyNameMap[glfw.KeyPause] = "Pause" + keyNameMap[glfw.KeyScrollLock] = "ScrollLock" + keyNameMap[glfw.KeyKP7] = "Numpad7" + keyNameMap[glfw.KeyKP8] = "Numpad8" + keyNameMap[glfw.KeyKP9] = "Numpad9" + keyNameMap[glfw.KeyKPSubtract] = "NumpadSubtract" + keyNameMap[glfw.KeyKP4] = "Numpad4" + keyNameMap[glfw.KeyKP5] = "Numpad5" + keyNameMap[glfw.KeyKP6] = "Numpad6" + keyNameMap[glfw.KeyKPAdd] = "NumpadAdd" + keyNameMap[glfw.KeyKP1] = "Numpad1" + keyNameMap[glfw.KeyKP2] = "Numpad2" + keyNameMap[glfw.KeyKP3] = "Numpad3" + keyNameMap[glfw.KeyKP0] = "Numpad0" + keyNameMap[glfw.KeyKPDecimal] = "NumpadDecimal" + keyNameMap[glfw.KeyPrintScreen] = "PrintScreen" + // keyNameMap[glfw.KeyNonUSBackslash] = "IntlBackslash" + keyNameMap[glfw.KeyF11] = "F11" + keyNameMap[glfw.KeyF12] = "F12" + keyNameMap[glfw.KeyKPEqual] = "NumpadEqual" + keyNameMap[glfw.KeyF13] = "F13" + keyNameMap[glfw.KeyF14] = "F14" + keyNameMap[glfw.KeyF15] = "F15" + keyNameMap[glfw.KeyF16] = "F16" + keyNameMap[glfw.KeyF17] = "F17" + keyNameMap[glfw.KeyF18] = "F18" + keyNameMap[glfw.KeyF19] = "F19" + // keyNameMap[glfw.KeyUndo] = "Undo" + // keyNameMap[glfw.KeyPaste] = "Paste" + // keyNameMap[glfw.KeyAudioNext] = "MediaTrackPrevious" + // keyNameMap[glfw.KeyCut] = "Cut" + // keyNameMap[glfw.KeyCopy] = "Copy" + // keyNameMap[glfw.KeyAudioNext] = "MediaTrackNext" + keyNameMap[glfw.KeyKPEnter] = "NumpadEnter" + keyNameMap[glfw.KeyRightControl] = "ControlRight" + // keyNameMap[glfw.KeyMute] = "AudioVolumeMute" + // keyNameMap[glfw.KeyAudioPlay] = "MediaPlayPause" + // keyNameMap[glfw.KeyAudioStop] = "MediaStop" + // keyNameMap[glfw.KeyVolumeDown] = "AudioVolumeDown" + // keyNameMap[glfw.KeyVolumeUp] = "AudioVolumeUp" + keyNameMap[glfw.KeyKPDivide] = "NumpadDivide" + keyNameMap[glfw.KeyRightAlt] = "AltRight" + // keyNameMap[glfw.KeyHelp] = "Help" + keyNameMap[glfw.KeyHome] = "Home" + keyNameMap[glfw.KeyUp] = "ArrowUp" + keyNameMap[glfw.KeyPageUp] = "PageUp" + keyNameMap[glfw.KeyLeft] = "ArrowLeft" + keyNameMap[glfw.KeyRight] = "ArrowRight" + keyNameMap[glfw.KeyEnd] = "End" + keyNameMap[glfw.KeyDown] = "ArrowDown" + keyNameMap[glfw.KeyInsert] = "Insert" + keyNameMap[glfw.KeyDelete] = "Delete" + // keyNameMap[glfw.KeyApplication] = "ContextMenu" + + keyRuneMap[glfw.Key0] = '0' + keyRuneMap[glfw.Key1] = '1' + keyRuneMap[glfw.Key2] = '2' + keyRuneMap[glfw.Key3] = '3' + keyRuneMap[glfw.Key4] = '4' + keyRuneMap[glfw.Key5] = '5' + keyRuneMap[glfw.Key6] = '6' + keyRuneMap[glfw.Key7] = '7' + keyRuneMap[glfw.Key8] = '8' + keyRuneMap[glfw.Key9] = '9' + keyRuneMap[glfw.KeyMinus] = '-' + keyRuneMap[glfw.KeyEqual] = '=' + keyRuneMap[glfw.KeyTab] = '\t' + keyRuneMap[glfw.KeyQ] = 'Q' + keyRuneMap[glfw.KeyW] = 'W' + keyRuneMap[glfw.KeyE] = 'E' + keyRuneMap[glfw.KeyR] = 'R' + keyRuneMap[glfw.KeyT] = 'T' + keyRuneMap[glfw.KeyY] = 'Y' + keyRuneMap[glfw.KeyU] = 'U' + keyRuneMap[glfw.KeyI] = 'I' + keyRuneMap[glfw.KeyO] = 'O' + keyRuneMap[glfw.KeyP] = 'P' + keyRuneMap[glfw.KeyLeftBracket] = '[' + keyRuneMap[glfw.KeyRightBracket] = ']' + keyRuneMap[glfw.KeyEnter] = '\n' + keyRuneMap[glfw.KeyA] = 'A' + keyRuneMap[glfw.KeyS] = 'S' + keyRuneMap[glfw.KeyD] = 'D' + keyRuneMap[glfw.KeyF] = 'F' + keyRuneMap[glfw.KeyG] = 'G' + keyRuneMap[glfw.KeyH] = 'H' + keyRuneMap[glfw.KeyJ] = 'J' + keyRuneMap[glfw.KeyK] = 'K' + keyRuneMap[glfw.KeyL] = 'L' + keyRuneMap[glfw.KeySemicolon] = ';' + keyRuneMap[glfw.KeyApostrophe] = '\'' + keyRuneMap[glfw.KeyGraveAccent] = '`' + keyRuneMap[glfw.KeyBackslash] = '\\' + keyRuneMap[glfw.KeyZ] = 'Z' + keyRuneMap[glfw.KeyX] = 'X' + keyRuneMap[glfw.KeyC] = 'C' + keyRuneMap[glfw.KeyV] = 'V' + keyRuneMap[glfw.KeyB] = 'B' + keyRuneMap[glfw.KeyN] = 'N' + keyRuneMap[glfw.KeyM] = 'M' + keyRuneMap[glfw.KeyComma] = ',' + keyRuneMap[glfw.KeyPeriod] = '.' + keyRuneMap[glfw.KeySlash] = '/' + keyRuneMap[glfw.KeyKPMultiply] = '*' + keyRuneMap[glfw.KeySpace] = ' ' + keyRuneMap[glfw.KeyKP7] = '7' + keyRuneMap[glfw.KeyKP8] = '8' + keyRuneMap[glfw.KeyKP9] = '9' + keyRuneMap[glfw.KeyKPSubtract] = '-' + keyRuneMap[glfw.KeyKP4] = '4' + keyRuneMap[glfw.KeyKP5] = '5' + keyRuneMap[glfw.KeyKP6] = '6' + keyRuneMap[glfw.KeyKPAdd] = '+' + keyRuneMap[glfw.KeyKP1] = '1' + keyRuneMap[glfw.KeyKP2] = '2' + keyRuneMap[glfw.KeyKP3] = '3' + keyRuneMap[glfw.KeyKP0] = '0' + keyRuneMap[glfw.KeyKPDecimal] = '.' + keyRuneMap[glfw.KeyKPEqual] = '=' + keyRuneMap[glfw.KeyKPEnter] = '\n' + keyRuneMap[glfw.KeyKPDivide] = '/' +} + +func keyName(key glfw.Key) string { + if int(key) >= len(keyNameMap) { + return "Unidentified" + } + name := keyNameMap[key] + if name == "" { + return "Unidentified" + } + return name +} + +func keyRune(key glfw.Key) rune { + if int(key) >= len(keyNameMap) { + return 0 + } + return keyRuneMap[key] +}