browser_wasm.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  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. mods := toModifierKey(ke)
  115. if w.keyCallback != nil {
  116. go w.keyCallback(w, key, -1, action, mods)
  117. }
  118. if w.charCallback != nil && mods < 2 {
  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. //
  495. // generated by browsers. -iota-2 is used as a temporary solution to have unique but invalid values.
  496. // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode.
  497. const (
  498. KeyUnknown Key = -1
  499. KeySpace Key = 32
  500. KeyApostrophe Key = 222
  501. KeyComma Key = 188
  502. KeyMinus Key = 189
  503. KeyPeriod Key = 190
  504. KeySlash Key = 191
  505. Key0 Key = 48
  506. Key1 Key = 49
  507. Key2 Key = 50
  508. Key3 Key = 51
  509. Key4 Key = 52
  510. Key5 Key = 53
  511. Key6 Key = 54
  512. Key7 Key = 55
  513. Key8 Key = 56
  514. Key9 Key = 57
  515. KeySemicolon Key = 186
  516. KeyEqual Key = 187
  517. KeyA Key = 65
  518. KeyB Key = 66
  519. KeyC Key = 67
  520. KeyD Key = 68
  521. KeyE Key = 69
  522. KeyF Key = 70
  523. KeyG Key = 71
  524. KeyH Key = 72
  525. KeyI Key = 73
  526. KeyJ Key = 74
  527. KeyK Key = 75
  528. KeyL Key = 76
  529. KeyM Key = 77
  530. KeyN Key = 78
  531. KeyO Key = 79
  532. KeyP Key = 80
  533. KeyQ Key = 81
  534. KeyR Key = 82
  535. KeyS Key = 83
  536. KeyT Key = 84
  537. KeyU Key = 85
  538. KeyV Key = 86
  539. KeyW Key = 87
  540. KeyX Key = 88
  541. KeyY Key = 89
  542. KeyZ Key = 90
  543. KeyLeftBracket Key = 219
  544. KeyBackslash Key = 220
  545. KeyRightBracket Key = 221
  546. KeyGraveAccent Key = 192
  547. KeyWorld1 Key = -iota - 2
  548. KeyWorld2 Key = -iota - 2
  549. KeyEscape Key = 27
  550. KeyEnter Key = 13
  551. KeyTab Key = 9
  552. KeyBackspace Key = 8
  553. KeyInsert Key = -iota - 2
  554. KeyDelete Key = 46
  555. KeyRight Key = 39
  556. KeyLeft Key = 37
  557. KeyDown Key = 40
  558. KeyUp Key = 38
  559. KeyPageUp Key = -iota - 2
  560. KeyPageDown Key = -iota - 2
  561. KeyHome Key = -iota - 2
  562. KeyEnd Key = -iota - 2
  563. KeyCapsLock Key = 20
  564. KeyScrollLock Key = -iota - 2
  565. KeyNumLock Key = -iota - 2
  566. KeyPrintScreen Key = -iota - 2
  567. KeyPause Key = -iota - 2
  568. KeyF1 Key = 112
  569. KeyF2 Key = 113
  570. KeyF3 Key = 114
  571. KeyF4 Key = 115
  572. KeyF5 Key = 116
  573. KeyF6 Key = 117
  574. KeyF7 Key = 118
  575. KeyF8 Key = 119
  576. KeyF9 Key = 120
  577. KeyF10 Key = 121
  578. KeyF11 Key = 122
  579. KeyF12 Key = 123
  580. KeyF13 Key = -iota - 2
  581. KeyF14 Key = -iota - 2
  582. KeyF15 Key = -iota - 2
  583. KeyF16 Key = -iota - 2
  584. KeyF17 Key = -iota - 2
  585. KeyF18 Key = -iota - 2
  586. KeyF19 Key = -iota - 2
  587. KeyF20 Key = -iota - 2
  588. KeyF21 Key = -iota - 2
  589. KeyF22 Key = -iota - 2
  590. KeyF23 Key = -iota - 2
  591. KeyF24 Key = -iota - 2
  592. KeyF25 Key = -iota - 2
  593. KeyKP0 Key = -iota - 2
  594. KeyKP1 Key = -iota - 2
  595. KeyKP2 Key = -iota - 2
  596. KeyKP3 Key = -iota - 2
  597. KeyKP4 Key = -iota - 2
  598. KeyKP5 Key = -iota - 2
  599. KeyKP6 Key = -iota - 2
  600. KeyKP7 Key = -iota - 2
  601. KeyKP8 Key = -iota - 2
  602. KeyKP9 Key = -iota - 2
  603. KeyKPDecimal Key = -iota - 2
  604. KeyKPDivide Key = -iota - 2
  605. KeyKPMultiply Key = -iota - 2
  606. KeyKPSubtract Key = -iota - 2
  607. KeyKPAdd Key = -iota - 2
  608. KeyKPEnter Key = -iota - 2
  609. KeyKPEqual Key = -iota - 2
  610. KeyLeftShift Key = 340
  611. KeyLeftControl Key = 341
  612. KeyLeftAlt Key = 342
  613. KeyLeftSuper Key = 91
  614. KeyRightShift Key = 344
  615. KeyRightControl Key = 345
  616. KeyRightAlt Key = 346
  617. KeyRightSuper Key = 93
  618. KeyMenu Key = -iota - 2
  619. )
  620. // toKey extracts Key from given KeyboardEvent.
  621. func toKey(ke js.Value) Key {
  622. // TODO: Factor out into DOM package.
  623. const (
  624. KeyLocationLeft = 1
  625. KeyLocationRight = 2
  626. )
  627. key := Key(ke.Get("keyCode").Int())
  628. switch {
  629. case key == 16 && ke.Get("location").Int() == KeyLocationLeft:
  630. key = KeyLeftShift
  631. case key == 16 && ke.Get("location").Int() == KeyLocationRight:
  632. key = KeyRightShift
  633. case key == 17 && ke.Get("location").Int() == KeyLocationLeft:
  634. key = KeyLeftControl
  635. case key == 17 && ke.Get("location").Int() == KeyLocationRight:
  636. key = KeyRightControl
  637. case key == 18 && ke.Get("location").Int() == KeyLocationLeft:
  638. key = KeyLeftAlt
  639. case key == 18 && ke.Get("location").Int() == KeyLocationRight:
  640. key = KeyRightAlt
  641. }
  642. return key
  643. }
  644. // toModifierKey extracts ModifierKey from given KeyboardEvent.
  645. func toModifierKey(ke js.Value) ModifierKey {
  646. mods := ModifierKey(0)
  647. if ke.Get("shiftKey").Bool() {
  648. mods += ModShift
  649. }
  650. if ke.Get("ctrlKey").Bool() {
  651. mods += ModControl
  652. }
  653. if ke.Get("altKey").Bool() {
  654. mods += ModAlt
  655. }
  656. if ke.Get("metaKey").Bool() {
  657. mods += ModSuper
  658. }
  659. return mods
  660. }
  661. type MouseButton int
  662. const (
  663. MouseButton1 MouseButton = 0
  664. MouseButton2 MouseButton = 2 // Web MouseEvent has middle and right mouse buttons in reverse order.
  665. MouseButton3 MouseButton = 1 // Web MouseEvent has middle and right mouse buttons in reverse order.
  666. MouseButtonLeft = MouseButton1
  667. MouseButtonRight = MouseButton2
  668. MouseButtonMiddle = MouseButton3
  669. )
  670. type Joystick int
  671. const (
  672. Joystick1 Joystick = iota
  673. Joystick2
  674. Joystick3
  675. Joystick4
  676. Joystick5
  677. Joystick6
  678. Joystick7
  679. Joystick8
  680. Joystick9
  681. Joystick10
  682. Joystick11
  683. Joystick12
  684. Joystick13
  685. Joystick14
  686. Joystick15
  687. Joystick16
  688. JoystickLast = Joystick16
  689. )
  690. type GamepadAxis int
  691. const (
  692. AxisLeftX GamepadAxis = iota
  693. AxisLeftY
  694. AxisRightX
  695. AxisRightY
  696. AxisLeftTrigger
  697. AxisRightTrigger
  698. AxisLast = AxisRightTrigger
  699. )
  700. type GamepadButton int
  701. const (
  702. ButtonA GamepadButton = iota
  703. ButtonB
  704. ButtonX
  705. ButtonY
  706. ButtonLeftBumper
  707. ButtonRightBumper
  708. ButtonBack
  709. ButtonStart
  710. ButtonGuide
  711. ButtonLeftThumb
  712. ButtonRightThumb
  713. ButtonDpadUp
  714. ButtonDpadRight
  715. ButtonDpadDown
  716. ButtonDpadLeft
  717. ButtonLast = ButtonDpadLeft
  718. ButtonCross = ButtonA
  719. ButtonCircle = ButtonB
  720. ButtonSquare = ButtonX
  721. ButtonTriangle = ButtonY
  722. )
  723. type Action int
  724. const (
  725. Release Action = 0
  726. Press Action = 1
  727. Repeat Action = 2
  728. )
  729. type InputMode int
  730. const (
  731. CursorMode InputMode = iota
  732. StickyKeysMode
  733. StickyMouseButtonsMode
  734. LockKeyMods
  735. RawMouseMotion
  736. )
  737. const (
  738. CursorNormal = iota
  739. CursorHidden
  740. CursorDisabled
  741. )
  742. type ModifierKey int
  743. const (
  744. ModShift ModifierKey = (1 << iota)
  745. ModControl
  746. ModAlt
  747. ModSuper
  748. )
  749. func (joy Joystick) IsPresent() bool {
  750. // TODO: Implement.
  751. return false
  752. }
  753. func (joy Joystick) GetGamepadName() string {
  754. // TODO: Implement.
  755. return "Gamepad"
  756. }
  757. func (joy Joystick) GetButtons() []Action {
  758. // TODO: Implement.
  759. return make([]Action, 0)
  760. }
  761. func (joy Joystick) GetAxes() []float32 {
  762. // TODO: Implement.
  763. return make([]float32, 0)
  764. }
  765. // Open opens a named asset. It's the caller's responsibility to close it when done.
  766. func Open(name string) (io.ReadCloser, error) {
  767. resp, err := http.Get(name)
  768. if err != nil {
  769. return nil, err
  770. }
  771. if resp.StatusCode != 200 {
  772. return nil, fmt.Errorf("non-200 status: %s", resp.Status)
  773. }
  774. return resp.Body, nil
  775. }
  776. // ---
  777. func WaitEvents() {
  778. // TODO.
  779. runtime.Gosched()
  780. }
  781. func PostEmptyEvent() {
  782. // TODO: Implement.
  783. }
  784. func DefaultWindowHints() {
  785. // TODO: Implement.
  786. }
  787. func (w *Window) SetClipboardString(str string) {
  788. // Set the clipboard content from the input str
  789. js.Global().Get("navigator").Get("clipboard").Call("writeText", str)
  790. }
  791. func (w *Window) GetClipboardString() (string, error) {
  792. // Get the clipboard object.
  793. clipboard := js.Global().Get("navigator").Get("clipboard")
  794. clipboardChan := make(chan js.Value)
  795. // Call the `readText()` function and send the result to the channel
  796. clipboard.Call("readText").Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
  797. clipboardContent := p[0]
  798. clipboardChan <- clipboardContent
  799. return nil
  800. })).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
  801. clipboardChan <- js.ValueOf(nil)
  802. return nil
  803. }))
  804. // Get the js.Value of the clipboard text from the channel
  805. result := <-clipboardChan
  806. if result.Truthy() {
  807. // Convert the value to a string and return the value
  808. text := result.String()
  809. return text, nil
  810. }
  811. return "", errors.New("Failed to get clipboard text")
  812. }
  813. func (w *Window) SetTitle(title string) {
  814. document.Set("title", title)
  815. }
  816. func (w *Window) Show() {
  817. // TODO: Implement.
  818. }
  819. func (w *Window) Hide() {
  820. // TODO: Implement.
  821. }
  822. func (w *Window) Destroy() {
  823. document.Get("body").Call("removeChild", w.canvas)
  824. if w.fullscreen {
  825. if w.missing.fullscreen {
  826. log.Println("warning: Fullscreen API unsupported")
  827. } else {
  828. document.Call("webkitExitFullscreen")
  829. w.fullscreen = false
  830. }
  831. }
  832. }
  833. type CloseCallback func(w *Window)
  834. func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback) {
  835. // TODO: Implement.
  836. // TODO: Handle previous.
  837. return nil
  838. }
  839. type RefreshCallback func(w *Window)
  840. func (w *Window) SetRefreshCallback(cbfun RefreshCallback) (previous RefreshCallback) {
  841. // TODO: Implement.
  842. // TODO: Handle previous.
  843. return nil
  844. }
  845. type SizeCallback func(w *Window, width int, height int)
  846. func (w *Window) SetSizeCallback(cbfun SizeCallback) (previous SizeCallback) {
  847. w.sizeCallback = cbfun
  848. // TODO: Handle previous.
  849. return nil
  850. }
  851. type CursorEnterCallback func(w *Window, entered bool)
  852. func (w *Window) SetCursorEnterCallback(cbfun CursorEnterCallback) (previous CursorEnterCallback) {
  853. // TODO: Implement.
  854. // TODO: Handle previous.
  855. return nil
  856. }
  857. type CharModsCallback func(w *Window, char rune, mods ModifierKey)
  858. func (w *Window) SetCharModsCallback(cbfun CharModsCallback) (previous CharModsCallback) {
  859. // TODO: Implement.
  860. // TODO: Handle previous.
  861. return nil
  862. }
  863. type PosCallback func(w *Window, xpos int, ypos int)
  864. func (w *Window) SetPosCallback(cbfun PosCallback) (previous PosCallback) {
  865. // TODO: Implement.
  866. // TODO: Handle previous.
  867. return nil
  868. }
  869. type FocusCallback func(w *Window, focused bool)
  870. func (w *Window) SetFocusCallback(cbfun FocusCallback) (previous FocusCallback) {
  871. // TODO: Implement.
  872. // TODO: Handle previous.
  873. return nil
  874. }
  875. type IconifyCallback func(w *Window, iconified bool)
  876. func (w *Window) SetIconifyCallback(cbfun IconifyCallback) (previous IconifyCallback) {
  877. // TODO: Implement.
  878. // TODO: Handle previous.
  879. return nil
  880. }
  881. type DropCallback func(w *Window, names []string)
  882. func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback) {
  883. // TODO: Implement. Can use HTML5 file drag and drop API?
  884. // TODO: Handle previous.
  885. return nil
  886. }