| // 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. |
| |
| // +build linux darwin |
| |
| package app |
| |
| import ( |
| "io" |
| "log" |
| "runtime" |
| |
| "golang.org/x/mobile/event" |
| ) |
| |
| // 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) error) { |
| runtime.LockOSThread() |
| if err := main(f); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| // 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: |
| // - event.Config |
| // - event.Draw |
| // - event.Lifecycle |
| // - event.Touch |
| // from the golang.org/x/mobile/events package. 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{}) |
| |
| // EndDraw flushes any pending OpenGL commands or buffers to the screen. |
| EndDraw() |
| } |
| |
| var ( |
| lifecycleStage = event.LifecycleStageDead |
| pixelsPerPt = float32(1) |
| |
| eventsOut = make(chan interface{}) |
| eventsIn = pump(eventsOut) |
| endDraw = make(chan struct{}, 1) |
| ) |
| |
| func sendLifecycle(to event.LifecycleStage) { |
| if lifecycleStage == to { |
| return |
| } |
| eventsIn <- event.Lifecycle{ |
| From: lifecycleStage, |
| To: to, |
| } |
| lifecycleStage = to |
| } |
| |
| type app struct{} |
| |
| func (app) Events() <-chan interface{} { |
| return eventsOut |
| } |
| |
| func (app) Send(event interface{}) { |
| eventsIn <- event |
| } |
| |
| func (app) EndDraw() { |
| select { |
| case endDraw <- struct{}{}: |
| default: |
| } |
| } |
| |
| 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 |
| |
| maybeSrc := src |
| for { |
| maybeDst := dst |
| if i == j { |
| maybeDst = nil |
| } |
| if maybeDst == nil && maybeSrc == nil { |
| break |
| } |
| |
| select { |
| case maybeDst <- buf[i&mask]: |
| buf[i&mask] = nil |
| i++ |
| |
| case e := <-maybeSrc: |
| if _, ok := e.(stopPumping); ok { |
| maybeSrc = nil |
| 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 |
| } |
| |
| // Run starts the mobile application. |
| // |
| // It must be called directly from the main function and will block until the |
| // application exits. |
| // |
| // Deprecated: call Main directly instead. |
| func Run(cb Callbacks) { |
| Main(func(a App) error { |
| var c event.Config |
| for e := range a.Events() { |
| switch e := event.Filter(e).(type) { |
| case event.Lifecycle: |
| switch e.Crosses(event.LifecycleStageVisible) { |
| case event.ChangeOn: |
| if cb.Start != nil { |
| cb.Start() |
| } |
| case event.ChangeOff: |
| if cb.Stop != nil { |
| cb.Stop() |
| } |
| } |
| case event.Config: |
| if cb.Config != nil { |
| cb.Config(e, c) |
| } |
| c = e |
| case event.Draw: |
| if cb.Draw != nil { |
| cb.Draw(c) |
| } |
| a.EndDraw() |
| case event.Touch: |
| if cb.Touch != nil { |
| cb.Touch(e, c) |
| } |
| } |
| } |
| return nil |
| }) |
| } |
| |
| // Callbacks is the set of functions called by the app. |
| // |
| // Deprecated: call Main directly instead. |
| type Callbacks struct { |
| // Start is called when the app enters the foreground. |
| // The app will start receiving Draw and Touch calls. |
| // |
| // If the app is responsible for the screen (that is, it is an |
| // all-Go app), then Window geometry will be configured and an |
| // OpenGL context will be available during Start. |
| // |
| // If this is a library, Start will be called before the |
| // app is told that Go has finished initialization. |
| // |
| // Start is an equivalent lifecycle state to onStart() on |
| // Android and applicationDidBecomeActive on iOS. |
| Start func() |
| |
| // Stop is called shortly before a program is suspended. |
| // |
| // When Stop is received, the app is no longer visible and is not |
| // receiving events. It should: |
| // |
| // - Save any state the user expects saved (for example text). |
| // - Release all resources that are not needed. |
| // |
| // Execution time in the stop state is limited, and the limit is |
| // enforced by the operating system. Stop as quickly as you can. |
| // |
| // An app that is stopped may be started again. For example, the user |
| // opens Recent Apps and switches to your app. A stopped app may also |
| // be terminated by the operating system with no further warning. |
| // |
| // Stop is equivalent to onStop() on Android and |
| // applicationDidEnterBackground on iOS. |
| Stop func() |
| |
| // Draw is called by the render loop to draw the screen. |
| // |
| // Drawing is done into a framebuffer, which is then swapped onto the |
| // screen when Draw returns. It is called 60 times a second. |
| Draw func(event.Config) |
| |
| // Touch is called by the app when a touch event occurs. |
| Touch func(event.Touch, event.Config) |
| |
| // Config is called by the app when configuration has changed. |
| Config func(new, old event.Config) |
| } |
| |
| // Open opens a named asset. |
| // |
| // On Android, assets are accessed via android.content.res.AssetManager. |
| // These files are stored in the assets/ directory of the app. Any raw asset |
| // can be accessed by its direct relative name. For example assets/img.png |
| // can be opened with Open("img.png"). |
| // |
| // On iOS an asset is a resource stored in the application bundle. |
| // Resources can be loaded using the same relative paths. |
| // |
| // For consistency when debugging on a desktop, assets are read from a |
| // directoy named assets under the current working directory. |
| func Open(name string) (ReadSeekCloser, error) { |
| return openAsset(name) |
| } |
| |
| // ReadSeekCloser is an io.ReadSeeker and io.Closer. |
| type ReadSeekCloser interface { |
| io.ReadSeeker |
| io.Closer |
| } |