From 3591b390130eb6e555dcedb6f87b17fbbe29cc93 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 10 Sep 2018 02:22:17 -0400 Subject: [PATCH 1/2] WIP: Start of WebAssembly support. --- browser.go | 2 +- browser_wasm.go | 900 ++++++++++++++++++++++++++++++++++++++++++ context_webgl.go | 2 +- context_webgl_wasm.go | 55 +++ 4 files changed, 957 insertions(+), 2 deletions(-) create mode 100644 browser_wasm.go create mode 100644 context_webgl_wasm.go diff --git a/browser.go b/browser.go index 89893fc..1d5807d 100644 --- a/browser.go +++ b/browser.go @@ -1,4 +1,4 @@ -// +build js +// +build js,!wasm package glfw diff --git a/browser_wasm.go b/browser_wasm.go new file mode 100644 index 0000000..1e98883 --- /dev/null +++ b/browser_wasm.go @@ -0,0 +1,900 @@ +// +build js,wasm + +package glfw + +import ( + "errors" + "fmt" + "io" + "log" + "net/http" + "runtime" + "syscall/js" +) + +var document = js.Global().Get("document") + +var contextWatcher ContextWatcher + +func Init(cw ContextWatcher) error { + contextWatcher = cw + return nil +} + +func Terminate() error { + return nil +} + +func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Window, error) { + // THINK: Consider https://developer.mozilla.org/en-US/docs/Web/API/Window.open? + + // HACK: Go fullscreen? + width := js.Global().Get("innerWidth").Int() + height := js.Global().Get("innerHeight").Int() + + canvas := document.Call("createElement", "canvas") + + devicePixelRatio := js.Global().Get("devicePixelRatio").Float() + canvas.Set("width", int(float64(width)*devicePixelRatio+0.5)) // Nearest non-negative int. + canvas.Set("height", int(float64(height)*devicePixelRatio+0.5)) // Nearest non-negative int. + canvas.Get("style").Call("setProperty", "width", fmt.Sprintf("%vpx", width)) + canvas.Get("style").Call("setProperty", "height", fmt.Sprintf("%vpx", height)) + + if document.Get("body") == js.Null() { + body := document.Call("createElement", "body") + document.Set("body", body) + log.Println("Creating body, since it doesn't exist.") + } + document.Get("body").Get("style").Call("setProperty", "margin", "0") + document.Get("body").Call("appendChild", canvas) + + document.Set("title", title) + + // Use glfw hints. + attrs := defaultAttributes() + attrs.Alpha = (hints[AlphaBits] > 0) + if _, ok := hints[DepthBits]; ok { + attrs.Depth = (hints[DepthBits] > 0) + } + attrs.Stencil = (hints[StencilBits] > 0) + attrs.Antialias = (hints[Samples] > 0) + attrs.PremultipliedAlpha = (hints[PremultipliedAlpha] > 0) + attrs.PreserveDrawingBuffer = (hints[PreserveDrawingBuffer] > 0) + attrs.PreferLowPowerToHighPerformance = (hints[PreferLowPowerToHighPerformance] > 0) + attrs.FailIfMajorPerformanceCaveat = (hints[FailIfMajorPerformanceCaveat] > 0) + + // Create GL context. + context, err := newContext(canvas, attrs) + if err != nil { + return nil, err + } + + w := &Window{ + canvas: canvas, + context: context, + } + + if w.canvas.Get("requestPointerLock") == js.Undefined() || + document.Get("exitPointerLock") == js.Undefined() { + + w.missing.pointerLock = true + } + if w.canvas.Get("webkitRequestFullscreen") == js.Undefined() || + document.Get("webkitExitFullscreen") == js.Undefined() { + + w.missing.fullscreen = true + } + + if monitor != nil { + if w.missing.fullscreen { + log.Println("warning: Fullscreen API unsupported") + } else { + w.requestFullscreen = true + } + } + + /*dom.GetWindow().AddEventListener("resize", false, func(event dom.Event) { + // HACK: Go fullscreen? + width := dom.GetWindow().InnerWidth() + height := dom.GetWindow().InnerHeight() + + devicePixelRatio := js.Global.Get("devicePixelRatio").Float() + w.canvas.Width = int(float64(width)*devicePixelRatio + 0.5) // Nearest non-negative int. + w.canvas.Height = int(float64(height)*devicePixelRatio + 0.5) // Nearest non-negative int. + w.canvas.Style().SetProperty("width", fmt.Sprintf("%vpx", width), "") + w.canvas.Style().SetProperty("height", fmt.Sprintf("%vpx", height), "") + + if w.framebufferSizeCallback != nil { + // TODO: Callbacks may be blocking so they need to happen asyncronously. However, + // GLFW API promises the callbacks will occur from one thread (i.e., sequentially), so may want to do that. + go w.framebufferSizeCallback(w, w.canvas.Width, w.canvas.Height) + } + if w.sizeCallback != nil { + go w.sizeCallback(w, int(w.canvas.GetBoundingClientRect().Width), int(w.canvas.GetBoundingClientRect().Height)) + } + }) + + document.AddEventListener("keydown", false, func(event dom.Event) { + w.goFullscreenIfRequested() + + ke := event.(*dom.KeyboardEvent) + + action := Press + if ke.Repeat { + action = Repeat + } + + key := toKey(ke) + + // Extend slice if needed. + neededSize := int(key) + 1 + if neededSize > len(w.keys) { + w.keys = append(w.keys, make([]Action, neededSize-len(w.keys))...) + } + w.keys[key] = action + + if w.keyCallback != nil { + mods := toModifierKey(ke) + + go w.keyCallback(w, key, -1, action, mods) + } + + ke.PreventDefault() + }) + document.AddEventListener("keyup", false, func(event dom.Event) { + w.goFullscreenIfRequested() + + ke := event.(*dom.KeyboardEvent) + + key := toKey(ke) + + // Extend slice if needed. + neededSize := int(key) + 1 + if neededSize > len(w.keys) { + w.keys = append(w.keys, make([]Action, neededSize-len(w.keys))...) + } + w.keys[key] = Release + + if w.keyCallback != nil { + mods := toModifierKey(ke) + + go w.keyCallback(w, key, -1, Release, mods) + } + + ke.PreventDefault() + }) + + document.AddEventListener("mousedown", false, func(event dom.Event) { + w.goFullscreenIfRequested() + + me := event.(*dom.MouseEvent) + if !(me.Button >= 0 && me.Button <= 2) { + return + } + + w.mouseButton[me.Button] = Press + if w.mouseButtonCallback != nil { + go w.mouseButtonCallback(w, MouseButton(me.Button), Press, 0) + } + + me.PreventDefault() + }) + document.AddEventListener("mouseup", false, func(event dom.Event) { + w.goFullscreenIfRequested() + + me := event.(*dom.MouseEvent) + if !(me.Button >= 0 && me.Button <= 2) { + return + } + + w.mouseButton[me.Button] = Release + if w.mouseButtonCallback != nil { + go w.mouseButtonCallback(w, MouseButton(me.Button), Release, 0) + } + + me.PreventDefault() + })*/ + document.Call("addEventListener", "contextmenu", js.NewEventCallback(js.PreventDefault, func(js.Value) {})) + + document.Call("addEventListener", "mousemove", js.NewEventCallback(js.PreventDefault, func(me js.Value) { + var movementX, movementY float64 + if !w.missing.pointerLock { + movementX = me.Get("movementX").Float() + movementY = me.Get("movementY").Float() + } else { + movementX = me.Get("clientX").Float() - w.cursorPos[0] + movementY = me.Get("clientY").Float() - w.cursorPos[1] + } + + w.cursorPos[0], w.cursorPos[1] = me.Get("clientX").Float(), me.Get("clientY").Float() + if w.cursorPosCallback != nil { + go w.cursorPosCallback(w, w.cursorPos[0], w.cursorPos[1]) + } + if w.mouseMovementCallback != nil { + go w.mouseMovementCallback(w, w.cursorPos[0], w.cursorPos[1], movementX, movementY) + } + })) + /*document.AddEventListener("wheel", false, func(event dom.Event) { + we := event.(*dom.WheelEvent) + + var multiplier float64 + switch we.DeltaMode { + case dom.DeltaPixel: + multiplier = 0.1 + case dom.DeltaLine: + multiplier = 1 + default: + log.Println("unsupported WheelEvent.DeltaMode:", we.DeltaMode) + multiplier = 1 + } + + if w.scrollCallback != nil { + go w.scrollCallback(w, -we.DeltaX*multiplier, -we.DeltaY*multiplier) + } + + we.PreventDefault() + }) + + // Hacky mouse-emulation-via-touch. + touchHandler := func(event dom.Event) { + w.goFullscreenIfRequested() + + te := event.(*dom.TouchEvent) + + touches := te.Get("touches") + if touches.Length() > 0 { + t := touches.Index(0) + + if w.touches != nil && w.touches.Length() > 0 { // This event is a movement only if we previously had > 0 touch points. + if w.mouseMovementCallback != nil { + go w.mouseMovementCallback(w, t.Get("clientX").Float(), t.Get("clientY").Float(), t.Get("clientX").Float()-w.cursorPos[0], t.Get("clientY").Float()-w.cursorPos[1]) + } + } + + w.cursorPos[0], w.cursorPos[1] = t.Get("clientX").Float(), t.Get("clientY").Float() + if w.cursorPosCallback != nil { + go w.cursorPosCallback(w, w.cursorPos[0], w.cursorPos[1]) + } + } + w.touches = touches + + te.PreventDefault() + } + document.AddEventListener("touchstart", false, touchHandler) + document.AddEventListener("touchmove", false, touchHandler) + document.AddEventListener("touchend", false, touchHandler)*/ + + // Request first animation frame. + js.Global().Call("requestAnimationFrame", animationFrameCallback) + + return w, nil +} + +func SwapInterval(interval int) error { + // TODO: Implement. + return nil +} + +type Window struct { + canvas js.Value + context js.Value + requestFullscreen bool // requestFullscreen is set to true when fullscreen should be entered as soon as possible (in a user input handler). + fullscreen bool // fullscreen is true if we're currently in fullscreen mode. + + // Unavailable browser APIs. + missing struct { + pointerLock bool // Pointer Lock API. + fullscreen bool // Fullscreen API. + } + + cursorMode int + cursorPos [2]float64 + mouseButton [3]Action + + keys []Action + + cursorPosCallback CursorPosCallback + mouseMovementCallback MouseMovementCallback + mouseButtonCallback MouseButtonCallback + keyCallback KeyCallback + scrollCallback ScrollCallback + framebufferSizeCallback FramebufferSizeCallback + sizeCallback SizeCallback + + touches js.Value // Hacky mouse-emulation-via-touch. +} + +func (w *Window) SetPos(xpos, ypos int) { + fmt.Println("not implemented: SetPos:", xpos, ypos) +} + +func (w *Window) SetSize(width, height int) { + fmt.Println("not implemented: SetSize:", width, height) +} + +// goFullscreenIfRequested performs webkitRequestFullscreen if it was scheduled. It is called only from +// user events, because that API will fail if called at any other time. +func (w *Window) goFullscreenIfRequested() { + if !w.requestFullscreen { + return + } + w.requestFullscreen = false + w.canvas.Call("webkitRequestFullscreen") + w.fullscreen = true +} + +type Monitor struct{} + +func (m *Monitor) GetVideoMode() *VidMode { + return &VidMode{ + // HACK: Hardcoded sample values. + // TODO: Try to get real values from browser via some API, if possible. + Width: 1680, + Height: 1050, + RedBits: 8, + GreenBits: 8, + BlueBits: 8, + RefreshRate: 60, + } +} + +func GetPrimaryMonitor() *Monitor { + // TODO: Implement real functionality. + return &Monitor{} +} + +func PollEvents() error { + return nil +} + +func (w *Window) MakeContextCurrent() { + contextWatcher.OnMakeCurrent(w.context) +} + +func DetachCurrentContext() { + contextWatcher.OnDetach() +} + +func GetCurrentContext() *Window { + panic("not implemented") +} + +type CursorPosCallback func(w *Window, xpos float64, ypos float64) + +func (w *Window) SetCursorPosCallback(cbfun CursorPosCallback) (previous CursorPosCallback) { + w.cursorPosCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type MouseMovementCallback func(w *Window, xpos float64, ypos float64, xdelta float64, ydelta float64) + +func (w *Window) SetMouseMovementCallback(cbfun MouseMovementCallback) (previous MouseMovementCallback) { + w.mouseMovementCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type KeyCallback func(w *Window, key Key, scancode int, action Action, mods ModifierKey) + +func (w *Window) SetKeyCallback(cbfun KeyCallback) (previous KeyCallback) { + w.keyCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type CharCallback func(w *Window, char rune) + +func (w *Window) SetCharCallback(cbfun CharCallback) (previous CharCallback) { + // TODO. + return nil +} + +type ScrollCallback func(w *Window, xoff float64, yoff float64) + +func (w *Window) SetScrollCallback(cbfun ScrollCallback) (previous ScrollCallback) { + w.scrollCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type MouseButtonCallback func(w *Window, button MouseButton, action Action, mods ModifierKey) + +func (w *Window) SetMouseButtonCallback(cbfun MouseButtonCallback) (previous MouseButtonCallback) { + w.mouseButtonCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type FramebufferSizeCallback func(w *Window, width int, height int) + +func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) { + w.framebufferSizeCallback = cbfun + + // TODO: Handle previous. + return nil +} + +func (w *Window) GetSize() (width, height int) { + // TODO: See if dpi adjustments need to be made. + //fmt.Println("Window.GetSize:", w.canvas.GetBoundingClientRect().Width, w.canvas.GetBoundingClientRect().Height, + // "->", int(w.canvas.GetBoundingClientRect().Width), int(w.canvas.GetBoundingClientRect().Height)) + + return w.canvas.Call("getBoundingClientRect").Get("width").Int(), w.canvas.Call("getBoundingClientRect").Get("height").Int() +} + +func (w *Window) GetFramebufferSize() (width, height int) { + return w.canvas.Get("width").Int(), w.canvas.Get("height").Int() +} + +func (w *Window) GetPos() (x, y int) { + // Not implemented. + return +} + +func (w *Window) ShouldClose() bool { + return false +} + +func (w *Window) SetShouldClose(value bool) { + // TODO: Implement. + // THINK: What should happen in the browser if we're told to "close" the window. Do we destroy/remove the canvas? Or nothing? + // Perhaps https://developer.mozilla.org/en-US/docs/Web/API/Window.close is relevant. +} + +func (w *Window) SwapBuffers() error { + <-animationFrameChan + js.Global().Call("requestAnimationFrame", animationFrameCallback) + + return nil +} + +var animationFrameChan = make(chan struct{}, 1) + +var animationFrameCallback = js.NewCallback(func([]js.Value) { + animationFrameChan <- struct{}{} +}) + +func (w *Window) GetCursorPos() (x, y float64) { + return w.cursorPos[0], w.cursorPos[1] +} + +var keyWarnings = 10 + +func (w *Window) GetKey(key Key) Action { + if key == -1 && keyWarnings > 0 { + // TODO: Implement all keys, get rid of this. + keyWarnings-- + log.Println("GetKey: key not implemented.") + return Release + } + if int(key) >= len(w.keys) { + return Release + } + return w.keys[key] +} + +func (w *Window) GetMouseButton(button MouseButton) Action { + if !(button >= 0 && button <= 2) { + panic(fmt.Errorf("button is out of range: %v", button)) + } + + // Hacky mouse-emulation-via-touch. + if w.touches != (js.Value{}) { + switch button { + case MouseButton1: + if w.touches.Length() == 1 || w.touches.Length() == 3 { + return Press + } + case MouseButton2: + if w.touches.Length() == 2 || w.touches.Length() == 3 { + return Press + } + } + + return Release + } + + return w.mouseButton[button] +} + +func (w *Window) GetInputMode(mode InputMode) int { + switch mode { + case CursorMode: + return w.cursorMode + default: + panic(errors.New("not implemented")) + } +} + +var ErrInvalidParameter = errors.New("invalid parameter") +var ErrInvalidValue = errors.New("invalid value") + +func (w *Window) SetInputMode(mode InputMode, value int) { + switch mode { + case CursorMode: + if w.missing.pointerLock { + log.Println("warning: Pointer Lock API unsupported") + return + } + switch value { + case CursorNormal: + w.cursorMode = value + document.Call("exitPointerLock") + w.canvas.Get("style").Call("setProperty", "cursor", "initial") + return + case CursorHidden: + w.cursorMode = value + document.Call("exitPointerLock") + w.canvas.Get("style").Call("setProperty", "cursor", "none") + return + case CursorDisabled: + w.cursorMode = value + w.canvas.Call("requestPointerLock") + return + default: + panic(ErrInvalidValue) + } + case StickyKeysMode: + panic(errors.New("not implemented")) + case StickyMouseButtonsMode: + panic(errors.New("not implemented")) + default: + panic(ErrInvalidParameter) + } +} + +type Key int + +// TODO: Keys defined as -iota-1 need to be set to a valid positive value that matches the keyCode +// generated by browsers. -iota-1 is used as a temporary solution to have unique but invalid values. +// See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode. +const ( + KeySpace Key = 32 + KeyApostrophe Key = 222 + KeyComma Key = 188 + KeyMinus Key = 189 + KeyPeriod Key = 190 + KeySlash Key = 191 + Key0 Key = 48 + Key1 Key = 49 + Key2 Key = 50 + Key3 Key = 51 + Key4 Key = 52 + Key5 Key = 53 + Key6 Key = 54 + Key7 Key = 55 + Key8 Key = 56 + Key9 Key = 57 + KeySemicolon Key = 186 + KeyEqual Key = 187 + KeyA Key = 65 + KeyB Key = 66 + KeyC Key = 67 + KeyD Key = 68 + KeyE Key = 69 + KeyF Key = 70 + KeyG Key = 71 + KeyH Key = 72 + KeyI Key = 73 + KeyJ Key = 74 + KeyK Key = 75 + KeyL Key = 76 + KeyM Key = 77 + KeyN Key = 78 + KeyO Key = 79 + KeyP Key = 80 + KeyQ Key = 81 + KeyR Key = 82 + KeyS Key = 83 + KeyT Key = 84 + KeyU Key = 85 + KeyV Key = 86 + KeyW Key = 87 + KeyX Key = 88 + KeyY Key = 89 + KeyZ Key = 90 + KeyLeftBracket Key = 219 + KeyBackslash Key = 220 + KeyRightBracket Key = 221 + KeyGraveAccent Key = 192 + KeyWorld1 Key = -iota - 1 + KeyWorld2 Key = -iota - 1 + KeyEscape Key = 27 + KeyEnter Key = 13 + KeyTab Key = 9 + KeyBackspace Key = 8 + KeyInsert Key = -iota - 1 + KeyDelete Key = 46 + KeyRight Key = 39 + KeyLeft Key = 37 + KeyDown Key = 40 + KeyUp Key = 38 + KeyPageUp Key = -iota - 1 + KeyPageDown Key = -iota - 1 + KeyHome Key = -iota - 1 + KeyEnd Key = -iota - 1 + KeyCapsLock Key = 20 + KeyScrollLock Key = -iota - 1 + KeyNumLock Key = -iota - 1 + KeyPrintScreen Key = -iota - 1 + KeyPause Key = -iota - 1 + KeyF1 Key = 112 + KeyF2 Key = 113 + KeyF3 Key = 114 + KeyF4 Key = 115 + KeyF5 Key = 116 + KeyF6 Key = 117 + KeyF7 Key = 118 + KeyF8 Key = 119 + KeyF9 Key = 120 + KeyF10 Key = 121 + KeyF11 Key = 122 + KeyF12 Key = 123 + KeyF13 Key = -iota - 1 + KeyF14 Key = -iota - 1 + KeyF15 Key = -iota - 1 + KeyF16 Key = -iota - 1 + KeyF17 Key = -iota - 1 + KeyF18 Key = -iota - 1 + KeyF19 Key = -iota - 1 + KeyF20 Key = -iota - 1 + KeyF21 Key = -iota - 1 + KeyF22 Key = -iota - 1 + KeyF23 Key = -iota - 1 + KeyF24 Key = -iota - 1 + KeyF25 Key = -iota - 1 + KeyKP0 Key = -iota - 1 + KeyKP1 Key = -iota - 1 + KeyKP2 Key = -iota - 1 + KeyKP3 Key = -iota - 1 + KeyKP4 Key = -iota - 1 + KeyKP5 Key = -iota - 1 + KeyKP6 Key = -iota - 1 + KeyKP7 Key = -iota - 1 + KeyKP8 Key = -iota - 1 + KeyKP9 Key = -iota - 1 + KeyKPDecimal Key = -iota - 1 + KeyKPDivide Key = -iota - 1 + KeyKPMultiply Key = -iota - 1 + KeyKPSubtract Key = -iota - 1 + KeyKPAdd Key = -iota - 1 + KeyKPEnter Key = -iota - 1 + KeyKPEqual Key = -iota - 1 + KeyLeftShift Key = 340 + KeyLeftControl Key = 341 + KeyLeftAlt Key = 342 + KeyLeftSuper Key = 91 + KeyRightShift Key = 344 + KeyRightControl Key = 345 + KeyRightAlt Key = 346 + KeyRightSuper Key = 93 + KeyMenu Key = -iota - 1 +) + +// toKey extracts Key from given KeyboardEvent. +/*func toKey(ke *dom.KeyboardEvent) Key { + key := Key(ke.KeyCode) + switch { + case key == 16 && ke.Location == dom.KeyLocationLeft: + key = KeyLeftShift + case key == 16 && ke.Location == dom.KeyLocationRight: + key = KeyRightShift + case key == 17 && ke.Location == dom.KeyLocationLeft: + key = KeyLeftControl + case key == 17 && ke.Location == dom.KeyLocationRight: + key = KeyRightControl + case key == 18 && ke.Location == dom.KeyLocationLeft: + key = KeyLeftAlt + case key == 18 && ke.Location == dom.KeyLocationRight: + key = KeyRightAlt + } + return key +} + +// toModifierKey extracts ModifierKey from given KeyboardEvent. +func toModifierKey(ke *dom.KeyboardEvent) ModifierKey { + mods := ModifierKey(0) + if ke.ShiftKey { + mods += ModShift + } + if ke.CtrlKey { + mods += ModControl + } + if ke.AltKey { + mods += ModAlt + } + if ke.MetaKey { + mods += ModSuper + } + return mods +}*/ + +type MouseButton int + +const ( + MouseButton1 MouseButton = 0 + MouseButton2 MouseButton = 2 // Web MouseEvent has middle and right mouse buttons in reverse order. + MouseButton3 MouseButton = 1 // Web MouseEvent has middle and right mouse buttons in reverse order. + + MouseButtonLeft = MouseButton1 + MouseButtonRight = MouseButton2 + MouseButtonMiddle = MouseButton3 +) + +type Action int + +const ( + Release Action = 0 + Press Action = 1 + Repeat Action = 2 +) + +type InputMode int + +const ( + CursorMode InputMode = iota + StickyKeysMode + StickyMouseButtonsMode +) + +const ( + CursorNormal = iota + CursorHidden + CursorDisabled +) + +type ModifierKey int + +const ( + ModShift ModifierKey = (1 << iota) + ModControl + ModAlt + ModSuper +) + +// Open opens a named asset. It's the caller's responsibility to close it when done. +func Open(name string) (io.ReadCloser, error) { + resp, err := http.Get(name) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("non-200 status: %s", resp.Status) + } + return resp.Body, nil +} + +// --- + +func WaitEvents() { + // TODO. + + runtime.Gosched() +} + +func PostEmptyEvent() { + // TODO: Implement. +} + +func DefaultWindowHints() { + // TODO: Implement. +} + +func (w *Window) SetClipboardString(str string) { + // TODO: Implement. +} +func (w *Window) GetClipboardString() (string, error) { + // TODO: Implement. + return "", errors.New("GetClipboardString not implemented") +} + +func (w *Window) SetTitle(title string) { + document.Set("title", title) +} + +func (w *Window) Show() { + // TODO: Implement. +} + +func (w *Window) Hide() { + // TODO: Implement. +} + +func (w *Window) Destroy() { + document.Get("body").Call("removeChild", w.canvas) + if w.fullscreen { + if w.missing.fullscreen { + log.Println("warning: Fullscreen API unsupported") + } else { + document.Call("webkitExitFullscreen") + w.fullscreen = false + } + } +} + +type CloseCallback func(w *Window) + +func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type RefreshCallback func(w *Window) + +func (w *Window) SetRefreshCallback(cbfun RefreshCallback) (previous RefreshCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type SizeCallback func(w *Window, width int, height int) + +func (w *Window) SetSizeCallback(cbfun SizeCallback) (previous SizeCallback) { + w.sizeCallback = cbfun + + // TODO: Handle previous. + return nil +} + +type CursorEnterCallback func(w *Window, entered bool) + +func (w *Window) SetCursorEnterCallback(cbfun CursorEnterCallback) (previous CursorEnterCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type CharModsCallback func(w *Window, char rune, mods ModifierKey) + +func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type PosCallback func(w *Window, xpos int, ypos int) + +func (w *Window) SetPosCallback(cbfun PosCallback) (previous PosCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type FocusCallback func(w *Window, focused bool) + +func (w *Window) SetFocusCallback(cbfun FocusCallback) (previous FocusCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type IconifyCallback func(w *Window, iconified bool) + +func (w *Window) SetIconifyCallback(cbfun IconifyCallback) (previous IconifyCallback) { + // TODO: Implement. + + // TODO: Handle previous. + return nil +} + +type DropCallback func(w *Window, names []string) + +func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback) { + // TODO: Implement. Can use HTML5 file drag and drop API? + + // TODO: Handle previous. + return nil +} diff --git a/context_webgl.go b/context_webgl.go index fb7e6de..fb9bb93 100644 --- a/context_webgl.go +++ b/context_webgl.go @@ -1,4 +1,4 @@ -// +build js +// +build js,!wasm package glfw diff --git a/context_webgl_wasm.go b/context_webgl_wasm.go new file mode 100644 index 0000000..680bf76 --- /dev/null +++ b/context_webgl_wasm.go @@ -0,0 +1,55 @@ +// +build js,wasm + +package glfw + +import ( + "errors" + "syscall/js" +) + +func newContext(canvas js.Value, ca *contextAttributes) (context js.Value, err error) { + if js.Global().Get("WebGLRenderingContext") == js.Undefined() { + return js.Value{}, errors.New("Your browser doesn't appear to support WebGL.") + } + + attrs := map[string]interface{}{ + "alpha": ca.Alpha, + "depth": ca.Depth, + "stencil": ca.Stencil, + "antialias": ca.Antialias, + "premultipliedAlpha": ca.PremultipliedAlpha, + "preserveDrawingBuffer": ca.PreserveDrawingBuffer, + "preferLowPowerToHighPerformance": ca.PreferLowPowerToHighPerformance, + "failIfMajorPerformanceCaveat": ca.FailIfMajorPerformanceCaveat, + } + + if gl := canvas.Call("getContext", "webgl", attrs); gl != js.Null() { + return gl, nil + } else if gl := canvas.Call("getContext", "experimental-webgl", attrs); gl != js.Null() { + return gl, nil + } else { + return js.Value{}, errors.New("Creating a WebGL context has failed.") + } +} + +type contextAttributes struct { + Alpha bool + Depth bool + Stencil bool + Antialias bool + PremultipliedAlpha bool + PreserveDrawingBuffer bool + PreferLowPowerToHighPerformance bool + FailIfMajorPerformanceCaveat bool +} + +func defaultAttributes() *contextAttributes { + return &contextAttributes{ + Alpha: false, + Depth: true, + Stencil: false, + Antialias: false, + PremultipliedAlpha: false, + PreserveDrawingBuffer: false, + } +} From b44d6b5d1c6150c88ebe11a492e8e3737350691f Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Wed, 12 Sep 2018 03:54:14 -0400 Subject: [PATCH 2/2] WIP: Work on WebAssembly support. --- browser_wasm.go | 76 ++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/browser_wasm.go b/browser_wasm.go index 1e98883..d7c186f 100644 --- a/browser_wasm.go +++ b/browser_wasm.go @@ -93,34 +93,32 @@ func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Win } } - /*dom.GetWindow().AddEventListener("resize", false, func(event dom.Event) { + js.Global().Call("addEventListener", "resize", js.NewEventCallback(0, func(event js.Value) { // HACK: Go fullscreen? - width := dom.GetWindow().InnerWidth() - height := dom.GetWindow().InnerHeight() + width := js.Global().Get("innerWidth").Int() + height := js.Global().Get("innerHeight").Int() - devicePixelRatio := js.Global.Get("devicePixelRatio").Float() - w.canvas.Width = int(float64(width)*devicePixelRatio + 0.5) // Nearest non-negative int. - w.canvas.Height = int(float64(height)*devicePixelRatio + 0.5) // Nearest non-negative int. - w.canvas.Style().SetProperty("width", fmt.Sprintf("%vpx", width), "") - w.canvas.Style().SetProperty("height", fmt.Sprintf("%vpx", height), "") + devicePixelRatio := js.Global().Get("devicePixelRatio").Float() + canvas.Set("width", int(float64(width)*devicePixelRatio+0.5)) // Nearest non-negative int. + canvas.Set("height", int(float64(height)*devicePixelRatio+0.5)) // Nearest non-negative int. + canvas.Get("style").Call("setProperty", "width", fmt.Sprintf("%vpx", width)) + canvas.Get("style").Call("setProperty", "height", fmt.Sprintf("%vpx", height)) if w.framebufferSizeCallback != nil { // TODO: Callbacks may be blocking so they need to happen asyncronously. However, // GLFW API promises the callbacks will occur from one thread (i.e., sequentially), so may want to do that. - go w.framebufferSizeCallback(w, w.canvas.Width, w.canvas.Height) + go w.framebufferSizeCallback(w, w.canvas.Get("width").Int(), w.canvas.Get("height").Int()) } if w.sizeCallback != nil { - go w.sizeCallback(w, int(w.canvas.GetBoundingClientRect().Width), int(w.canvas.GetBoundingClientRect().Height)) + go w.sizeCallback(w, w.canvas.Call("getBoundingClientRect").Get("width").Int(), w.canvas.Call("getBoundingClientRect").Get("height").Int()) } - }) + })) - document.AddEventListener("keydown", false, func(event dom.Event) { + document.Call("addEventListener", "keydown", js.NewEventCallback(js.PreventDefault, func(ke js.Value) { w.goFullscreenIfRequested() - ke := event.(*dom.KeyboardEvent) - action := Press - if ke.Repeat { + if ke.Get("repeat").Bool() { action = Repeat } @@ -138,14 +136,10 @@ func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Win go w.keyCallback(w, key, -1, action, mods) } - - ke.PreventDefault() - }) - document.AddEventListener("keyup", false, func(event dom.Event) { + })) + document.Call("addEventListener", "keyup", js.NewEventCallback(js.PreventDefault, func(ke js.Value) { w.goFullscreenIfRequested() - ke := event.(*dom.KeyboardEvent) - key := toKey(ke) // Extend slice if needed. @@ -160,11 +154,9 @@ func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Win go w.keyCallback(w, key, -1, Release, mods) } + })) - ke.PreventDefault() - }) - - document.AddEventListener("mousedown", false, func(event dom.Event) { + /*document.AddEventListener("mousedown", false, func(event dom.Event) { w.goFullscreenIfRequested() me := event.(*dom.MouseEvent) @@ -678,42 +670,48 @@ const ( ) // toKey extracts Key from given KeyboardEvent. -/*func toKey(ke *dom.KeyboardEvent) Key { - key := Key(ke.KeyCode) +func toKey(ke js.Value) Key { + // TODO: Factor out into DOM package. + const ( + KeyLocationLeft = 1 + KeyLocationRight = 2 + ) + + key := Key(ke.Get("keyCode").Int()) switch { - case key == 16 && ke.Location == dom.KeyLocationLeft: + case key == 16 && ke.Get("location").Int() == KeyLocationLeft: key = KeyLeftShift - case key == 16 && ke.Location == dom.KeyLocationRight: + case key == 16 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightShift - case key == 17 && ke.Location == dom.KeyLocationLeft: + case key == 17 && ke.Get("location").Int() == KeyLocationLeft: key = KeyLeftControl - case key == 17 && ke.Location == dom.KeyLocationRight: + case key == 17 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightControl - case key == 18 && ke.Location == dom.KeyLocationLeft: + case key == 18 && ke.Get("location").Int() == KeyLocationLeft: key = KeyLeftAlt - case key == 18 && ke.Location == dom.KeyLocationRight: + case key == 18 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightAlt } return key } // toModifierKey extracts ModifierKey from given KeyboardEvent. -func toModifierKey(ke *dom.KeyboardEvent) ModifierKey { +func toModifierKey(ke js.Value) ModifierKey { mods := ModifierKey(0) - if ke.ShiftKey { + if ke.Get("shiftKey").Bool() { mods += ModShift } - if ke.CtrlKey { + if ke.Get("ctrlKey").Bool() { mods += ModControl } - if ke.AltKey { + if ke.Get("altKey").Bool() { mods += ModAlt } - if ke.MetaKey { + if ke.Get("metaKey").Bool() { mods += ModSuper } return mods -}*/ +} type MouseButton int