| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- // Copyright 2014 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //go:build linux || darwin || windows
- package app
- import (
- "golang.org/x/mobile/event/lifecycle"
- "golang.org/x/mobile/event/size"
- "golang.org/x/mobile/gl"
- _ "golang.org/x/mobile/internal/mobileinit"
- )
- // Main is called by the main.main function to run the mobile application.
- //
- // It calls f on the App, in a separate goroutine, as some OS-specific
- // libraries require being on 'the main thread'.
- func Main(f func(App)) {
- main(f)
- }
- // App is how a GUI mobile application interacts with the OS.
- type App interface {
- // Events returns the events channel. It carries events from the system to
- // the app. The type of such events include:
- // - lifecycle.Event
- // - mouse.Event
- // - paint.Event
- // - size.Event
- // - touch.Event
- // from the golang.org/x/mobile/event/etc packages. Other packages may
- // define other event types that are carried on this channel.
- Events() <-chan interface{}
- // Send sends an event on the events channel. It does not block.
- Send(event interface{})
- // Publish flushes any pending drawing commands, such as OpenGL calls, and
- // swaps the back buffer to the screen.
- Publish() PublishResult
- // TODO: replace filters (and the Events channel) with a NextEvent method?
- // Filter calls each registered event filter function in sequence.
- Filter(event interface{}) interface{}
- // RegisterFilter registers a event filter function to be called by Filter. The
- // function can return a different event, or return nil to consume the event,
- // but the function can also return its argument unchanged, where its purpose
- // is to trigger a side effect rather than modify the event.
- RegisterFilter(f func(interface{}) interface{})
- }
- // PublishResult is the result of an App.Publish call.
- type PublishResult struct {
- // BackBufferPreserved is whether the contents of the back buffer was
- // preserved. If false, the contents are undefined.
- BackBufferPreserved bool
- }
- var theApp = &app{
- eventsOut: make(chan interface{}),
- lifecycleStage: lifecycle.StageDead,
- publish: make(chan struct{}),
- publishResult: make(chan PublishResult),
- }
- func init() {
- theApp.eventsIn = pump(theApp.eventsOut)
- theApp.glctx, theApp.worker = gl.NewContext()
- }
- func (a *app) sendLifecycle(to lifecycle.Stage) {
- if a.lifecycleStage == to {
- return
- }
- a.eventsIn <- lifecycle.Event{
- From: a.lifecycleStage,
- To: to,
- DrawContext: a.glctx,
- }
- a.lifecycleStage = to
- }
- type app struct {
- filters []func(interface{}) interface{}
- eventsOut chan interface{}
- eventsIn chan interface{}
- lifecycleStage lifecycle.Stage
- publish chan struct{}
- publishResult chan PublishResult
- glctx gl.Context
- worker gl.Worker
- }
- func (a *app) Events() <-chan interface{} {
- return a.eventsOut
- }
- func (a *app) Send(event interface{}) {
- a.eventsIn <- event
- }
- func (a *app) Publish() PublishResult {
- // gl.Flush is a lightweight (on modern GL drivers) blocking call
- // that ensures all GL functions pending in the gl package have
- // been passed onto the GL driver before the app package attempts
- // to swap the screen buffer.
- //
- // This enforces that the final receive (for this paint cycle) on
- // gl.WorkAvailable happens before the send on endPaint.
- a.glctx.Flush()
- a.publish <- struct{}{}
- return <-a.publishResult
- }
- func (a *app) Filter(event interface{}) interface{} {
- for _, f := range a.filters {
- event = f(event)
- }
- return event
- }
- func (a *app) RegisterFilter(f func(interface{}) interface{}) {
- a.filters = append(a.filters, f)
- }
- type stopPumping struct{}
- // pump returns a channel src such that sending on src will eventually send on
- // dst, in order, but that src will always be ready to send/receive soon, even
- // if dst currently isn't. It is effectively an infinitely buffered channel.
- //
- // In particular, goroutine A sending on src will not deadlock even if goroutine
- // B that's responsible for receiving on dst is currently blocked trying to
- // send to A on a separate channel.
- //
- // Send a stopPumping on the src channel to close the dst channel after all queued
- // events are sent on dst. After that, other goroutines can still send to src,
- // so that such sends won't block forever, but such events will be ignored.
- func pump(dst chan interface{}) (src chan interface{}) {
- src = make(chan interface{})
- go func() {
- // initialSize is the initial size of the circular buffer. It must be a
- // power of 2.
- const initialSize = 16
- i, j, buf, mask := 0, 0, make([]interface{}, initialSize), initialSize-1
- srcActive := true
- for {
- maybeDst := dst
- if i == j {
- maybeDst = nil
- }
- if maybeDst == nil && !srcActive {
- // Pump is stopped and empty.
- break
- }
- select {
- case maybeDst <- buf[i&mask]:
- buf[i&mask] = nil
- i++
- case e := <-src:
- if _, ok := e.(stopPumping); ok {
- srcActive = false
- continue
- }
- if !srcActive {
- continue
- }
- // Allocate a bigger buffer if necessary.
- if i+len(buf) == j {
- b := make([]interface{}, 2*len(buf))
- n := copy(b, buf[j&mask:])
- copy(b[n:], buf[:j&mask])
- i, j = 0, len(buf)
- buf, mask = b, len(b)-1
- }
- buf[j&mask] = e
- j++
- }
- }
- close(dst)
- // Block forever.
- for range src {
- }
- }()
- return src
- }
- // TODO: do this for all build targets, not just linux (x11 and Android)? If
- // so, should package gl instead of this package call RegisterFilter??
- //
- // TODO: does Android need this?? It seems to work without it (Nexus 7,
- // KitKat). If only x11 needs this, should we move this to x11.go??
- func (a *app) registerGLViewportFilter() {
- a.RegisterFilter(func(e interface{}) interface{} {
- if e, ok := e.(size.Event); ok {
- a.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx)
- }
- return e
- })
- }
|