browser_wasm.go 25 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
  1. //go:build js && wasm
  2. // +build js,wasm
  3. package glfw
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net/http"
  10. "runtime"
  11. "syscall/js"
  12. )
  13. const (
  14. True int = 1
  15. False int = 0
  16. DontCare int = -1
  17. )
  18. var document = js.Global().Get("document")
  19. var contextWatcher ContextWatcher
  20. func Init(cw ContextWatcher) error {
  21. contextWatcher = cw
  22. return nil
  23. }
  24. func Terminate() error {
  25. return nil
  26. }
  27. func CreateWindow(_, _ int, title string, monitor *Monitor, share *Window) (*Window, error) {
  28. // THINK: Consider https://developer.mozilla.org/en-US/docs/Web/API/Window.open?
  29. body := document.Get("body")
  30. if body.Equal(js.Null()) {
  31. body = document.Call("createElement", "body")
  32. document.Set("body", body)
  33. }
  34. body.Get("style").Call("setProperty", "margin", "0")
  35. canvas := document.Call("createElement", "canvas")
  36. body.Call("appendChild", canvas)
  37. // HACK: Go fullscreen /* canvas being sized asynchronously, we are using body the window inner Width/Height */?
  38. width := js.Global().Get("innerWidth").Int()
  39. height := js.Global().Get("innerHeight").Int()
  40. devicePixelRatio := js.Global().Get("devicePixelRatio").Float()
  41. canvas.Set("width", int(float64(width)*devicePixelRatio+0.5)) // Nearest non-negative int.
  42. canvas.Set("height", int(float64(height)*devicePixelRatio+0.5)) // Nearest non-negative int.
  43. canvas.Get("style").Call("setProperty", "width", "100vw")
  44. canvas.Get("style").Call("setProperty", "height", "100vh")
  45. document.Set("title", title)
  46. // Use glfw hints.
  47. attrs := defaultAttributes()
  48. attrs.Alpha = (hints[AlphaBits] > 0)
  49. if _, ok := hints[DepthBits]; ok {
  50. attrs.Depth = (hints[DepthBits] > 0)
  51. }
  52. attrs.Stencil = (hints[StencilBits] > 0)
  53. attrs.Antialias = (hints[Samples] > 0)
  54. attrs.PremultipliedAlpha = (hints[PremultipliedAlpha] > 0)
  55. attrs.PreserveDrawingBuffer = (hints[PreserveDrawingBuffer] > 0)
  56. attrs.PreferLowPowerToHighPerformance = (hints[PreferLowPowerToHighPerformance] > 0)
  57. attrs.FailIfMajorPerformanceCaveat = (hints[FailIfMajorPerformanceCaveat] > 0)
  58. // Create GL context.
  59. context, err := newContext(canvas, attrs)
  60. if context.Equal(js.Value{}) {
  61. return nil, err
  62. }
  63. w := &Window{
  64. canvas: canvas,
  65. context: context,
  66. devicePixelRatio: devicePixelRatio,
  67. }
  68. if w.canvas.Get("requestPointerLock").Equal(js.Undefined()) ||
  69. document.Get("exitPointerLock").Equal(js.Undefined()) {
  70. w.missing.pointerLock = true
  71. }
  72. if w.canvas.Get("webkitRequestFullscreen").Equal(js.Undefined()) ||
  73. document.Get("webkitExitFullscreen").Equal(js.Undefined()) {
  74. w.missing.fullscreen = true
  75. }
  76. if monitor != nil {
  77. if w.missing.fullscreen {
  78. log.Println("warning: Fullscreen API unsupported")
  79. } else {
  80. w.requestFullscreen = true
  81. }
  82. }
  83. js.Global().Call("addEventListener", "resize", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  84. // HACK: Go fullscreen?
  85. w.devicePixelRatio = js.Global().Get("devicePixelRatio").Float()
  86. widthScaled, heightScaled := w.GetSize()
  87. canvas.Set("width", widthScaled)
  88. canvas.Set("height", heightScaled)
  89. if w.framebufferSizeCallback != nil {
  90. // TODO: Callbacks may be blocking so they need to happen asyncronously. However,
  91. // GLFW API promises the callbacks will occur from one thread (i.e., sequentially), so may want to do that.
  92. widthFramebuffer, heightFramebuffer := w.GetFramebufferSize()
  93. go w.framebufferSizeCallback(w, widthFramebuffer, heightFramebuffer)
  94. }
  95. if w.sizeCallback != nil {
  96. go w.sizeCallback(w, widthScaled, heightScaled)
  97. }
  98. return nil
  99. }))
  100. document.Call("addEventListener", "keydown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  101. ke := args[0]
  102. w.goFullscreenIfRequested()
  103. action := Press
  104. if ke.Get("repeat").Bool() {
  105. action = Repeat
  106. }
  107. key := toKey(ke)
  108. // Extend slice if needed.
  109. neededSize := int(key) + 1
  110. if neededSize > len(w.keys) {
  111. w.keys = append(w.keys, make([]Action, neededSize-len(w.keys))...)
  112. }
  113. w.keys[key] = action
  114. if w.keyCallback != nil {
  115. mods := toModifierKey(ke)
  116. go w.keyCallback(w, key, -1, action, mods)
  117. }
  118. if w.charCallback != nil {
  119. keyStr := ke.Get("key").String()
  120. if len(keyStr) == 1 {
  121. keyRune := []rune(keyStr)
  122. go w.charCallback(w, keyRune[0])
  123. }
  124. }
  125. ke.Call("preventDefault")
  126. return nil
  127. }))
  128. document.Call("addEventListener", "keyup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  129. ke := args[0]
  130. w.goFullscreenIfRequested()
  131. key := toKey(ke)
  132. // Extend slice if needed.
  133. neededSize := int(key) + 1
  134. if neededSize > len(w.keys) {
  135. w.keys = append(w.keys, make([]Action, neededSize-len(w.keys))...)
  136. }
  137. w.keys[key] = Release
  138. if w.keyCallback != nil {
  139. mods := toModifierKey(ke)
  140. go w.keyCallback(w, key, -1, Release, mods)
  141. }
  142. ke.Call("preventDefault")
  143. return nil
  144. }))
  145. document.Call("addEventListener", "mousedown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  146. me := args[0]
  147. w.goFullscreenIfRequested()
  148. button := me.Get("button").Int()
  149. if !(button >= 0 && button <= 2) {
  150. return nil
  151. }
  152. w.mouseButton[button] = Press
  153. if w.mouseButtonCallback != nil {
  154. go w.mouseButtonCallback(w, MouseButton(button), Press, 0)
  155. }
  156. me.Call("preventDefault")
  157. return nil
  158. }))
  159. document.Call("addEventListener", "mouseup", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  160. me := args[0]
  161. w.goFullscreenIfRequested()
  162. button := me.Get("button").Int()
  163. if !(button >= 0 && button <= 2) {
  164. return nil
  165. }
  166. w.mouseButton[button] = Release
  167. if w.mouseButtonCallback != nil {
  168. go w.mouseButtonCallback(w, MouseButton(button), Release, 0)
  169. }
  170. me.Call("preventDefault")
  171. return nil
  172. }))
  173. document.Call("addEventListener", "contextmenu", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  174. me := args[0]
  175. me.Call("preventDefault")
  176. return nil
  177. }))
  178. document.Call("addEventListener", "mousemove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  179. me := args[0]
  180. var movementX, movementY float64
  181. if !w.missing.pointerLock {
  182. movementX = me.Get("movementX").Float()
  183. movementY = me.Get("movementY").Float()
  184. } else {
  185. movementX = me.Get("clientX").Float() - w.cursorPos[0]
  186. movementY = me.Get("clientY").Float() - w.cursorPos[1]
  187. }
  188. movementX *= w.devicePixelRatio
  189. movementY *= w.devicePixelRatio
  190. w.cursorPos[0], w.cursorPos[1] = me.Get("clientX").Float()*w.devicePixelRatio, me.Get("clientY").Float()*w.devicePixelRatio
  191. if w.cursorPosCallback != nil {
  192. go w.cursorPosCallback(w, w.cursorPos[0], w.cursorPos[1])
  193. }
  194. if w.mouseMovementCallback != nil {
  195. go w.mouseMovementCallback(w, w.cursorPos[0], w.cursorPos[1], movementX, movementY)
  196. }
  197. me.Call("preventDefault")
  198. return nil
  199. }))
  200. document.Call("addEventListener", "wheel", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  201. we := args[0]
  202. deltaX := we.Get("deltaX").Float()
  203. deltaY := we.Get("deltaY").Float()
  204. var multiplier float64
  205. /*
  206. switch we.DeltaMode {
  207. case dom.DeltaPixel:
  208. multiplier = 0.1
  209. case dom.DeltaLine:
  210. multiplier = 1
  211. default:
  212. log.Println("unsupported WheelEvent.DeltaMode:", we.DeltaMode)
  213. multiplier = 1
  214. }*/
  215. multiplier = 1
  216. if w.scrollCallback != nil {
  217. go w.scrollCallback(w, -deltaX*multiplier, -deltaY*multiplier)
  218. }
  219. we.Call("preventDefault")
  220. return nil
  221. }))
  222. /*
  223. // Hacky mouse-emulation-via-touch.
  224. touchHandler := func(event dom.Event) {
  225. w.goFullscreenIfRequested()
  226. te := event.(*dom.TouchEvent)
  227. touches := te.Get("touches")
  228. if touches.Length() > 0 {
  229. t := touches.Index(0)
  230. if w.touches != nil && w.touches.Length() > 0 { // This event is a movement only if we previously had > 0 touch points.
  231. if w.mouseMovementCallback != nil {
  232. 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])
  233. }
  234. }
  235. w.cursorPos[0], w.cursorPos[1] = t.Get("clientX").Float(), t.Get("clientY").Float()
  236. if w.cursorPosCallback != nil {
  237. go w.cursorPosCallback(w, w.cursorPos[0], w.cursorPos[1])
  238. }
  239. }
  240. w.touches = touches
  241. te.PreventDefault()
  242. }
  243. document.AddEventListener("touchstart", false, touchHandler)
  244. document.AddEventListener("touchmove", false, touchHandler)
  245. document.AddEventListener("touchend", false, touchHandler)*/
  246. // Request first animation frame.
  247. js.Global().Call("requestAnimationFrame", animationFrameCallback)
  248. return w, nil
  249. }
  250. func (w *Window) SetAttrib(attrib Hint, value int) {
  251. // TODO: Implement.
  252. }
  253. func SwapInterval(interval int) error {
  254. // TODO: Implement.
  255. return nil
  256. }
  257. type Window struct {
  258. canvas js.Value
  259. context js.Value
  260. requestFullscreen bool // requestFullscreen is set to true when fullscreen should be entered as soon as possible (in a user input handler).
  261. fullscreen bool // fullscreen is true if we're currently in fullscreen mode.
  262. // Unavailable browser APIs.
  263. missing struct {
  264. pointerLock bool // Pointer Lock API.
  265. fullscreen bool // Fullscreen API.
  266. }
  267. devicePixelRatio float64
  268. cursorMode int
  269. cursorPos [2]float64
  270. mouseButton [3]Action
  271. keys []Action
  272. cursorPosCallback CursorPosCallback
  273. mouseMovementCallback MouseMovementCallback
  274. mouseButtonCallback MouseButtonCallback
  275. keyCallback KeyCallback
  276. scrollCallback ScrollCallback
  277. charCallback CharCallback
  278. framebufferSizeCallback FramebufferSizeCallback
  279. sizeCallback SizeCallback
  280. touches js.Value // Hacky mouse-emulation-via-touch.
  281. }
  282. func (w *Window) SetPos(xpos, ypos int) {
  283. fmt.Println("not implemented: SetPos:", xpos, ypos)
  284. }
  285. func (w *Window) SetSize(width, height int) {
  286. fmt.Println("not implemented: SetSize:", width, height)
  287. }
  288. func (w *Window) SetIcon(images interface{}) {
  289. // images is actually of type []image.Image, but no need to import image until we actually do something with it
  290. fmt.Println("not implemented: SetIcon")
  291. }
  292. // goFullscreenIfRequested performs webkitRequestFullscreen if it was scheduled. It is called only from
  293. // user events, because that API will fail if called at any other time.
  294. func (w *Window) goFullscreenIfRequested() {
  295. if !w.requestFullscreen {
  296. return
  297. }
  298. w.requestFullscreen = false
  299. w.canvas.Call("webkitRequestFullscreen")
  300. w.fullscreen = true
  301. }
  302. type Monitor struct{}
  303. func (m *Monitor) GetVideoMode() *VidMode {
  304. return &VidMode{
  305. // HACK: Hardcoded sample values.
  306. // TODO: Try to get real values from browser via some API, if possible.
  307. Width: 1680,
  308. Height: 1050,
  309. RedBits: 8,
  310. GreenBits: 8,
  311. BlueBits: 8,
  312. RefreshRate: 60,
  313. }
  314. }
  315. func GetPrimaryMonitor() *Monitor {
  316. // TODO: Implement real functionality.
  317. return &Monitor{}
  318. }
  319. func (w *Window) SetMonitor(monitor *Monitor, xpos, ypos, width, height, refreshRate int) {
  320. // TODO: Implement real functionality.
  321. }
  322. func PollEvents() error {
  323. return nil
  324. }
  325. func (w *Window) MakeContextCurrent() {
  326. contextWatcher.OnMakeCurrent(w.context)
  327. }
  328. func DetachCurrentContext() {
  329. contextWatcher.OnDetach()
  330. }
  331. func GetCurrentContext() *Window {
  332. panic("not implemented")
  333. }
  334. type CursorPosCallback func(w *Window, xpos float64, ypos float64)
  335. func (w *Window) SetCursorPosCallback(cbfun CursorPosCallback) (previous CursorPosCallback) {
  336. w.cursorPosCallback = cbfun
  337. // TODO: Handle previous.
  338. return nil
  339. }
  340. type MouseMovementCallback func(w *Window, xpos float64, ypos float64, xdelta float64, ydelta float64)
  341. func (w *Window) SetMouseMovementCallback(cbfun MouseMovementCallback) (previous MouseMovementCallback) {
  342. w.mouseMovementCallback = cbfun
  343. // TODO: Handle previous.
  344. return nil
  345. }
  346. type KeyCallback func(w *Window, key Key, scancode int, action Action, mods ModifierKey)
  347. func (w *Window) SetKeyCallback(cbfun KeyCallback) (previous KeyCallback) {
  348. w.keyCallback = cbfun
  349. // TODO: Handle previous.
  350. return nil
  351. }
  352. type CharCallback func(w *Window, char rune)
  353. func (w *Window) SetCharCallback(cbfun CharCallback) (previous CharCallback) {
  354. w.charCallback = cbfun
  355. // TODO: Handle previous.
  356. return nil
  357. }
  358. type ScrollCallback func(w *Window, xoff float64, yoff float64)
  359. func (w *Window) SetScrollCallback(cbfun ScrollCallback) (previous ScrollCallback) {
  360. w.scrollCallback = cbfun
  361. // TODO: Handle previous.
  362. return nil
  363. }
  364. type MouseButtonCallback func(w *Window, button MouseButton, action Action, mods ModifierKey)
  365. func (w *Window) SetMouseButtonCallback(cbfun MouseButtonCallback) (previous MouseButtonCallback) {
  366. w.mouseButtonCallback = cbfun
  367. // TODO: Handle previous.
  368. return nil
  369. }
  370. type FramebufferSizeCallback func(w *Window, width int, height int)
  371. func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) {
  372. w.framebufferSizeCallback = cbfun
  373. // TODO: Handle previous.
  374. return nil
  375. }
  376. // Nearest non-negative int.
  377. func (w *Window) scaleRound(f float64) int {
  378. return int(f*w.devicePixelRatio + 0.5)
  379. }
  380. func (w *Window) GetSize() (width, height int) {
  381. return w.scaleRound(w.canvas.Get("clientWidth").Float()), w.scaleRound(w.canvas.Get("clientHeight").Float())
  382. }
  383. func (w *Window) GetFramebufferSize() (width, height int) {
  384. return w.canvas.Get("width").Int(), w.canvas.Get("height").Int()
  385. }
  386. func (w *Window) GetPos() (x, y int) {
  387. // Not implemented.
  388. return
  389. }
  390. func (w *Window) ShouldClose() bool {
  391. return false
  392. }
  393. func (w *Window) SetShouldClose(value bool) {
  394. // TODO: Implement.
  395. // THINK: What should happen in the browser if we're told to "close" the window. Do we destroy/remove the canvas? Or nothing?
  396. // Perhaps https://developer.mozilla.org/en-US/docs/Web/API/Window.close is relevant.
  397. }
  398. func (w *Window) SwapBuffers() error {
  399. <-animationFrameChan
  400. js.Global().Call("requestAnimationFrame", animationFrameCallback)
  401. return nil
  402. }
  403. var animationFrameChan = make(chan struct{}, 1)
  404. var animationFrameCallback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  405. animationFrameChan <- struct{}{}
  406. return nil
  407. })
  408. func (w *Window) GetCursorPos() (x, y float64) {
  409. return w.cursorPos[0], w.cursorPos[1]
  410. }
  411. var keyWarnings = 10
  412. func (w *Window) GetKey(key Key) Action {
  413. if key == -1 && keyWarnings > 0 {
  414. // TODO: Implement all keys, get rid of this.
  415. keyWarnings--
  416. log.Println("GetKey: key not implemented.")
  417. return Release
  418. }
  419. if int(key) >= len(w.keys) {
  420. return Release
  421. }
  422. return w.keys[key]
  423. }
  424. func (w *Window) GetMouseButton(button MouseButton) Action {
  425. if !(button >= 0 && button <= 2) {
  426. panic(fmt.Errorf("button is out of range: %v", button))
  427. }
  428. // Hacky mouse-emulation-via-touch.
  429. if !w.touches.Equal(js.Value{}) {
  430. switch button {
  431. case MouseButton1:
  432. if w.touches.Length() == 1 || w.touches.Length() == 3 {
  433. return Press
  434. }
  435. case MouseButton2:
  436. if w.touches.Length() == 2 || w.touches.Length() == 3 {
  437. return Press
  438. }
  439. }
  440. return Release
  441. }
  442. return w.mouseButton[button]
  443. }
  444. func (w *Window) GetInputMode(mode InputMode) int {
  445. switch mode {
  446. case CursorMode:
  447. return w.cursorMode
  448. default:
  449. panic(errors.New("not implemented"))
  450. }
  451. }
  452. var ErrInvalidParameter = errors.New("invalid parameter")
  453. var ErrInvalidValue = errors.New("invalid value")
  454. func (w *Window) SetInputMode(mode InputMode, value int) {
  455. switch mode {
  456. case CursorMode:
  457. // TODO; Make cursor API compatible with GLFW and Fyne use/expectation.
  458. /*
  459. // Temporarily disable cursor change
  460. if w.missing.pointerLock {
  461. log.Println("warning: Pointer Lock API unsupported")
  462. return
  463. }
  464. switch value {
  465. case CursorNormal:
  466. w.cursorMode = value
  467. document.Call("exitPointerLock")
  468. w.canvas.Get("style").Call("setProperty", "cursor", "initial")
  469. return
  470. case CursorHidden:
  471. w.cursorMode = value
  472. document.Call("exitPointerLock")
  473. w.canvas.Get("style").Call("setProperty", "cursor", "none")
  474. return
  475. case CursorDisabled:
  476. w.cursorMode = value
  477. w.canvas.Call("requestPointerLock")
  478. return
  479. default:
  480. panic(ErrInvalidValue)
  481. }
  482. */
  483. return
  484. case StickyKeysMode:
  485. panic(errors.New("not implemented"))
  486. case StickyMouseButtonsMode:
  487. panic(errors.New("not implemented"))
  488. default:
  489. panic(ErrInvalidParameter)
  490. }
  491. }
  492. type Key int
  493. // TODO: Keys defined as -iota-2 need to be set to a valid positive value that matches the keyCode
  494. // generated by browsers. -iota-2 is used as a temporary solution to have unique but invalid values.
  495. // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode.
  496. const (
  497. KeyUnknown Key = -1
  498. KeySpace Key = 32
  499. KeyApostrophe Key = 222
  500. KeyComma Key = 188
  501. KeyMinus Key = 189
  502. KeyPeriod Key = 190
  503. KeySlash Key = 191
  504. Key0 Key = 48
  505. Key1 Key = 49
  506. Key2 Key = 50
  507. Key3 Key = 51
  508. Key4 Key = 52
  509. Key5 Key = 53
  510. Key6 Key = 54
  511. Key7 Key = 55
  512. Key8 Key = 56
  513. Key9 Key = 57
  514. KeySemicolon Key = 186
  515. KeyEqual Key = 187
  516. KeyA Key = 65
  517. KeyB Key = 66
  518. KeyC Key = 67
  519. KeyD Key = 68
  520. KeyE Key = 69
  521. KeyF Key = 70
  522. KeyG Key = 71
  523. KeyH Key = 72
  524. KeyI Key = 73
  525. KeyJ Key = 74
  526. KeyK Key = 75
  527. KeyL Key = 76
  528. KeyM Key = 77
  529. KeyN Key = 78
  530. KeyO Key = 79
  531. KeyP Key = 80
  532. KeyQ Key = 81
  533. KeyR Key = 82
  534. KeyS Key = 83
  535. KeyT Key = 84
  536. KeyU Key = 85
  537. KeyV Key = 86
  538. KeyW Key = 87
  539. KeyX Key = 88
  540. KeyY Key = 89
  541. KeyZ Key = 90
  542. KeyLeftBracket Key = 219
  543. KeyBackslash Key = 220
  544. KeyRightBracket Key = 221
  545. KeyGraveAccent Key = 192
  546. KeyWorld1 Key = -iota - 2
  547. KeyWorld2 Key = -iota - 2
  548. KeyEscape Key = 27
  549. KeyEnter Key = 13
  550. KeyTab Key = 9
  551. KeyBackspace Key = 8
  552. KeyInsert Key = -iota - 2
  553. KeyDelete Key = 46
  554. KeyRight Key = 39
  555. KeyLeft Key = 37
  556. KeyDown Key = 40
  557. KeyUp Key = 38
  558. KeyPageUp Key = -iota - 2
  559. KeyPageDown Key = -iota - 2
  560. KeyHome Key = -iota - 2
  561. KeyEnd Key = -iota - 2
  562. KeyCapsLock Key = 20
  563. KeyScrollLock Key = -iota - 2
  564. KeyNumLock Key = -iota - 2
  565. KeyPrintScreen Key = -iota - 2
  566. KeyPause Key = -iota - 2
  567. KeyF1 Key = 112
  568. KeyF2 Key = 113
  569. KeyF3 Key = 114
  570. KeyF4 Key = 115
  571. KeyF5 Key = 116
  572. KeyF6 Key = 117
  573. KeyF7 Key = 118
  574. KeyF8 Key = 119
  575. KeyF9 Key = 120
  576. KeyF10 Key = 121
  577. KeyF11 Key = 122
  578. KeyF12 Key = 123
  579. KeyF13 Key = -iota - 2
  580. KeyF14 Key = -iota - 2
  581. KeyF15 Key = -iota - 2
  582. KeyF16 Key = -iota - 2
  583. KeyF17 Key = -iota - 2
  584. KeyF18 Key = -iota - 2
  585. KeyF19 Key = -iota - 2
  586. KeyF20 Key = -iota - 2
  587. KeyF21 Key = -iota - 2
  588. KeyF22 Key = -iota - 2
  589. KeyF23 Key = -iota - 2
  590. KeyF24 Key = -iota - 2
  591. KeyF25 Key = -iota - 2
  592. KeyKP0 Key = -iota - 2
  593. KeyKP1 Key = -iota - 2
  594. KeyKP2 Key = -iota - 2
  595. KeyKP3 Key = -iota - 2
  596. KeyKP4 Key = -iota - 2
  597. KeyKP5 Key = -iota - 2
  598. KeyKP6 Key = -iota - 2
  599. KeyKP7 Key = -iota - 2
  600. KeyKP8 Key = -iota - 2
  601. KeyKP9 Key = -iota - 2
  602. KeyKPDecimal Key = -iota - 2
  603. KeyKPDivide Key = -iota - 2
  604. KeyKPMultiply Key = -iota - 2
  605. KeyKPSubtract Key = -iota - 2
  606. KeyKPAdd Key = -iota - 2
  607. KeyKPEnter Key = -iota - 2
  608. KeyKPEqual Key = -iota - 2
  609. KeyLeftShift Key = 340
  610. KeyLeftControl Key = 341
  611. KeyLeftAlt Key = 342
  612. KeyLeftSuper Key = 91
  613. KeyRightShift Key = 344
  614. KeyRightControl Key = 345
  615. KeyRightAlt Key = 346
  616. KeyRightSuper Key = 93
  617. KeyMenu Key = -iota - 2
  618. )
  619. // toKey extracts Key from given KeyboardEvent.
  620. func toKey(ke js.Value) Key {
  621. // TODO: Factor out into DOM package.
  622. const (
  623. KeyLocationLeft = 1
  624. KeyLocationRight = 2
  625. )
  626. key := Key(ke.Get("keyCode").Int())
  627. switch {
  628. case key == 16 && ke.Get("location").Int() == KeyLocationLeft:
  629. key = KeyLeftShift
  630. case key == 16 && ke.Get("location").Int() == KeyLocationRight:
  631. key = KeyRightShift
  632. case key == 17 && ke.Get("location").Int() == KeyLocationLeft:
  633. key = KeyLeftControl
  634. case key == 17 && ke.Get("location").Int() == KeyLocationRight:
  635. key = KeyRightControl
  636. case key == 18 && ke.Get("location").Int() == KeyLocationLeft:
  637. key = KeyLeftAlt
  638. case key == 18 && ke.Get("location").Int() == KeyLocationRight:
  639. key = KeyRightAlt
  640. }
  641. return key
  642. }
  643. // toModifierKey extracts ModifierKey from given KeyboardEvent.
  644. func toModifierKey(ke js.Value) ModifierKey {
  645. mods := ModifierKey(0)
  646. if ke.Get("shiftKey").Bool() {
  647. mods += ModShift
  648. }
  649. if ke.Get("ctrlKey").Bool() {
  650. mods += ModControl
  651. }
  652. if ke.Get("altKey").Bool() {
  653. mods += ModAlt
  654. }
  655. if ke.Get("metaKey").Bool() {
  656. mods += ModSuper
  657. }
  658. return mods
  659. }
  660. type MouseButton int
  661. const (
  662. MouseButton1 MouseButton = 0
  663. MouseButton2 MouseButton = 2 // Web MouseEvent has middle and right mouse buttons in reverse order.
  664. MouseButton3 MouseButton = 1 // Web MouseEvent has middle and right mouse buttons in reverse order.
  665. MouseButtonLeft = MouseButton1
  666. MouseButtonRight = MouseButton2
  667. MouseButtonMiddle = MouseButton3
  668. )
  669. type Joystick int
  670. const (
  671. Joystick1 Joystick = iota
  672. Joystick2
  673. Joystick3
  674. Joystick4
  675. Joystick5
  676. Joystick6
  677. Joystick7
  678. Joystick8
  679. Joystick9
  680. Joystick10
  681. Joystick11
  682. Joystick12
  683. Joystick13
  684. Joystick14
  685. Joystick15
  686. Joystick16
  687. JoystickLast = Joystick16
  688. )
  689. type GamepadAxis int
  690. const (
  691. AxisLeftX GamepadAxis = iota
  692. AxisLeftY
  693. AxisRightX
  694. AxisRightY
  695. AxisLeftTrigger
  696. AxisRightTrigger
  697. AxisLast = AxisRightTrigger
  698. )
  699. type GamepadButton int
  700. const (
  701. ButtonA GamepadButton = iota
  702. ButtonB
  703. ButtonX
  704. ButtonY
  705. ButtonLeftBumper
  706. ButtonRightBumper
  707. ButtonBack
  708. ButtonStart
  709. ButtonGuide
  710. ButtonLeftThumb
  711. ButtonRightThumb
  712. ButtonDpadUp
  713. ButtonDpadRight
  714. ButtonDpadDown
  715. ButtonDpadLeft
  716. ButtonLast = ButtonDpadLeft
  717. ButtonCross = ButtonA
  718. ButtonCircle = ButtonB
  719. ButtonSquare = ButtonX
  720. ButtonTriangle = ButtonY
  721. )
  722. type Action int
  723. const (
  724. Release Action = 0
  725. Press Action = 1
  726. Repeat Action = 2
  727. )
  728. type InputMode int
  729. const (
  730. CursorMode InputMode = iota
  731. StickyKeysMode
  732. StickyMouseButtonsMode
  733. LockKeyMods
  734. RawMouseMotion
  735. )
  736. const (
  737. CursorNormal = iota
  738. CursorHidden
  739. CursorDisabled
  740. )
  741. type ModifierKey int
  742. const (
  743. ModShift ModifierKey = (1 << iota)
  744. ModControl
  745. ModAlt
  746. ModSuper
  747. )
  748. func (joy Joystick) IsPresent() bool {
  749. // TODO: Implement.
  750. return false
  751. }
  752. func (joy Joystick) GetGamepadName() string {
  753. // TODO: Implement.
  754. return "Gamepad"
  755. }
  756. func (joy Joystick) GetButtons() []Action {
  757. // TODO: Implement.
  758. return make([]Action, 0)
  759. }
  760. func (joy Joystick) GetAxes() []float32 {
  761. // TODO: Implement.
  762. return make([]float32, 0)
  763. }
  764. // Open opens a named asset. It's the caller's responsibility to close it when done.
  765. func Open(name string) (io.ReadCloser, error) {
  766. resp, err := http.Get(name)
  767. if err != nil {
  768. return nil, err
  769. }
  770. if resp.StatusCode != 200 {
  771. return nil, fmt.Errorf("non-200 status: %s", resp.Status)
  772. }
  773. return resp.Body, nil
  774. }
  775. // ---
  776. func WaitEvents() {
  777. // TODO.
  778. runtime.Gosched()
  779. }
  780. func PostEmptyEvent() {
  781. // TODO: Implement.
  782. }
  783. func DefaultWindowHints() {
  784. // TODO: Implement.
  785. }
  786. func (w *Window) SetClipboardString(str string) {
  787. // TODO: Implement.
  788. }
  789. func (w *Window) GetClipboardString() (string, error) {
  790. // TODO: Implement.
  791. return "", errors.New("GetClipboardString not implemented")
  792. }
  793. func (w *Window) SetTitle(title string) {
  794. document.Set("title", title)
  795. }
  796. func (w *Window) Show() {
  797. // TODO: Implement.
  798. }
  799. func (w *Window) Hide() {
  800. // TODO: Implement.
  801. }
  802. func (w *Window) Destroy() {
  803. document.Get("body").Call("removeChild", w.canvas)
  804. if w.fullscreen {
  805. if w.missing.fullscreen {
  806. log.Println("warning: Fullscreen API unsupported")
  807. } else {
  808. document.Call("webkitExitFullscreen")
  809. w.fullscreen = false
  810. }
  811. }
  812. }
  813. type CloseCallback func(w *Window)
  814. func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback) {
  815. // TODO: Implement.
  816. // TODO: Handle previous.
  817. return nil
  818. }
  819. type RefreshCallback func(w *Window)
  820. func (w *Window) SetRefreshCallback(cbfun RefreshCallback) (previous RefreshCallback) {
  821. // TODO: Implement.
  822. // TODO: Handle previous.
  823. return nil
  824. }
  825. type SizeCallback func(w *Window, width int, height int)
  826. func (w *Window) SetSizeCallback(cbfun SizeCallback) (previous SizeCallback) {
  827. w.sizeCallback = cbfun
  828. // TODO: Handle previous.
  829. return nil
  830. }
  831. type CursorEnterCallback func(w *Window, entered bool)
  832. func (w *Window) SetCursorEnterCallback(cbfun CursorEnterCallback) (previous CursorEnterCallback) {
  833. // TODO: Implement.
  834. // TODO: Handle previous.
  835. return nil
  836. }
  837. type CharModsCallback func(w *Window, char rune, mods ModifierKey)
  838. func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) {
  839. // TODO: Implement.
  840. // TODO: Handle previous.
  841. return nil
  842. }
  843. type PosCallback func(w *Window, xpos int, ypos int)
  844. func (w *Window) SetPosCallback(cbfun PosCallback) (previous PosCallback) {
  845. // TODO: Implement.
  846. // TODO: Handle previous.
  847. return nil
  848. }
  849. type FocusCallback func(w *Window, focused bool)
  850. func (w *Window) SetFocusCallback(cbfun FocusCallback) (previous FocusCallback) {
  851. // TODO: Implement.
  852. // TODO: Handle previous.
  853. return nil
  854. }
  855. type IconifyCallback func(w *Window, iconified bool)
  856. func (w *Window) SetIconifyCallback(cbfun IconifyCallback) (previous IconifyCallback) {
  857. // TODO: Implement.
  858. // TODO: Handle previous.
  859. return nil
  860. }
  861. type DropCallback func(w *Window, names []string)
  862. func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback) {
  863. // TODO: Implement. Can use HTML5 file drag and drop API?
  864. // TODO: Handle previous.
  865. return nil
  866. }