window.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package app
  3. import (
  4. "errors"
  5. "fmt"
  6. "image"
  7. "image/color"
  8. "runtime"
  9. "sync"
  10. "time"
  11. "unicode/utf8"
  12. "gioui.org/f32"
  13. "gioui.org/font/gofont"
  14. "gioui.org/gpu"
  15. "gioui.org/internal/debug"
  16. "gioui.org/internal/ops"
  17. "gioui.org/io/event"
  18. "gioui.org/io/input"
  19. "gioui.org/io/key"
  20. "gioui.org/io/pointer"
  21. "gioui.org/io/system"
  22. "gioui.org/layout"
  23. "gioui.org/op"
  24. "gioui.org/text"
  25. "gioui.org/unit"
  26. "gioui.org/widget"
  27. "gioui.org/widget/material"
  28. )
  29. // Option configures a window.
  30. type Option func(unit.Metric, *Config)
  31. // Window represents an operating system window.
  32. //
  33. // The zero-value Window is useful; the GUI window is created and shown the first
  34. // time the [Event] method is called. On iOS or Android, the first Window represents
  35. // the window previously created by the platform.
  36. //
  37. // More than one Window is not supported on iOS, Android, WebAssembly.
  38. type Window struct {
  39. initialOpts []Option
  40. initialActions []system.Action
  41. ctx context
  42. gpu gpu.GPU
  43. // timer tracks the delayed invalidate goroutine.
  44. timer struct {
  45. // quit is shuts down the goroutine.
  46. quit chan struct{}
  47. // update the invalidate time.
  48. update chan time.Time
  49. }
  50. animating bool
  51. hasNextFrame bool
  52. nextFrame time.Time
  53. // viewport is the latest frame size with insets applied.
  54. viewport image.Rectangle
  55. // metric is the metric from the most recent frame.
  56. metric unit.Metric
  57. queue input.Router
  58. cursor pointer.Cursor
  59. decorations struct {
  60. op.Ops
  61. // enabled tracks the Decorated option as
  62. // given to the Option method. It may differ
  63. // from Config.Decorated depending on platform
  64. // capability.
  65. enabled bool
  66. Config
  67. height unit.Dp
  68. currentHeight int
  69. *material.Theme
  70. *widget.Decorations
  71. }
  72. nocontext bool
  73. // semantic data, lazily evaluated if requested by a backend to speed up
  74. // the cases where semantic data is not needed.
  75. semantic struct {
  76. // uptodate tracks whether the fields below are up to date.
  77. uptodate bool
  78. root input.SemanticID
  79. prevTree []input.SemanticNode
  80. tree []input.SemanticNode
  81. ids map[input.SemanticID]input.SemanticNode
  82. }
  83. imeState editorState
  84. driver driver
  85. // gpuErr tracks the GPU error that is to be reported when
  86. // the window is closed.
  87. gpuErr error
  88. // invMu protects mayInvalidate.
  89. invMu sync.Mutex
  90. mayInvalidate bool
  91. // coalesced tracks the most recent events waiting to be delivered
  92. // to the client.
  93. coalesced eventSummary
  94. // frame tracks the most recent frame event.
  95. lastFrame struct {
  96. sync bool
  97. size image.Point
  98. off image.Point
  99. deco op.CallOp
  100. }
  101. }
  102. type eventSummary struct {
  103. wakeup bool
  104. cfg *ConfigEvent
  105. view *ViewEvent
  106. frame *frameEvent
  107. framePending bool
  108. destroy *DestroyEvent
  109. }
  110. type callbacks struct {
  111. w *Window
  112. }
  113. func decoHeightOpt(h unit.Dp) Option {
  114. return func(m unit.Metric, c *Config) {
  115. c.decoHeight = h
  116. }
  117. }
  118. func (w *Window) validateAndProcess(size image.Point, sync bool, frame *op.Ops, sigChan chan<- struct{}) error {
  119. signal := func() {
  120. if sigChan != nil {
  121. // We're done with frame, let the client continue.
  122. sigChan <- struct{}{}
  123. // Signal at most once.
  124. sigChan = nil
  125. }
  126. }
  127. defer signal()
  128. for {
  129. if w.gpu == nil && !w.nocontext {
  130. var err error
  131. if w.ctx == nil {
  132. w.ctx, err = w.driver.NewContext()
  133. if err != nil {
  134. return err
  135. }
  136. sync = true
  137. }
  138. }
  139. if sync && w.ctx != nil {
  140. if err := w.ctx.Refresh(); err != nil {
  141. if errors.Is(err, errOutOfDate) {
  142. // Surface couldn't be created for transient reasons. Skip
  143. // this frame and wait for the next.
  144. return nil
  145. }
  146. w.destroyGPU()
  147. if errors.Is(err, gpu.ErrDeviceLost) {
  148. continue
  149. }
  150. return err
  151. }
  152. }
  153. if w.ctx != nil {
  154. if err := w.ctx.Lock(); err != nil {
  155. w.destroyGPU()
  156. return err
  157. }
  158. }
  159. if w.gpu == nil && !w.nocontext {
  160. gpu, err := gpu.New(w.ctx.API())
  161. if err != nil {
  162. w.ctx.Unlock()
  163. w.destroyGPU()
  164. return err
  165. }
  166. w.gpu = gpu
  167. }
  168. if w.gpu != nil {
  169. if err := w.frame(frame, size); err != nil {
  170. w.ctx.Unlock()
  171. if errors.Is(err, errOutOfDate) {
  172. // GPU surface needs refreshing.
  173. sync = true
  174. continue
  175. }
  176. w.destroyGPU()
  177. if errors.Is(err, gpu.ErrDeviceLost) {
  178. continue
  179. }
  180. return err
  181. }
  182. }
  183. w.queue.Frame(frame)
  184. // Let the client continue as soon as possible, in particular before
  185. // a potentially blocking Present.
  186. signal()
  187. var err error
  188. if w.gpu != nil {
  189. err = w.ctx.Present()
  190. w.ctx.Unlock()
  191. }
  192. return err
  193. }
  194. }
  195. func (w *Window) frame(frame *op.Ops, viewport image.Point) error {
  196. if runtime.GOOS == "js" {
  197. // Use transparent black when Gio is embedded, to allow mixing of Gio and
  198. // foreign content below.
  199. w.gpu.Clear(color.NRGBA{A: 0x00, R: 0x00, G: 0x00, B: 0x00})
  200. } else {
  201. w.gpu.Clear(color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
  202. }
  203. target, err := w.ctx.RenderTarget()
  204. if err != nil {
  205. return err
  206. }
  207. return w.gpu.Frame(frame, target, viewport)
  208. }
  209. func (w *Window) processFrame(frame *op.Ops, ack chan<- struct{}) {
  210. w.coalesced.framePending = false
  211. wrapper := &w.decorations.Ops
  212. off := op.Offset(w.lastFrame.off).Push(wrapper)
  213. ops.AddCall(&wrapper.Internal, &frame.Internal, ops.PC{}, ops.PCFor(&frame.Internal))
  214. off.Pop()
  215. w.lastFrame.deco.Add(wrapper)
  216. if err := w.validateAndProcess(w.lastFrame.size, w.lastFrame.sync, wrapper, ack); err != nil {
  217. w.destroyGPU()
  218. w.gpuErr = err
  219. w.driver.Perform(system.ActionClose)
  220. return
  221. }
  222. w.updateState()
  223. w.updateCursor()
  224. }
  225. func (w *Window) updateState() {
  226. for k := range w.semantic.ids {
  227. delete(w.semantic.ids, k)
  228. }
  229. w.semantic.uptodate = false
  230. q := &w.queue
  231. switch q.TextInputState() {
  232. case input.TextInputOpen:
  233. w.driver.ShowTextInput(true)
  234. case input.TextInputClose:
  235. w.driver.ShowTextInput(false)
  236. }
  237. if hint, ok := q.TextInputHint(); ok {
  238. w.driver.SetInputHint(hint)
  239. }
  240. if mime, txt, ok := q.WriteClipboard(); ok {
  241. w.driver.WriteClipboard(mime, txt)
  242. }
  243. if q.ClipboardRequested() {
  244. w.driver.ReadClipboard()
  245. }
  246. oldState := w.imeState
  247. newState := oldState
  248. newState.EditorState = q.EditorState()
  249. if newState != oldState {
  250. w.imeState = newState
  251. w.driver.EditorStateChanged(oldState, newState)
  252. }
  253. if t, ok := q.WakeupTime(); ok {
  254. w.setNextFrame(t)
  255. }
  256. w.updateAnimation()
  257. }
  258. // Invalidate the window such that a [FrameEvent] will be generated immediately.
  259. // If the window is inactive, an unspecified event is sent instead.
  260. //
  261. // Note that Invalidate is intended for externally triggered updates, such as a
  262. // response from a network request. The [op.InvalidateCmd] command is more efficient
  263. // for animation.
  264. //
  265. // Invalidate is safe for concurrent use.
  266. func (w *Window) Invalidate() {
  267. w.invMu.Lock()
  268. defer w.invMu.Unlock()
  269. if w.mayInvalidate {
  270. w.mayInvalidate = false
  271. w.driver.Invalidate()
  272. }
  273. }
  274. // Option applies the options to the window. The options are hints; the platform is
  275. // free to ignore or adjust them.
  276. func (w *Window) Option(opts ...Option) {
  277. if len(opts) == 0 {
  278. return
  279. }
  280. if w.driver == nil {
  281. w.initialOpts = append(w.initialOpts, opts...)
  282. return
  283. }
  284. w.Run(func() {
  285. cnf := Config{Decorated: w.decorations.enabled}
  286. for _, opt := range opts {
  287. opt(w.metric, &cnf)
  288. }
  289. w.decorations.enabled = cnf.Decorated
  290. decoHeight := w.decorations.height
  291. if !w.decorations.enabled {
  292. decoHeight = 0
  293. }
  294. opts = append(opts, decoHeightOpt(decoHeight))
  295. w.driver.Configure(opts)
  296. w.setNextFrame(time.Time{})
  297. w.updateAnimation()
  298. })
  299. }
  300. // Run f in the same thread as the native window event loop, and wait for f to
  301. // return or the window to close. If the window has not yet been created,
  302. // Run calls f directly.
  303. //
  304. // Note that most programs should not call Run; configuring a Window with
  305. // [CustomRenderer] is a notable exception.
  306. func (w *Window) Run(f func()) {
  307. if w.driver == nil {
  308. f()
  309. return
  310. }
  311. done := make(chan struct{})
  312. w.driver.Run(func() {
  313. defer close(done)
  314. f()
  315. })
  316. <-done
  317. }
  318. func (w *Window) updateAnimation() {
  319. if w.driver == nil {
  320. return
  321. }
  322. animate := false
  323. if w.hasNextFrame {
  324. if dt := time.Until(w.nextFrame); dt <= 0 {
  325. animate = true
  326. } else {
  327. // Schedule redraw.
  328. w.scheduleInvalidate(w.nextFrame)
  329. }
  330. }
  331. if animate != w.animating {
  332. w.animating = animate
  333. w.driver.SetAnimating(animate)
  334. }
  335. }
  336. func (w *Window) scheduleInvalidate(t time.Time) {
  337. if w.timer.quit == nil {
  338. w.timer.quit = make(chan struct{})
  339. w.timer.update = make(chan time.Time)
  340. go func() {
  341. var timer *time.Timer
  342. for {
  343. var timeC <-chan time.Time
  344. if timer != nil {
  345. timeC = timer.C
  346. }
  347. select {
  348. case <-w.timer.quit:
  349. w.timer.quit <- struct{}{}
  350. return
  351. case t := <-w.timer.update:
  352. if timer != nil {
  353. timer.Stop()
  354. }
  355. timer = time.NewTimer(time.Until(t))
  356. case <-timeC:
  357. w.Invalidate()
  358. }
  359. }
  360. }()
  361. }
  362. w.timer.update <- t
  363. }
  364. func (w *Window) setNextFrame(at time.Time) {
  365. if !w.hasNextFrame || at.Before(w.nextFrame) {
  366. w.hasNextFrame = true
  367. w.nextFrame = at
  368. }
  369. }
  370. func (c *callbacks) SetDriver(d driver) {
  371. if d == nil {
  372. panic("nil driver")
  373. }
  374. c.w.invMu.Lock()
  375. defer c.w.invMu.Unlock()
  376. c.w.driver = d
  377. }
  378. func (c *callbacks) ProcessFrame(frame *op.Ops, ack chan<- struct{}) {
  379. c.w.processFrame(frame, ack)
  380. }
  381. func (c *callbacks) ProcessEvent(e event.Event) bool {
  382. return c.w.processEvent(e)
  383. }
  384. // SemanticRoot returns the ID of the semantic root.
  385. func (c *callbacks) SemanticRoot() input.SemanticID {
  386. c.w.updateSemantics()
  387. return c.w.semantic.root
  388. }
  389. // LookupSemantic looks up a semantic node from an ID. The zero ID denotes the root.
  390. func (c *callbacks) LookupSemantic(semID input.SemanticID) (input.SemanticNode, bool) {
  391. c.w.updateSemantics()
  392. n, found := c.w.semantic.ids[semID]
  393. return n, found
  394. }
  395. func (c *callbacks) AppendSemanticDiffs(diffs []input.SemanticID) []input.SemanticID {
  396. c.w.updateSemantics()
  397. if tree := c.w.semantic.prevTree; len(tree) > 0 {
  398. c.w.collectSemanticDiffs(&diffs, c.w.semantic.prevTree[0])
  399. }
  400. return diffs
  401. }
  402. func (c *callbacks) SemanticAt(pos f32.Point) (input.SemanticID, bool) {
  403. c.w.updateSemantics()
  404. return c.w.queue.SemanticAt(pos)
  405. }
  406. func (c *callbacks) EditorState() editorState {
  407. return c.w.imeState
  408. }
  409. func (c *callbacks) SetComposingRegion(r key.Range) {
  410. c.w.imeState.compose = r
  411. }
  412. func (c *callbacks) EditorInsert(text string) {
  413. sel := c.w.imeState.Selection.Range
  414. c.EditorReplace(sel, text)
  415. start := sel.Start
  416. if sel.End < start {
  417. start = sel.End
  418. }
  419. sel.Start = start + utf8.RuneCountInString(text)
  420. sel.End = sel.Start
  421. c.SetEditorSelection(sel)
  422. }
  423. func (c *callbacks) EditorReplace(r key.Range, text string) {
  424. c.w.imeState.Replace(r, text)
  425. c.w.driver.ProcessEvent(key.EditEvent{Range: r, Text: text})
  426. c.w.driver.ProcessEvent(key.SnippetEvent(c.w.imeState.Snippet.Range))
  427. }
  428. func (c *callbacks) SetEditorSelection(r key.Range) {
  429. c.w.imeState.Selection.Range = r
  430. c.w.driver.ProcessEvent(key.SelectionEvent(r))
  431. }
  432. func (c *callbacks) SetEditorSnippet(r key.Range) {
  433. if sn := c.EditorState().Snippet.Range; sn == r {
  434. // No need to expand.
  435. return
  436. }
  437. c.w.driver.ProcessEvent(key.SnippetEvent(r))
  438. }
  439. func (w *Window) moveFocus(dir key.FocusDirection) {
  440. w.queue.MoveFocus(dir)
  441. if _, handled := w.queue.WakeupTime(); handled {
  442. w.queue.RevealFocus(w.viewport)
  443. } else {
  444. var v image.Point
  445. switch dir {
  446. case key.FocusRight:
  447. v = image.Pt(+1, 0)
  448. case key.FocusLeft:
  449. v = image.Pt(-1, 0)
  450. case key.FocusDown:
  451. v = image.Pt(0, +1)
  452. case key.FocusUp:
  453. v = image.Pt(0, -1)
  454. default:
  455. return
  456. }
  457. const scrollABit = unit.Dp(50)
  458. dist := v.Mul(int(w.metric.Dp(scrollABit)))
  459. w.queue.ScrollFocus(dist)
  460. }
  461. }
  462. func (c *callbacks) ClickFocus() {
  463. c.w.queue.ClickFocus()
  464. c.w.setNextFrame(time.Time{})
  465. c.w.updateAnimation()
  466. }
  467. func (c *callbacks) ActionAt(p f32.Point) (system.Action, bool) {
  468. return c.w.queue.ActionAt(p)
  469. }
  470. func (w *Window) destroyGPU() {
  471. if w.gpu != nil {
  472. w.ctx.Lock()
  473. w.gpu.Release()
  474. w.ctx.Unlock()
  475. w.gpu = nil
  476. }
  477. if w.ctx != nil {
  478. w.ctx.Release()
  479. w.ctx = nil
  480. }
  481. }
  482. // updateSemantics refreshes the semantics tree, the id to node map and the ids of
  483. // updated nodes.
  484. func (w *Window) updateSemantics() {
  485. if w.semantic.uptodate {
  486. return
  487. }
  488. w.semantic.uptodate = true
  489. w.semantic.prevTree, w.semantic.tree = w.semantic.tree, w.semantic.prevTree
  490. w.semantic.tree = w.queue.AppendSemantics(w.semantic.tree[:0])
  491. w.semantic.root = w.semantic.tree[0].ID
  492. for _, n := range w.semantic.tree {
  493. w.semantic.ids[n.ID] = n
  494. }
  495. }
  496. // collectSemanticDiffs traverses the previous semantic tree, noting changed nodes.
  497. func (w *Window) collectSemanticDiffs(diffs *[]input.SemanticID, n input.SemanticNode) {
  498. newNode, exists := w.semantic.ids[n.ID]
  499. // Ignore deleted nodes, as their disappearance will be reported through an
  500. // ancestor node.
  501. if !exists {
  502. return
  503. }
  504. diff := newNode.Desc != n.Desc || len(n.Children) != len(newNode.Children)
  505. for i, ch := range n.Children {
  506. if !diff {
  507. newCh := newNode.Children[i]
  508. diff = ch.ID != newCh.ID
  509. }
  510. w.collectSemanticDiffs(diffs, ch)
  511. }
  512. if diff {
  513. *diffs = append(*diffs, n.ID)
  514. }
  515. }
  516. func (c *callbacks) Invalidate() {
  517. c.w.setNextFrame(time.Time{})
  518. c.w.updateAnimation()
  519. // Guarantee a wakeup, even when not animating.
  520. c.w.processEvent(wakeupEvent{})
  521. }
  522. func (c *callbacks) nextEvent() (event.Event, bool) {
  523. return c.w.nextEvent()
  524. }
  525. func (w *Window) nextEvent() (event.Event, bool) {
  526. s := &w.coalesced
  527. defer func() {
  528. // Every event counts as a wakeup.
  529. s.wakeup = false
  530. }()
  531. switch {
  532. case s.framePending:
  533. // If the user didn't call FrameEvent.Event, process
  534. // an empty frame.
  535. w.processFrame(new(op.Ops), nil)
  536. case s.view != nil:
  537. e := *s.view
  538. s.view = nil
  539. return e, true
  540. case s.destroy != nil:
  541. e := *s.destroy
  542. // Clear pending events after DestroyEvent is delivered.
  543. *s = eventSummary{}
  544. return e, true
  545. case s.cfg != nil:
  546. e := *s.cfg
  547. s.cfg = nil
  548. return e, true
  549. case s.frame != nil:
  550. e := *s.frame
  551. s.frame = nil
  552. s.framePending = true
  553. return e.FrameEvent, true
  554. case s.wakeup:
  555. return wakeupEvent{}, true
  556. }
  557. w.invMu.Lock()
  558. defer w.invMu.Unlock()
  559. w.mayInvalidate = w.driver != nil
  560. return nil, false
  561. }
  562. func (w *Window) processEvent(e event.Event) bool {
  563. switch e2 := e.(type) {
  564. case wakeupEvent:
  565. w.coalesced.wakeup = true
  566. case frameEvent:
  567. if e2.Size == (image.Point{}) {
  568. panic(errors.New("internal error: zero-sized Draw"))
  569. }
  570. w.metric = e2.Metric
  571. w.hasNextFrame = false
  572. e2.Frame = w.driver.Frame
  573. e2.Source = w.queue.Source()
  574. // Prepare the decorations and update the frame insets.
  575. viewport := image.Rectangle{
  576. Min: image.Point{
  577. X: e2.Metric.Dp(e2.Insets.Left),
  578. Y: e2.Metric.Dp(e2.Insets.Top),
  579. },
  580. Max: image.Point{
  581. X: e2.Size.X - e2.Metric.Dp(e2.Insets.Right),
  582. Y: e2.Size.Y - e2.Metric.Dp(e2.Insets.Bottom),
  583. },
  584. }
  585. // Scroll to focus if viewport is shrinking in any dimension.
  586. if old, new := w.viewport.Size(), viewport.Size(); new.X < old.X || new.Y < old.Y {
  587. w.queue.RevealFocus(viewport)
  588. }
  589. w.viewport = viewport
  590. wrapper := &w.decorations.Ops
  591. wrapper.Reset()
  592. m := op.Record(wrapper)
  593. offset := w.decorate(e2.FrameEvent, wrapper)
  594. w.lastFrame.deco = m.Stop()
  595. w.lastFrame.size = e2.Size
  596. w.lastFrame.sync = e2.Sync
  597. w.lastFrame.off = offset
  598. e2.Size = e2.Size.Sub(offset)
  599. w.coalesced.frame = &e2
  600. case DestroyEvent:
  601. if w.gpuErr != nil {
  602. e2.Err = w.gpuErr
  603. }
  604. w.destroyGPU()
  605. w.invMu.Lock()
  606. w.mayInvalidate = false
  607. w.driver = nil
  608. w.invMu.Unlock()
  609. if q := w.timer.quit; q != nil {
  610. q <- struct{}{}
  611. <-q
  612. }
  613. w.coalesced.destroy = &e2
  614. case ViewEvent:
  615. if !e2.Valid() && w.gpu != nil {
  616. w.ctx.Lock()
  617. w.gpu.Release()
  618. w.gpu = nil
  619. w.ctx.Unlock()
  620. }
  621. w.coalesced.view = &e2
  622. case ConfigEvent:
  623. w.decorations.Decorations.Maximized = e2.Config.Mode == Maximized
  624. wasFocused := w.decorations.Config.Focused
  625. w.decorations.Config = e2.Config
  626. e2.Config = w.effectiveConfig()
  627. w.coalesced.cfg = &e2
  628. if f := w.decorations.Config.Focused; f != wasFocused {
  629. w.queue.Queue(key.FocusEvent{Focus: f})
  630. }
  631. t, handled := w.queue.WakeupTime()
  632. if handled {
  633. w.setNextFrame(t)
  634. w.updateAnimation()
  635. }
  636. return handled
  637. case event.Event:
  638. focusDir := key.FocusDirection(-1)
  639. if e, ok := e2.(key.Event); ok && e.State == key.Press {
  640. isMobile := runtime.GOOS == "ios" || runtime.GOOS == "android"
  641. switch {
  642. case e.Name == key.NameTab && e.Modifiers == 0:
  643. focusDir = key.FocusForward
  644. case e.Name == key.NameTab && e.Modifiers == key.ModShift:
  645. focusDir = key.FocusBackward
  646. case e.Name == key.NameUpArrow && e.Modifiers == 0 && isMobile:
  647. focusDir = key.FocusUp
  648. case e.Name == key.NameDownArrow && e.Modifiers == 0 && isMobile:
  649. focusDir = key.FocusDown
  650. case e.Name == key.NameLeftArrow && e.Modifiers == 0 && isMobile:
  651. focusDir = key.FocusLeft
  652. case e.Name == key.NameRightArrow && e.Modifiers == 0 && isMobile:
  653. focusDir = key.FocusRight
  654. }
  655. }
  656. e := e2
  657. if focusDir != -1 {
  658. e = input.SystemEvent{Event: e}
  659. }
  660. w.queue.Queue(e)
  661. t, handled := w.queue.WakeupTime()
  662. if focusDir != -1 && !handled {
  663. w.moveFocus(focusDir)
  664. t, handled = w.queue.WakeupTime()
  665. }
  666. w.updateCursor()
  667. if handled {
  668. w.setNextFrame(t)
  669. w.updateAnimation()
  670. }
  671. return handled
  672. }
  673. return true
  674. }
  675. // Event blocks until an event is received from the window, such as
  676. // [FrameEvent], or until [Invalidate] is called. The window is created
  677. // and shown the first time Event is called.
  678. func (w *Window) Event() event.Event {
  679. if w.driver == nil {
  680. w.init()
  681. }
  682. if w.driver == nil {
  683. e, ok := w.nextEvent()
  684. if !ok {
  685. panic("window initialization failed without a DestroyEvent")
  686. }
  687. return e
  688. }
  689. return w.driver.Event()
  690. }
  691. func (w *Window) init() {
  692. debug.Parse()
  693. // Measure decoration height.
  694. deco := new(widget.Decorations)
  695. theme := material.NewTheme()
  696. theme.Shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(gofont.Regular()))
  697. decoStyle := material.Decorations(theme, deco, 0, "")
  698. gtx := layout.Context{
  699. Ops: new(op.Ops),
  700. // Measure in Dp.
  701. Metric: unit.Metric{},
  702. }
  703. // Allow plenty of space.
  704. gtx.Constraints.Max.Y = 200
  705. dims := decoStyle.Layout(gtx)
  706. decoHeight := unit.Dp(dims.Size.Y)
  707. defaultOptions := []Option{
  708. Size(800, 600),
  709. Title("Gio"),
  710. Decorated(true),
  711. decoHeightOpt(decoHeight),
  712. }
  713. options := append(defaultOptions, w.initialOpts...)
  714. w.initialOpts = nil
  715. var cnf Config
  716. cnf.apply(unit.Metric{}, options)
  717. w.nocontext = cnf.CustomRenderer
  718. w.decorations.Theme = theme
  719. w.decorations.Decorations = deco
  720. w.decorations.enabled = cnf.Decorated
  721. w.decorations.height = decoHeight
  722. w.imeState.compose = key.Range{Start: -1, End: -1}
  723. w.semantic.ids = make(map[input.SemanticID]input.SemanticNode)
  724. newWindow(&callbacks{w}, options)
  725. for _, acts := range w.initialActions {
  726. w.Perform(acts)
  727. }
  728. w.initialActions = nil
  729. }
  730. func (w *Window) updateCursor() {
  731. if c := w.queue.Cursor(); c != w.cursor {
  732. w.cursor = c
  733. w.driver.SetCursor(c)
  734. }
  735. }
  736. func (w *Window) fallbackDecorate() bool {
  737. cnf := w.decorations.Config
  738. return w.decorations.enabled && !cnf.Decorated && cnf.Mode != Fullscreen && !w.nocontext
  739. }
  740. // decorate the window if enabled and returns the corresponding Insets.
  741. func (w *Window) decorate(e FrameEvent, o *op.Ops) image.Point {
  742. if !w.fallbackDecorate() {
  743. return image.Pt(0, 0)
  744. }
  745. deco := w.decorations.Decorations
  746. allActions := system.ActionMinimize | system.ActionMaximize | system.ActionUnmaximize |
  747. system.ActionClose | system.ActionMove
  748. style := material.Decorations(w.decorations.Theme, deco, allActions, w.decorations.Config.Title)
  749. // Update the decorations based on the current window mode.
  750. var actions system.Action
  751. switch m := w.decorations.Config.Mode; m {
  752. case Windowed:
  753. actions |= system.ActionUnmaximize
  754. case Minimized:
  755. actions |= system.ActionMinimize
  756. case Maximized:
  757. actions |= system.ActionMaximize
  758. case Fullscreen:
  759. actions |= system.ActionFullscreen
  760. default:
  761. panic(fmt.Errorf("unknown WindowMode %v", m))
  762. }
  763. gtx := layout.Context{
  764. Ops: o,
  765. Now: e.Now,
  766. Source: e.Source,
  767. Metric: e.Metric,
  768. Constraints: layout.Exact(e.Size),
  769. }
  770. // Update the window based on the actions on the decorations.
  771. opts, acts := splitActions(deco.Update(gtx))
  772. if len(opts) > 0 {
  773. w.driver.Configure(opts)
  774. }
  775. if acts != 0 {
  776. w.driver.Perform(acts)
  777. }
  778. style.Layout(gtx)
  779. // Offset to place the frame content below the decorations.
  780. decoHeight := gtx.Dp(w.decorations.Config.decoHeight)
  781. if w.decorations.currentHeight != decoHeight {
  782. w.decorations.currentHeight = decoHeight
  783. w.coalesced.cfg = &ConfigEvent{Config: w.effectiveConfig()}
  784. }
  785. return image.Pt(0, decoHeight)
  786. }
  787. func (w *Window) effectiveConfig() Config {
  788. cnf := w.decorations.Config
  789. cnf.Size.Y -= w.decorations.currentHeight
  790. cnf.Decorated = w.decorations.enabled || cnf.Decorated
  791. return cnf
  792. }
  793. // splitActions splits options from actions and return them and the remaining
  794. // actions.
  795. func splitActions(actions system.Action) ([]Option, system.Action) {
  796. var opts []Option
  797. walkActions(actions, func(action system.Action) {
  798. switch action {
  799. case system.ActionMinimize:
  800. opts = append(opts, Minimized.Option())
  801. case system.ActionMaximize:
  802. opts = append(opts, Maximized.Option())
  803. case system.ActionUnmaximize:
  804. opts = append(opts, Windowed.Option())
  805. case system.ActionFullscreen:
  806. opts = append(opts, Fullscreen.Option())
  807. default:
  808. return
  809. }
  810. actions &^= action
  811. })
  812. return opts, actions
  813. }
  814. // Perform the actions on the window.
  815. func (w *Window) Perform(actions system.Action) {
  816. opts, acts := splitActions(actions)
  817. w.Option(opts...)
  818. if acts == 0 {
  819. return
  820. }
  821. if w.driver == nil {
  822. w.initialActions = append(w.initialActions, acts)
  823. return
  824. }
  825. w.Run(func() {
  826. w.driver.Perform(actions)
  827. })
  828. }
  829. // Title sets the title of the window.
  830. func Title(t string) Option {
  831. return func(_ unit.Metric, cnf *Config) {
  832. cnf.Title = t
  833. }
  834. }
  835. // Size sets the size of the window. The mode will be changed to Windowed.
  836. func Size(w, h unit.Dp) Option {
  837. if w <= 0 {
  838. panic("width must be larger than or equal to 0")
  839. }
  840. if h <= 0 {
  841. panic("height must be larger than or equal to 0")
  842. }
  843. return func(m unit.Metric, cnf *Config) {
  844. cnf.Mode = Windowed
  845. cnf.Size = image.Point{
  846. X: m.Dp(w),
  847. Y: m.Dp(h),
  848. }
  849. }
  850. }
  851. // MaxSize sets the maximum size of the window.
  852. func MaxSize(w, h unit.Dp) Option {
  853. if w <= 0 {
  854. panic("width must be larger than or equal to 0")
  855. }
  856. if h <= 0 {
  857. panic("height must be larger than or equal to 0")
  858. }
  859. return func(m unit.Metric, cnf *Config) {
  860. cnf.MaxSize = image.Point{
  861. X: m.Dp(w),
  862. Y: m.Dp(h),
  863. }
  864. }
  865. }
  866. // MinSize sets the minimum size of the window.
  867. func MinSize(w, h unit.Dp) Option {
  868. if w <= 0 {
  869. panic("width must be larger than or equal to 0")
  870. }
  871. if h <= 0 {
  872. panic("height must be larger than or equal to 0")
  873. }
  874. return func(m unit.Metric, cnf *Config) {
  875. cnf.MinSize = image.Point{
  876. X: m.Dp(w),
  877. Y: m.Dp(h),
  878. }
  879. }
  880. }
  881. // StatusColor sets the color of the Android status bar.
  882. func StatusColor(color color.NRGBA) Option {
  883. return func(_ unit.Metric, cnf *Config) {
  884. cnf.StatusColor = color
  885. }
  886. }
  887. // NavigationColor sets the color of the navigation bar on Android, or the address bar in browsers.
  888. func NavigationColor(color color.NRGBA) Option {
  889. return func(_ unit.Metric, cnf *Config) {
  890. cnf.NavigationColor = color
  891. }
  892. }
  893. // CustomRenderer controls whether the window contents is
  894. // rendered by the client. If true, no GPU context is created.
  895. //
  896. // Caller must assume responsibility for rendering which includes
  897. // initializing the render backend, swapping the framebuffer and
  898. // handling frame pacing.
  899. func CustomRenderer(custom bool) Option {
  900. return func(_ unit.Metric, cnf *Config) {
  901. cnf.CustomRenderer = custom
  902. }
  903. }
  904. // Decorated controls whether Gio and/or the platform are responsible
  905. // for drawing window decorations. Providing false indicates that
  906. // the application will either be undecorated or will draw its own decorations.
  907. func Decorated(enabled bool) Option {
  908. return func(_ unit.Metric, cnf *Config) {
  909. cnf.Decorated = enabled
  910. }
  911. }
  912. // flushEvent is sent to detect when the user program
  913. // has completed processing of all prior events. Its an
  914. // [io/event.Event] but only for internal use.
  915. type flushEvent struct{}
  916. func (t flushEvent) ImplementsEvent() {}
  917. // theFlushEvent avoids allocating garbage when sending
  918. // flushEvents.
  919. var theFlushEvent flushEvent