//go:build wasm package glfw import ( "errors" "fmt" "io" "log" "net/http" "runtime" "syscall/js" ) const ( True int = 1 False int = 0 DontCare int = -1 ) var document = js.Global().Get("document") var contextWatcher ContextWatcher func Init(cw ContextWatcher) error { contextWatcher = cw return nil } func Terminate() error { return nil } //gocyclo:ignore func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Window, error) { // THINK: Consider https://developer.mozilla.org/en-US/docs/Web/API/Window.open? body := document.Get("body") if body.Equal(js.Null()) { body = document.Call("createElement", "body") document.Set("body", body) } body.Get("style").Call("setProperty", "margin", "0") canvas := document.Call("createElement", "canvas") body.Call("appendChild", canvas) // HACK: Go fullscreen /* canvas being sized asynchronously, we are using body the window inner Width/Height */? width := js.Global().Get("innerWidth").Int() height := js.Global().Get("innerHeight").Int() 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. // Use dvw and dvh if supported; otherwise, fall back to vw and vh. style := canvas.Get("style") setProperty := style.Get("setProperty").Call("bind", style) setProperty.Invoke("width", "100vw") setProperty.Invoke("width", "100dvw") setProperty.Invoke("height", "100vh") setProperty.Invoke("height", "100dvh") 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 context.Equal(js.Value{}) { return nil, err } w := &Window{ canvas: canvas, context: context, devicePixelRatio: devicePixelRatio, } if w.canvas.Get("requestPointerLock").Equal(js.Undefined()) || document.Get("exitPointerLock").Equal(js.Undefined()) { w.missing.pointerLock = true } if w.canvas.Get("webkitRequestFullscreen").Equal(js.Undefined()) || document.Get("webkitExitFullscreen").Equal(js.Undefined()) { w.missing.fullscreen = true } if monitor != nil { if w.missing.fullscreen { log.Println("warning: Fullscreen API unsupported") } else { w.requestFullscreen = true } } w.eventHandlerCleanups = make([]func(), 0, 11) newJsFuncFrom := func(handler func(this js.Value, args []js.Value) any) js.Func { function := js.FuncOf(handler) w.eventHandlerCleanups = append(w.eventHandlerCleanups, function.Release) return function } addGlobalEventListener := js.Global().Get("addEventListener").Call("bind", js.Global()) addGlobalEventListener.Invoke("focus", newJsFuncFrom(func(this js.Value, args []js.Value) any { if w.focusCallback != nil { w.focusCallback(w, true) } return nil })) addGlobalEventListener.Invoke("blur", newJsFuncFrom(func(this js.Value, args []js.Value) any { if w.focusCallback != nil { w.focusCallback(w, false) } return nil })) addGlobalEventListener.Invoke("resize", newJsFuncFrom(func(this js.Value, args []js.Value) any { // HACK: Go fullscreen? w.devicePixelRatio = js.Global().Get("devicePixelRatio").Float() widthScaled, heightScaled := w.GetSize() canvas.Set("width", widthScaled) canvas.Set("height", heightScaled) 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. widthFramebuffer, heightFramebuffer := w.GetFramebufferSize() go w.framebufferSizeCallback(w, widthFramebuffer, heightFramebuffer) } if w.sizeCallback != nil { go w.sizeCallback(w, widthScaled, heightScaled) } return nil })) addDocumentEventListener := document.Get("addEventListener").Call("bind", document) addDocumentEventListener.Invoke("keydown", newJsFuncFrom(func(this js.Value, args []js.Value) any { ke := args[0] w.goFullscreenIfRequested() action := Press if ke.Get("repeat").Bool() { 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 mods := toModifierKey(ke) if w.keyCallback != nil { go w.keyCallback(w, key, -1, action, mods) } if w.charCallback != nil && mods < 2 { keyStr := ke.Get("key").String() if len(keyStr) == 1 { keyRune := []rune(keyStr) go w.charCallback(w, keyRune[0]) } } ke.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("keyup", newJsFuncFrom(func(this js.Value, args []js.Value) any { ke := args[0] w.goFullscreenIfRequested() 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.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("mousedown", newJsFuncFrom(func(this js.Value, args []js.Value) any { me := args[0] w.goFullscreenIfRequested() button := me.Get("button").Int() if !(button >= 0 && button <= 2) { return nil } w.mouseButton[button] = Press if w.mouseButtonCallback != nil { go w.mouseButtonCallback(w, MouseButton(button), Press, 0) } me.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("mouseup", newJsFuncFrom(func(this js.Value, args []js.Value) any { me := args[0] w.goFullscreenIfRequested() button := me.Get("button").Int() if !(button >= 0 && button <= 2) { return nil } w.mouseButton[button] = Release if w.mouseButtonCallback != nil { go w.mouseButtonCallback(w, MouseButton(button), Release, 0) } me.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("contextmenu", newJsFuncFrom(func(this js.Value, args []js.Value) any { me := args[0] me.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("mousemove", newJsFuncFrom(func(this js.Value, args []js.Value) any { me := args[0] 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] } movementX *= w.devicePixelRatio movementY *= w.devicePixelRatio w.cursorPos[0], w.cursorPos[1] = me.Get("clientX").Float()*w.devicePixelRatio, me.Get("clientY").Float()*w.devicePixelRatio 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) } me.Call("preventDefault") return nil })) addDocumentEventListener.Invoke("wheel", newJsFuncFrom(func(this js.Value, args []js.Value) any { we := args[0] deltaX := we.Get("deltaX").Float() deltaY := we.Get("deltaY").Float() 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 }*/ multiplier = 1 if w.scrollCallback != nil { go w.scrollCallback(w, -deltaX*multiplier, -deltaY*multiplier) } we.Call("preventDefault") return nil })) /* // 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)*/ addDocumentEventListener.Invoke("beforeUnload", newJsFuncFrom(func(this js.Value, args []js.Value) any { if w.closeCallback != nil { w.closeCallback(w) } return nil })) // Request first animation frame. w.requestAnimationFrame = js.Global().Get("requestAnimationFrame").Call("bind", js.Global()) w.requestAnimationFrame.Invoke(animationFrameCallback) return w, nil } func (w *Window) SetAttrib(attrib Hint, value int) { // TODO: Implement. } 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. } devicePixelRatio float64 cursorMode int cursorPos [2]float64 mouseButton [3]Action keys []Action cursorPosCallback CursorPosCallback mouseMovementCallback MouseMovementCallback mouseButtonCallback MouseButtonCallback keyCallback KeyCallback scrollCallback ScrollCallback charCallback CharCallback framebufferSizeCallback FramebufferSizeCallback sizeCallback SizeCallback focusCallback FocusCallback closeCallback CloseCallback touches js.Value // Hacky mouse-emulation-via-touch. eventHandlerCleanups []func() requestAnimationFrame js.Value } 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) } func (w *Window) SetIcon(images any) { // images is actually of type []image.Image, but no need to import image until we actually do something with it fmt.Println("not implemented: SetIcon") } // 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 (w *Window) SetMonitor(monitor *Monitor, xpos, ypos, width, height, refreshRate int) { // TODO: Implement real functionality. } 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) { w.charCallback = cbfun // TODO: Handle previous. 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 } // Nearest non-negative int. func (w *Window) scaleRound(f float64) int { return int(f*w.devicePixelRatio + 0.5) } func (w *Window) GetSize() (width, height int) { return w.scaleRound(w.canvas.Get("clientWidth").Float()), w.scaleRound(w.canvas.Get("clientHeight").Float()) } 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 w.requestAnimationFrame.Invoke(animationFrameCallback) return nil } var animationFrameChan = make(chan struct{}, 1) var animationFrameCallback = js.FuncOf(func(this js.Value, args []js.Value) any { animationFrameChan <- struct{}{} return nil }) 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.Equal(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") ErrInvalidValue = errors.New("invalid value") ) func (w *Window) SetInputMode(mode InputMode, value int) { switch mode { case CursorMode: // TODO; Make cursor API compatible with GLFW and Fyne use/expectation. /* // Temporarily disable cursor change 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) } */ return 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-2 need to be set to a valid positive value that matches the keyCode // // generated by browsers. -iota-2 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 ( KeyUnknown Key = -1 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 - 2 KeyWorld2 Key = -iota - 2 KeyEscape Key = 27 KeyEnter Key = 13 KeyTab Key = 9 KeyBackspace Key = 8 KeyInsert Key = -iota - 2 KeyDelete Key = 46 KeyRight Key = 39 KeyLeft Key = 37 KeyDown Key = 40 KeyUp Key = 38 KeyPageUp Key = -iota - 2 KeyPageDown Key = -iota - 2 KeyHome Key = -iota - 2 KeyEnd Key = -iota - 2 KeyCapsLock Key = 20 KeyScrollLock Key = -iota - 2 KeyNumLock Key = -iota - 2 KeyPrintScreen Key = -iota - 2 KeyPause Key = -iota - 2 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 - 2 KeyF14 Key = -iota - 2 KeyF15 Key = -iota - 2 KeyF16 Key = -iota - 2 KeyF17 Key = -iota - 2 KeyF18 Key = -iota - 2 KeyF19 Key = -iota - 2 KeyF20 Key = -iota - 2 KeyF21 Key = -iota - 2 KeyF22 Key = -iota - 2 KeyF23 Key = -iota - 2 KeyF24 Key = -iota - 2 KeyF25 Key = -iota - 2 KeyKP0 Key = -iota - 2 KeyKP1 Key = -iota - 2 KeyKP2 Key = -iota - 2 KeyKP3 Key = -iota - 2 KeyKP4 Key = -iota - 2 KeyKP5 Key = -iota - 2 KeyKP6 Key = -iota - 2 KeyKP7 Key = -iota - 2 KeyKP8 Key = -iota - 2 KeyKP9 Key = -iota - 2 KeyKPDecimal Key = -iota - 2 KeyKPDivide Key = -iota - 2 KeyKPMultiply Key = -iota - 2 KeyKPSubtract Key = -iota - 2 KeyKPAdd Key = -iota - 2 KeyKPEnter Key = -iota - 2 KeyKPEqual Key = -iota - 2 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 - 2 ) // toKey extracts Key from given KeyboardEvent. 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.Get("location").Int() == KeyLocationLeft: key = KeyLeftShift case key == 16 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightShift case key == 17 && ke.Get("location").Int() == KeyLocationLeft: key = KeyLeftControl case key == 17 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightControl case key == 18 && ke.Get("location").Int() == KeyLocationLeft: key = KeyLeftAlt case key == 18 && ke.Get("location").Int() == KeyLocationRight: key = KeyRightAlt } return key } // toModifierKey extracts ModifierKey from given KeyboardEvent. func toModifierKey(ke js.Value) ModifierKey { mods := ModifierKey(0) if ke.Get("shiftKey").Bool() { mods += ModShift } if ke.Get("ctrlKey").Bool() { mods += ModControl } if ke.Get("altKey").Bool() { mods += ModAlt } if ke.Get("metaKey").Bool() { 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 Joystick int const ( Joystick1 Joystick = iota Joystick2 Joystick3 Joystick4 Joystick5 Joystick6 Joystick7 Joystick8 Joystick9 Joystick10 Joystick11 Joystick12 Joystick13 Joystick14 Joystick15 Joystick16 JoystickLast = Joystick16 ) type GamepadAxis int const ( AxisLeftX GamepadAxis = iota AxisLeftY AxisRightX AxisRightY AxisLeftTrigger AxisRightTrigger AxisLast = AxisRightTrigger ) type GamepadButton int const ( ButtonA GamepadButton = iota ButtonB ButtonX ButtonY ButtonLeftBumper ButtonRightBumper ButtonBack ButtonStart ButtonGuide ButtonLeftThumb ButtonRightThumb ButtonDpadUp ButtonDpadRight ButtonDpadDown ButtonDpadLeft ButtonLast = ButtonDpadLeft ButtonCross = ButtonA ButtonCircle = ButtonB ButtonSquare = ButtonX ButtonTriangle = ButtonY ) type Action int const ( Release Action = 0 Press Action = 1 Repeat Action = 2 ) type InputMode int const ( CursorMode InputMode = iota StickyKeysMode StickyMouseButtonsMode LockKeyMods RawMouseMotion ) const ( CursorNormal = iota CursorHidden CursorDisabled ) type ModifierKey int const ( ModShift ModifierKey = (1 << iota) ModControl ModAlt ModSuper ) func (joy Joystick) IsPresent() bool { // TODO: Implement. return false } func (joy Joystick) GetGamepadName() string { // TODO: Implement. return "Gamepad" } func (joy Joystick) GetButtons() []Action { // TODO: Implement. return make([]Action, 0) } func (joy Joystick) GetAxes() []float32 { // TODO: Implement. return make([]float32, 0) } // 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) { SetClipboardString(str) } func (w *Window) GetClipboardString() string { return GetClipboardString() } 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 } } for _, free := range w.eventHandlerCleanups { free() } } type CloseCallback func(w *Window) func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback) { previous = w.closeCallback w.closeCallback = cbfun return previous } 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) { previous = w.focusCallback w.focusCallback = cbfun return previous } 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 }