| // 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 ( |
| "golang.org/x/mobile/event/lifecycle" |
| "golang.org/x/mobile/event/paint" |
| "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{}) |
| |
| // EndPaint flushes any pending OpenGL commands or buffers to the screen. |
| // If EndPaint is called with an old generation number, it is ignored. |
| EndPaint(paint.Event) |
| } |
| |
| var ( |
| lifecycleStage = lifecycle.StageDead |
| |
| eventsOut = make(chan interface{}) |
| eventsIn = pump(eventsOut) |
| endPaint = make(chan paint.Event, 1) |
| ) |
| |
| func sendLifecycle(to lifecycle.Stage) { |
| if lifecycleStage == to { |
| return |
| } |
| eventsIn <- lifecycle.Event{ |
| 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) EndPaint(e paint.Event) { |
| // 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. |
| gl.Flush() |
| endPaint <- e |
| } |
| |
| var filters []func(interface{}) interface{} |
| |
| // Filter calls each registered event filter function in sequence. |
| func Filter(event interface{}) interface{} { |
| for _, f := range filters { |
| event = f(event) |
| } |
| return event |
| } |
| |
| // 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 should only be called from init functions. |
| func RegisterFilter(f func(interface{}) interface{}) { |
| filters = append(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 |
| |
| 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 |
| } |
| |
| // 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 registerGLViewportFilter() { |
| RegisterFilter(func(e interface{}) interface{} { |
| if e, ok := e.(size.Event); ok { |
| gl.Viewport(0, 0, e.WidthPx, e.HeightPx) |
| } |
| return e |
| }) |
| } |