os_js.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package app
  3. import (
  4. "fmt"
  5. "image"
  6. "image/color"
  7. "io"
  8. "strings"
  9. "syscall/js"
  10. "time"
  11. "unicode"
  12. "unicode/utf8"
  13. "gioui.org/internal/f32color"
  14. "gioui.org/op"
  15. "gioui.org/f32"
  16. "gioui.org/io/event"
  17. "gioui.org/io/key"
  18. "gioui.org/io/pointer"
  19. "gioui.org/io/system"
  20. "gioui.org/io/transfer"
  21. "gioui.org/unit"
  22. )
  23. type JSViewEvent struct {
  24. Element js.Value
  25. }
  26. type contextStatus int
  27. const (
  28. contextStatusOkay contextStatus = iota
  29. contextStatusLost
  30. contextStatusRestored
  31. )
  32. type window struct {
  33. window js.Value
  34. document js.Value
  35. head js.Value
  36. clipboard js.Value
  37. cnv js.Value
  38. tarea js.Value
  39. w *callbacks
  40. redraw js.Func
  41. clipboardCallback js.Func
  42. requestAnimationFrame js.Value
  43. browserHistory js.Value
  44. visualViewport js.Value
  45. screenOrientation js.Value
  46. cleanfuncs []func()
  47. touches []js.Value
  48. composing bool
  49. requestFocus bool
  50. config Config
  51. inset f32.Point
  52. scale float32
  53. animating bool
  54. // animRequested tracks whether a requestAnimationFrame callback
  55. // is pending.
  56. animRequested bool
  57. wakeups chan struct{}
  58. contextStatus contextStatus
  59. }
  60. func newWindow(win *callbacks, options []Option) {
  61. doc := js.Global().Get("document")
  62. cont := getContainer(doc)
  63. cnv := createCanvas(doc)
  64. cont.Call("appendChild", cnv)
  65. tarea := createTextArea(doc)
  66. cont.Call("appendChild", tarea)
  67. w := &window{
  68. cnv: cnv,
  69. document: doc,
  70. tarea: tarea,
  71. window: js.Global().Get("window"),
  72. head: doc.Get("head"),
  73. clipboard: js.Global().Get("navigator").Get("clipboard"),
  74. wakeups: make(chan struct{}, 1),
  75. w: win,
  76. }
  77. w.w.SetDriver(w)
  78. w.requestAnimationFrame = w.window.Get("requestAnimationFrame")
  79. w.browserHistory = w.window.Get("history")
  80. w.visualViewport = w.window.Get("visualViewport")
  81. if w.visualViewport.IsUndefined() {
  82. w.visualViewport = w.window
  83. }
  84. if screen := w.window.Get("screen"); screen.Truthy() {
  85. w.screenOrientation = screen.Get("orientation")
  86. }
  87. w.redraw = w.funcOf(func(this js.Value, args []js.Value) interface{} {
  88. w.draw(false)
  89. return nil
  90. })
  91. w.clipboardCallback = w.funcOf(func(this js.Value, args []js.Value) interface{} {
  92. content := args[0].String()
  93. w.processEvent(transfer.DataEvent{
  94. Type: "application/text",
  95. Open: func() io.ReadCloser {
  96. return io.NopCloser(strings.NewReader(content))
  97. },
  98. })
  99. return nil
  100. })
  101. w.addEventListeners()
  102. w.addHistory()
  103. w.Configure(options)
  104. w.blur()
  105. w.processEvent(JSViewEvent{Element: cont})
  106. w.resize()
  107. w.draw(true)
  108. }
  109. func getContainer(doc js.Value) js.Value {
  110. cont := doc.Call("getElementById", "giowindow")
  111. if !cont.IsNull() {
  112. return cont
  113. }
  114. cont = doc.Call("createElement", "DIV")
  115. doc.Get("body").Call("appendChild", cont)
  116. return cont
  117. }
  118. func createTextArea(doc js.Value) js.Value {
  119. tarea := doc.Call("createElement", "input")
  120. style := tarea.Get("style")
  121. style.Set("width", "1px")
  122. style.Set("height", "1px")
  123. style.Set("opacity", "0")
  124. style.Set("border", "0")
  125. style.Set("padding", "0")
  126. tarea.Set("autocomplete", "off")
  127. tarea.Set("autocorrect", "off")
  128. tarea.Set("autocapitalize", "off")
  129. tarea.Set("spellcheck", false)
  130. return tarea
  131. }
  132. func createCanvas(doc js.Value) js.Value {
  133. cnv := doc.Call("createElement", "canvas")
  134. style := cnv.Get("style")
  135. style.Set("position", "fixed")
  136. style.Set("width", "100%")
  137. style.Set("height", "100%")
  138. return cnv
  139. }
  140. func (w *window) cleanup() {
  141. // Cleanup in the opposite order of
  142. // construction.
  143. for i := len(w.cleanfuncs) - 1; i >= 0; i-- {
  144. w.cleanfuncs[i]()
  145. }
  146. w.cleanfuncs = nil
  147. }
  148. func (w *window) addEventListeners() {
  149. w.addEventListener(w.cnv, "webglcontextlost", func(this js.Value, args []js.Value) interface{} {
  150. args[0].Call("preventDefault")
  151. w.contextStatus = contextStatusLost
  152. return nil
  153. })
  154. w.addEventListener(w.cnv, "webglcontextrestored", func(this js.Value, args []js.Value) interface{} {
  155. args[0].Call("preventDefault")
  156. w.contextStatus = contextStatusRestored
  157. // Resize is required to force update the canvas content when restored.
  158. w.cnv.Set("width", 0)
  159. w.cnv.Set("height", 0)
  160. w.resize()
  161. w.draw(true)
  162. return nil
  163. })
  164. w.addEventListener(w.visualViewport, "resize", func(this js.Value, args []js.Value) interface{} {
  165. w.resize()
  166. w.draw(true)
  167. return nil
  168. })
  169. w.addEventListener(w.window, "contextmenu", func(this js.Value, args []js.Value) interface{} {
  170. args[0].Call("preventDefault")
  171. return nil
  172. })
  173. w.addEventListener(w.window, "popstate", func(this js.Value, args []js.Value) interface{} {
  174. if w.processEvent(key.Event{Name: key.NameBack}) {
  175. return w.browserHistory.Call("forward")
  176. }
  177. return w.browserHistory.Call("back")
  178. })
  179. w.addEventListener(w.cnv, "mousemove", func(this js.Value, args []js.Value) interface{} {
  180. w.pointerEvent(pointer.Move, 0, 0, args[0])
  181. return nil
  182. })
  183. w.addEventListener(w.cnv, "mousedown", func(this js.Value, args []js.Value) interface{} {
  184. w.pointerEvent(pointer.Press, 0, 0, args[0])
  185. if w.requestFocus {
  186. w.focus()
  187. w.requestFocus = false
  188. }
  189. return nil
  190. })
  191. w.addEventListener(w.cnv, "mouseup", func(this js.Value, args []js.Value) interface{} {
  192. w.pointerEvent(pointer.Release, 0, 0, args[0])
  193. return nil
  194. })
  195. w.addEventListener(w.cnv, "wheel", func(this js.Value, args []js.Value) interface{} {
  196. e := args[0]
  197. dx, dy := e.Get("deltaX").Float(), e.Get("deltaY").Float()
  198. // horizontal scroll if shift is pressed.
  199. if e.Get("shiftKey").Bool() {
  200. dx, dy = dy, dx
  201. }
  202. mode := e.Get("deltaMode").Int()
  203. switch mode {
  204. case 0x01: // DOM_DELTA_LINE
  205. dx *= 10
  206. dy *= 10
  207. case 0x02: // DOM_DELTA_PAGE
  208. dx *= 120
  209. dy *= 120
  210. }
  211. w.pointerEvent(pointer.Scroll, float32(dx), float32(dy), e)
  212. return nil
  213. })
  214. w.addEventListener(w.cnv, "touchstart", func(this js.Value, args []js.Value) interface{} {
  215. w.touchEvent(pointer.Press, args[0])
  216. if w.requestFocus {
  217. w.focus() // iOS can only focus inside a Touch event.
  218. w.requestFocus = false
  219. }
  220. return nil
  221. })
  222. w.addEventListener(w.cnv, "touchend", func(this js.Value, args []js.Value) interface{} {
  223. w.touchEvent(pointer.Release, args[0])
  224. return nil
  225. })
  226. w.addEventListener(w.cnv, "touchmove", func(this js.Value, args []js.Value) interface{} {
  227. w.touchEvent(pointer.Move, args[0])
  228. return nil
  229. })
  230. w.addEventListener(w.cnv, "touchcancel", func(this js.Value, args []js.Value) interface{} {
  231. // Cancel all touches even if only one touch was cancelled.
  232. for i := range w.touches {
  233. w.touches[i] = js.Null()
  234. }
  235. w.touches = w.touches[:0]
  236. w.processEvent(pointer.Event{
  237. Kind: pointer.Cancel,
  238. Source: pointer.Touch,
  239. })
  240. return nil
  241. })
  242. w.addEventListener(w.tarea, "focus", func(this js.Value, args []js.Value) interface{} {
  243. w.config.Focused = true
  244. w.processEvent(ConfigEvent{Config: w.config})
  245. return nil
  246. })
  247. w.addEventListener(w.tarea, "blur", func(this js.Value, args []js.Value) interface{} {
  248. w.config.Focused = false
  249. w.processEvent(ConfigEvent{Config: w.config})
  250. w.blur()
  251. return nil
  252. })
  253. w.addEventListener(w.tarea, "keydown", func(this js.Value, args []js.Value) interface{} {
  254. w.keyEvent(args[0], key.Press)
  255. return nil
  256. })
  257. w.addEventListener(w.tarea, "keyup", func(this js.Value, args []js.Value) interface{} {
  258. w.keyEvent(args[0], key.Release)
  259. return nil
  260. })
  261. w.addEventListener(w.tarea, "compositionstart", func(this js.Value, args []js.Value) interface{} {
  262. w.composing = true
  263. return nil
  264. })
  265. w.addEventListener(w.tarea, "compositionend", func(this js.Value, args []js.Value) interface{} {
  266. w.composing = false
  267. w.flushInput()
  268. return nil
  269. })
  270. w.addEventListener(w.tarea, "input", func(this js.Value, args []js.Value) interface{} {
  271. if w.composing {
  272. return nil
  273. }
  274. w.flushInput()
  275. return nil
  276. })
  277. w.addEventListener(w.tarea, "paste", func(this js.Value, args []js.Value) interface{} {
  278. if w.clipboard.IsUndefined() {
  279. return nil
  280. }
  281. // Prevents duplicated-paste, since "paste" is already handled through Clipboard API.
  282. args[0].Call("preventDefault")
  283. return nil
  284. })
  285. }
  286. func (w *window) addHistory() {
  287. w.browserHistory.Call("pushState", nil, nil, w.window.Get("location").Get("href"))
  288. }
  289. func (w *window) flushInput() {
  290. val := w.tarea.Get("value").String()
  291. w.tarea.Set("value", "")
  292. w.w.EditorInsert(string(val))
  293. }
  294. func (w *window) blur() {
  295. w.tarea.Call("blur")
  296. w.requestFocus = false
  297. }
  298. func (w *window) focus() {
  299. w.tarea.Call("focus")
  300. w.requestFocus = true
  301. }
  302. func (w *window) keyboard(hint key.InputHint) {
  303. var m string
  304. switch hint {
  305. case key.HintAny:
  306. m = "text"
  307. case key.HintText:
  308. m = "text"
  309. case key.HintNumeric:
  310. m = "decimal"
  311. case key.HintEmail:
  312. m = "email"
  313. case key.HintURL:
  314. m = "url"
  315. case key.HintTelephone:
  316. m = "tel"
  317. case key.HintPassword:
  318. m = "password"
  319. default:
  320. m = "text"
  321. }
  322. w.tarea.Set("inputMode", m)
  323. }
  324. func (w *window) keyEvent(e js.Value, ks key.State) {
  325. k := e.Get("key").String()
  326. if n, ok := translateKey(k); ok {
  327. cmd := key.Event{
  328. Name: n,
  329. Modifiers: modifiersFor(e),
  330. State: ks,
  331. }
  332. w.processEvent(cmd)
  333. }
  334. }
  335. func (w *window) ProcessEvent(e event.Event) {
  336. w.processEvent(e)
  337. }
  338. func (w *window) processEvent(e event.Event) bool {
  339. if !w.w.ProcessEvent(e) {
  340. return false
  341. }
  342. select {
  343. case w.wakeups <- struct{}{}:
  344. default:
  345. }
  346. return true
  347. }
  348. func (w *window) Event() event.Event {
  349. for {
  350. evt, ok := w.w.nextEvent()
  351. if ok {
  352. if _, destroy := evt.(DestroyEvent); destroy {
  353. w.cleanup()
  354. }
  355. return evt
  356. }
  357. <-w.wakeups
  358. }
  359. }
  360. func (w *window) Invalidate() {
  361. w.w.Invalidate()
  362. }
  363. func (w *window) Run(f func()) {
  364. f()
  365. }
  366. func (w *window) Frame(frame *op.Ops) {
  367. w.w.ProcessFrame(frame, nil)
  368. }
  369. // modifiersFor returns the modifier set for a DOM MouseEvent or
  370. // KeyEvent.
  371. func modifiersFor(e js.Value) key.Modifiers {
  372. var mods key.Modifiers
  373. if e.Get("getModifierState").IsUndefined() {
  374. // Some browsers doesn't support getModifierState.
  375. return mods
  376. }
  377. if e.Call("getModifierState", "Alt").Bool() {
  378. mods |= key.ModAlt
  379. }
  380. if e.Call("getModifierState", "Control").Bool() {
  381. mods |= key.ModCtrl
  382. }
  383. if e.Call("getModifierState", "Shift").Bool() {
  384. mods |= key.ModShift
  385. }
  386. return mods
  387. }
  388. func (w *window) touchEvent(kind pointer.Kind, e js.Value) {
  389. e.Call("preventDefault")
  390. t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
  391. changedTouches := e.Get("changedTouches")
  392. n := changedTouches.Length()
  393. rect := w.cnv.Call("getBoundingClientRect")
  394. scale := w.scale
  395. var mods key.Modifiers
  396. if e.Get("shiftKey").Bool() {
  397. mods |= key.ModShift
  398. }
  399. if e.Get("altKey").Bool() {
  400. mods |= key.ModAlt
  401. }
  402. if e.Get("ctrlKey").Bool() {
  403. mods |= key.ModCtrl
  404. }
  405. for i := 0; i < n; i++ {
  406. touch := changedTouches.Index(i)
  407. pid := w.touchIDFor(touch)
  408. x, y := touch.Get("clientX").Float(), touch.Get("clientY").Float()
  409. x -= rect.Get("left").Float()
  410. y -= rect.Get("top").Float()
  411. pos := f32.Point{
  412. X: float32(x) * scale,
  413. Y: float32(y) * scale,
  414. }
  415. w.processEvent(pointer.Event{
  416. Kind: kind,
  417. Source: pointer.Touch,
  418. Position: pos,
  419. PointerID: pid,
  420. Time: t,
  421. Modifiers: mods,
  422. })
  423. }
  424. }
  425. func (w *window) touchIDFor(touch js.Value) pointer.ID {
  426. id := touch.Get("identifier")
  427. for i, id2 := range w.touches {
  428. if id2.Equal(id) {
  429. return pointer.ID(i)
  430. }
  431. }
  432. pid := pointer.ID(len(w.touches))
  433. w.touches = append(w.touches, id)
  434. return pid
  435. }
  436. func (w *window) pointerEvent(kind pointer.Kind, dx, dy float32, e js.Value) {
  437. e.Call("preventDefault")
  438. x, y := e.Get("clientX").Float(), e.Get("clientY").Float()
  439. rect := w.cnv.Call("getBoundingClientRect")
  440. x -= rect.Get("left").Float()
  441. y -= rect.Get("top").Float()
  442. scale := w.scale
  443. pos := f32.Point{
  444. X: float32(x) * scale,
  445. Y: float32(y) * scale,
  446. }
  447. scroll := f32.Point{
  448. X: dx * scale,
  449. Y: dy * scale,
  450. }
  451. t := time.Duration(e.Get("timeStamp").Int()) * time.Millisecond
  452. jbtns := e.Get("buttons").Int()
  453. var btns pointer.Buttons
  454. if jbtns&1 != 0 {
  455. btns |= pointer.ButtonPrimary
  456. }
  457. if jbtns&2 != 0 {
  458. btns |= pointer.ButtonSecondary
  459. }
  460. if jbtns&4 != 0 {
  461. btns |= pointer.ButtonTertiary
  462. }
  463. w.processEvent(pointer.Event{
  464. Kind: kind,
  465. Source: pointer.Mouse,
  466. Buttons: btns,
  467. Position: pos,
  468. Scroll: scroll,
  469. Time: t,
  470. Modifiers: modifiersFor(e),
  471. })
  472. }
  473. func (w *window) addEventListener(this js.Value, event string, f func(this js.Value, args []js.Value) interface{}) {
  474. jsf := w.funcOf(f)
  475. this.Call("addEventListener", event, jsf)
  476. w.cleanfuncs = append(w.cleanfuncs, func() {
  477. this.Call("removeEventListener", event, jsf)
  478. })
  479. }
  480. // funcOf is like js.FuncOf but adds the js.Func to a list of
  481. // functions to be released during cleanup.
  482. func (w *window) funcOf(f func(this js.Value, args []js.Value) interface{}) js.Func {
  483. jsf := js.FuncOf(f)
  484. w.cleanfuncs = append(w.cleanfuncs, jsf.Release)
  485. return jsf
  486. }
  487. func (w *window) EditorStateChanged(old, new editorState) {}
  488. func (w *window) SetAnimating(anim bool) {
  489. w.animating = anim
  490. if anim && !w.animRequested {
  491. w.animRequested = true
  492. w.requestAnimationFrame.Invoke(w.redraw)
  493. }
  494. }
  495. func (w *window) ReadClipboard() {
  496. if w.clipboard.IsUndefined() {
  497. return
  498. }
  499. if w.clipboard.Get("readText").IsUndefined() {
  500. return
  501. }
  502. w.clipboard.Call("readText", w.clipboard).Call("then", w.clipboardCallback)
  503. }
  504. func (w *window) WriteClipboard(mime string, s []byte) {
  505. if w.clipboard.IsUndefined() {
  506. return
  507. }
  508. if w.clipboard.Get("writeText").IsUndefined() {
  509. return
  510. }
  511. w.clipboard.Call("writeText", string(s))
  512. }
  513. func (w *window) Configure(options []Option) {
  514. prev := w.config
  515. cnf := w.config
  516. cnf.apply(unit.Metric{}, options)
  517. // Decorations are never disabled.
  518. cnf.Decorated = true
  519. if prev.Title != cnf.Title {
  520. w.config.Title = cnf.Title
  521. w.document.Set("title", cnf.Title)
  522. }
  523. if prev.Mode != cnf.Mode {
  524. w.windowMode(cnf.Mode)
  525. }
  526. if prev.NavigationColor != cnf.NavigationColor {
  527. w.config.NavigationColor = cnf.NavigationColor
  528. w.navigationColor(cnf.NavigationColor)
  529. }
  530. if prev.Orientation != cnf.Orientation {
  531. w.config.Orientation = cnf.Orientation
  532. w.orientation(cnf.Orientation)
  533. }
  534. if cnf.Decorated != prev.Decorated {
  535. w.config.Decorated = cnf.Decorated
  536. }
  537. w.processEvent(ConfigEvent{Config: w.config})
  538. }
  539. func (w *window) Perform(system.Action) {}
  540. var webCursor = [...]string{
  541. pointer.CursorDefault: "default",
  542. pointer.CursorNone: "none",
  543. pointer.CursorText: "text",
  544. pointer.CursorVerticalText: "vertical-text",
  545. pointer.CursorPointer: "pointer",
  546. pointer.CursorCrosshair: "crosshair",
  547. pointer.CursorAllScroll: "all-scroll",
  548. pointer.CursorColResize: "col-resize",
  549. pointer.CursorRowResize: "row-resize",
  550. pointer.CursorGrab: "grab",
  551. pointer.CursorGrabbing: "grabbing",
  552. pointer.CursorNotAllowed: "not-allowed",
  553. pointer.CursorWait: "wait",
  554. pointer.CursorProgress: "progress",
  555. pointer.CursorNorthWestResize: "nw-resize",
  556. pointer.CursorNorthEastResize: "ne-resize",
  557. pointer.CursorSouthWestResize: "sw-resize",
  558. pointer.CursorSouthEastResize: "se-resize",
  559. pointer.CursorNorthSouthResize: "ns-resize",
  560. pointer.CursorEastWestResize: "ew-resize",
  561. pointer.CursorWestResize: "w-resize",
  562. pointer.CursorEastResize: "e-resize",
  563. pointer.CursorNorthResize: "n-resize",
  564. pointer.CursorSouthResize: "s-resize",
  565. pointer.CursorNorthEastSouthWestResize: "nesw-resize",
  566. pointer.CursorNorthWestSouthEastResize: "nwse-resize",
  567. }
  568. func (w *window) SetCursor(cursor pointer.Cursor) {
  569. style := w.cnv.Get("style")
  570. style.Set("cursor", webCursor[cursor])
  571. }
  572. func (w *window) ShowTextInput(show bool) {
  573. // Run in a goroutine to avoid a deadlock if the
  574. // focus change result in an event.
  575. if show {
  576. w.focus()
  577. } else {
  578. w.blur()
  579. }
  580. }
  581. func (w *window) SetInputHint(mode key.InputHint) {
  582. w.keyboard(mode)
  583. }
  584. func (w *window) resize() {
  585. w.scale = float32(w.window.Get("devicePixelRatio").Float())
  586. rect := w.cnv.Call("getBoundingClientRect")
  587. size := image.Point{
  588. X: int(float32(rect.Get("width").Float()) * w.scale),
  589. Y: int(float32(rect.Get("height").Float()) * w.scale),
  590. }
  591. if size != w.config.Size {
  592. w.config.Size = size
  593. w.processEvent(ConfigEvent{Config: w.config})
  594. }
  595. if vx, vy := w.visualViewport.Get("width"), w.visualViewport.Get("height"); !vx.IsUndefined() && !vy.IsUndefined() {
  596. w.inset.X = float32(w.config.Size.X) - float32(vx.Float())*w.scale
  597. w.inset.Y = float32(w.config.Size.Y) - float32(vy.Float())*w.scale
  598. }
  599. if w.config.Size.X == 0 || w.config.Size.Y == 0 {
  600. return
  601. }
  602. w.cnv.Set("width", w.config.Size.X)
  603. w.cnv.Set("height", w.config.Size.Y)
  604. }
  605. func (w *window) draw(sync bool) {
  606. if w.contextStatus == contextStatusLost {
  607. return
  608. }
  609. anim := w.animating
  610. w.animRequested = anim
  611. if anim {
  612. w.requestAnimationFrame.Invoke(w.redraw)
  613. } else if !sync {
  614. return
  615. }
  616. size, insets, metric := w.getConfig()
  617. if metric == (unit.Metric{}) || size.X == 0 || size.Y == 0 {
  618. return
  619. }
  620. w.processEvent(frameEvent{
  621. FrameEvent: FrameEvent{
  622. Now: time.Now(),
  623. Size: size,
  624. Insets: insets,
  625. Metric: metric,
  626. },
  627. Sync: sync,
  628. })
  629. }
  630. func (w *window) getConfig() (image.Point, Insets, unit.Metric) {
  631. invscale := unit.Dp(1. / w.scale)
  632. return image.Pt(w.config.Size.X, w.config.Size.Y),
  633. Insets{
  634. Bottom: unit.Dp(w.inset.Y) * invscale,
  635. Right: unit.Dp(w.inset.X) * invscale,
  636. }, unit.Metric{
  637. PxPerDp: w.scale,
  638. PxPerSp: w.scale,
  639. }
  640. }
  641. func (w *window) windowMode(mode WindowMode) {
  642. switch mode {
  643. case Windowed:
  644. if !w.document.Get("fullscreenElement").Truthy() {
  645. return // Browser is already Windowed.
  646. }
  647. if !w.document.Get("exitFullscreen").Truthy() {
  648. return // Browser doesn't support such feature.
  649. }
  650. w.document.Call("exitFullscreen")
  651. w.config.Mode = Windowed
  652. case Fullscreen:
  653. elem := w.document.Get("documentElement")
  654. if !elem.Get("requestFullscreen").Truthy() {
  655. return // Browser doesn't support such feature.
  656. }
  657. elem.Call("requestFullscreen")
  658. w.config.Mode = Fullscreen
  659. }
  660. }
  661. func (w *window) orientation(mode Orientation) {
  662. if j := w.screenOrientation; !j.Truthy() || !j.Get("unlock").Truthy() || !j.Get("lock").Truthy() {
  663. return // Browser don't support Screen Orientation API.
  664. }
  665. switch mode {
  666. case AnyOrientation:
  667. w.screenOrientation.Call("unlock")
  668. case LandscapeOrientation:
  669. w.screenOrientation.Call("lock", "landscape").Call("then", w.redraw)
  670. case PortraitOrientation:
  671. w.screenOrientation.Call("lock", "portrait").Call("then", w.redraw)
  672. }
  673. }
  674. func (w *window) navigationColor(c color.NRGBA) {
  675. theme := w.head.Call("querySelector", `meta[name="theme-color"]`)
  676. if !theme.Truthy() {
  677. theme = w.document.Call("createElement", "meta")
  678. theme.Set("name", "theme-color")
  679. w.head.Call("appendChild", theme)
  680. }
  681. rgba := f32color.NRGBAToRGBA(c)
  682. theme.Set("content", fmt.Sprintf("#%06X", []uint8{rgba.R, rgba.G, rgba.B}))
  683. }
  684. func osMain() {
  685. select {}
  686. }
  687. func translateKey(k string) (key.Name, bool) {
  688. var n key.Name
  689. switch k {
  690. case "ArrowUp":
  691. n = key.NameUpArrow
  692. case "ArrowDown":
  693. n = key.NameDownArrow
  694. case "ArrowLeft":
  695. n = key.NameLeftArrow
  696. case "ArrowRight":
  697. n = key.NameRightArrow
  698. case "Escape":
  699. n = key.NameEscape
  700. case "Enter":
  701. n = key.NameReturn
  702. case "Backspace":
  703. n = key.NameDeleteBackward
  704. case "Delete":
  705. n = key.NameDeleteForward
  706. case "Home":
  707. n = key.NameHome
  708. case "End":
  709. n = key.NameEnd
  710. case "PageUp":
  711. n = key.NamePageUp
  712. case "PageDown":
  713. n = key.NamePageDown
  714. case "Tab":
  715. n = key.NameTab
  716. case " ":
  717. n = key.NameSpace
  718. case "F1":
  719. n = key.NameF1
  720. case "F2":
  721. n = key.NameF2
  722. case "F3":
  723. n = key.NameF3
  724. case "F4":
  725. n = key.NameF4
  726. case "F5":
  727. n = key.NameF5
  728. case "F6":
  729. n = key.NameF6
  730. case "F7":
  731. n = key.NameF7
  732. case "F8":
  733. n = key.NameF8
  734. case "F9":
  735. n = key.NameF9
  736. case "F10":
  737. n = key.NameF10
  738. case "F11":
  739. n = key.NameF11
  740. case "F12":
  741. n = key.NameF12
  742. case "Control":
  743. n = key.NameCtrl
  744. case "Shift":
  745. n = key.NameShift
  746. case "Alt":
  747. n = key.NameAlt
  748. case "OS":
  749. n = key.NameSuper
  750. default:
  751. r, s := utf8.DecodeRuneInString(k)
  752. // If there is exactly one printable character, return that.
  753. if s == len(k) && unicode.IsPrint(r) {
  754. return key.Name(strings.ToUpper(k)), true
  755. }
  756. return "", false
  757. }
  758. return n, true
  759. }
  760. func (JSViewEvent) implementsViewEvent() {}
  761. func (JSViewEvent) ImplementsEvent() {}
  762. func (j JSViewEvent) Valid() bool {
  763. return !(j.Element.IsNull() || j.Element.IsUndefined())
  764. }