diff --git a/app/app.go b/app/app.go index 1215e16..512f81b 100644 --- a/app/app.go +++ b/app/app.go @@ -8,6 +8,10 @@ package app import ( "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/gl" _ "golang.org/x/mobile/internal/mobileinit" ) @@ -24,12 +28,12 @@ func Main(f func(App)) { 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. + // - config.Event + // - lifecycle.Event + // - paint.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. @@ -40,7 +44,7 @@ type App interface { } var ( - lifecycleStage = event.LifecycleStageDead + lifecycleStage = lifecycle.StageDead pixelsPerPt = float32(1) eventsOut = make(chan interface{}) @@ -48,11 +52,11 @@ var ( endDraw = make(chan struct{}, 1) ) -func sendLifecycle(to event.LifecycleStage) { +func sendLifecycle(to lifecycle.Stage) { if lifecycleStage == to { return } - eventsIn <- event.Lifecycle{ + eventsIn <- lifecycle.Event{ From: lifecycleStage, To: to, } @@ -157,31 +161,31 @@ func pump(dst chan interface{}) (src chan interface{}) { // Deprecated: call Main directly instead. func Run(cb Callbacks) { Main(func(a App) { - var c event.Config + var c config.Event for e := range a.Events() { switch e := event.Filter(e).(type) { - case event.Lifecycle: - switch e.Crosses(event.LifecycleStageVisible) { - case event.ChangeOn: + case lifecycle.Event: + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: if cb.Start != nil { cb.Start() } - case event.ChangeOff: + case lifecycle.CrossOff: if cb.Stop != nil { cb.Stop() } } - case event.Config: + case config.Event: if cb.Config != nil { cb.Config(e, c) } c = e - case event.Draw: + case paint.Event: if cb.Draw != nil { cb.Draw(c) } a.EndDraw() - case event.Touch: + case touch.Event: if cb.Touch != nil { cb.Touch(e, c) } @@ -231,13 +235,13 @@ type Callbacks struct { // // 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) + Draw func(config.Event) // Touch is called by the app when a touch event occurs. - Touch func(event.Touch, event.Config) + Touch func(touch.Event, config.Event) // Config is called by the app when configuration has changed. - Config func(new, old event.Config) + Config func(new, old config.Event) } // TODO: do this for all build targets, not just linux (x11 and Android)? If @@ -247,7 +251,7 @@ type Callbacks struct { // KitKat). If only x11 needs this, should we move this to x11.go?? func registerGLViewportFilter() { event.RegisterFilter(func(e interface{}) interface{} { - if e, ok := e.(event.Config); ok { + if e, ok := e.(config.Event); ok { w := int(e.PixelsPerPt * float32(e.Width)) h := int(e.PixelsPerPt * float32(e.Height)) gl.Viewport(0, 0, w, h) diff --git a/app/darwin_amd64.go b/app/darwin_amd64.go index 07ca59e..1da8c29 100644 --- a/app/darwin_amd64.go +++ b/app/darwin_amd64.go @@ -27,7 +27,10 @@ import ( "runtime" "sync" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" ) @@ -75,7 +78,7 @@ func loop(ctx C.GLintptr) { C.makeCurrentContext(ctx) for range draw { - eventsIn <- event.Draw{} + eventsIn <- paint.Event{} loop1: for { select { @@ -112,7 +115,7 @@ var windowHeight geom.Pt func setGeom(ppp float32, width, height int) { pixelsPerPt = ppp windowHeight = geom.Pt(float32(height) / pixelsPerPt) - eventsIn <- event.Config{ + eventsIn <- config.Event{ Width: geom.Pt(float32(width) / pixelsPerPt), Height: windowHeight, PixelsPerPt: pixelsPerPt, @@ -121,13 +124,13 @@ func setGeom(ppp float32, width, height int) { var touchEvents struct { sync.Mutex - pending []event.Touch + pending []touch.Event } -func sendTouch(c event.Change, x, y float32) { - eventsIn <- event.Touch{ - ID: 0, - Change: c, +func sendTouch(t touch.Type, x, y float32) { + eventsIn <- touch.Event{ + Sequence: 0, + Type: t, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: windowHeight - geom.Pt(y/pixelsPerPt), @@ -136,22 +139,22 @@ func sendTouch(c event.Change, x, y float32) { } //export eventMouseDown -func eventMouseDown(x, y float32) { sendTouch(event.ChangeOn, x, y) } +func eventMouseDown(x, y float32) { sendTouch(touch.TypeStart, x, y) } //export eventMouseDragged -func eventMouseDragged(x, y float32) { sendTouch(event.ChangeNone, x, y) } +func eventMouseDragged(x, y float32) { sendTouch(touch.TypeMove, x, y) } //export eventMouseEnd -func eventMouseEnd(x, y float32) { sendTouch(event.ChangeOff, x, y) } +func eventMouseEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } //export lifecycleDead -func lifecycleDead() { sendLifecycle(event.LifecycleStageDead) } +func lifecycleDead() { sendLifecycle(lifecycle.StageDead) } //export lifecycleAlive -func lifecycleAlive() { sendLifecycle(event.LifecycleStageAlive) } +func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) } //export lifecycleVisible -func lifecycleVisible() { sendLifecycle(event.LifecycleStageVisible) } +func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) } //export lifecycleFocused -func lifecycleFocused() { sendLifecycle(event.LifecycleStageFocused) } +func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) } diff --git a/app/darwin_armx.go b/app/darwin_armx.go index 339ddd7..f666563 100644 --- a/app/darwin_armx.go +++ b/app/darwin_armx.go @@ -28,7 +28,10 @@ import ( "sync" "unsafe" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" ) @@ -95,7 +98,7 @@ func setScreen(scale int) { //export updateConfig func updateConfig(width, height int) { - eventsIn <- event.Config{ + eventsIn <- config.Event{ Width: geom.Pt(float32(screenScale*width) / pixelsPerPt), Height: geom.Pt(float32(screenScale*height) / pixelsPerPt), PixelsPerPt: pixelsPerPt, @@ -113,11 +116,11 @@ var touchIDs [11]uintptr var touchEvents struct { sync.Mutex - pending []event.Touch + pending []touch.Event } //export sendTouch -func sendTouch(touch, change uintptr, x, y float32) { +func sendTouch(touch, touchType uintptr, x, y float32) { id := -1 for i, val := range touchIDs { if val == touch { @@ -138,14 +141,14 @@ func sendTouch(touch, change uintptr, x, y float32) { } } - c := event.Change(change) - if c == event.ChangeOff { + t := touch.Type(touchType) + if t == touch.TypeEnd { touchIDs[id] = 0 } - eventsIn <- event.Touch{ - ID: event.TouchSequenceID(id), - Change: c, + eventsIn <- touch.Event{ + Sequence: touch.Sequence(id), + Type: t, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: geom.Pt(y / pixelsPerPt), @@ -159,10 +162,10 @@ func drawgl(ctx uintptr) { startedgl = true C.setContext(unsafe.Pointer(ctx)) // TODO(crawshaw): not just on process start. - sendLifecycle(event.LifecycleStageFocused) + sendLifecycle(lifecycle.StageFocused) } - eventsIn <- event.Draw{} + eventsIn <- paint.Event{} for { select { diff --git a/app/darwin_armx.m b/app/darwin_armx.m index c4da341..ae3b68e 100644 --- a/app/darwin_armx.m +++ b/app/darwin_armx.m @@ -65,9 +65,9 @@ struct utsname sysInfo; drawgl((GoUintptr)self.context); } -#define CHANGE_NONE 0 // event.ChangeNone -#define CHANGE_ON 1 // event.ChangeOn -#define CHANGE_OFF 2 // event.ChangeOff +#define TOUCH_TYPE_START 0 // touch.TypeStart +#define TOUCH_TYPE_MOVE 1 // touch.TypeMove +#define TOUCH_TYPE_END 2 // touch.TypeEnd static void sendTouches(int change, NSSet* touches) { CGFloat scale = [UIScreen mainScreen].scale; @@ -78,15 +78,15 @@ static void sendTouches(int change, NSSet* touches) { } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - sendTouches(CHANGE_ON, touches); + sendTouches(TOUCH_TYPE_START, touches); } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - sendTouches(CHANGE_NONE, touches); + sendTouches(TOUCH_TYPE_MOVE, touches); } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - sendTouches(CHANGE_OFF, touches); + sendTouches(TOUCH_TYPE_END, touches); } @end diff --git a/app/doc.go b/app/doc.go index ee1c1e7..b2ae070 100644 --- a/app/doc.go +++ b/app/doc.go @@ -42,15 +42,18 @@ function exits, the app exits. "log" "golang.org/x/mobile/app" + "golang.org/x/mobile/event" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" ) func main() { app.Main(func(a app.App) { for e := range a.Events() { switch e := event.Filter(e).(type) { - case event.Lifecycle: + case lifecycle.Event: // ... - case event.Draw: + case paint.Event: log.Print("Call OpenGL here.") a.EndDraw() } diff --git a/app/loop_android.go b/app/loop_android.go index c44b403..de0f34d 100644 --- a/app/loop_android.go +++ b/app/loop_android.go @@ -84,7 +84,10 @@ import "C" import ( "log" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" ) @@ -95,16 +98,16 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) ( C.createEGLWindow(w) // TODO: is this needed if we also have the "case <-windowRedrawNeeded:" below?? - sendLifecycle(event.LifecycleStageFocused) - eventsIn <- event.Config{ + sendLifecycle(lifecycle.StageFocused) + eventsIn <- config.Event{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } if firstWindowDraw { firstWindowDraw = false - // TODO: be more principled about when to send a draw event. - eventsIn <- event.Draw{} + // TODO: be more principled about when to send a paint event. + eventsIn <- paint.Event{} } for { @@ -115,21 +118,21 @@ func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) ( case <-windowRedrawNeeded: // Re-query the width and height. C.querySurfaceWidthAndHeight() - sendLifecycle(event.LifecycleStageFocused) - eventsIn <- event.Config{ + sendLifecycle(lifecycle.StageFocused) + eventsIn <- config.Event{ Width: geom.Pt(float32(C.windowWidth) / pixelsPerPt), Height: geom.Pt(float32(C.windowHeight) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } case <-windowDestroyed: - sendLifecycle(event.LifecycleStageAlive) + sendLifecycle(lifecycle.StageAlive) return false case <-gl.WorkAvailable: gl.DoWork() case <-endDraw: // eglSwapBuffers blocks until vsync. C.eglSwapBuffers(C.display, C.surface) - eventsIn <- event.Draw{} + eventsIn <- paint.Event{} } } } @@ -152,22 +155,22 @@ func processEvent(e *C.AInputEvent) { case C.AINPUT_EVENT_TYPE_MOTION: // At most one of the events in this batch is an up or down event; get its index and change. upDownIndex := C.size_t(C.AMotionEvent_getAction(e)&C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT - upDownChange := event.ChangeNone + upDownType := touch.TypeMove switch C.AMotionEvent_getAction(e) & C.AMOTION_EVENT_ACTION_MASK { case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: - upDownChange = event.ChangeOn + upDownType = touch.TypeStart case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: - upDownChange = event.ChangeOff + upDownType = touch.TypeEnd } for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ { - change := event.ChangeNone + t := touch.TypeMove if i == upDownIndex { - change = upDownChange + t = upDownType } - eventsIn <- event.Touch{ - ID: event.TouchSequenceID(C.AMotionEvent_getPointerId(e, i)), - Change: change, + eventsIn <- touch.Event{ + Sequence: touch.Sequence(C.AMotionEvent_getPointerId(e, i)), + Type: t, Loc: geom.Point{ X: geom.Pt(float32(C.AMotionEvent_getX(e, i)) / pixelsPerPt), Y: geom.Pt(float32(C.AMotionEvent_getY(e, i)) / pixelsPerPt), diff --git a/app/x11.go b/app/x11.go index a6a3916..089329e 100644 --- a/app/x11.go +++ b/app/x11.go @@ -27,7 +27,10 @@ import ( "runtime" "time" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" + "golang.org/x/mobile/event/paint" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" ) @@ -41,7 +44,7 @@ func main(f func(App)) { C.createWindow() // TODO: send lifecycle events when e.g. the X11 window is iconified or moved off-screen. - sendLifecycle(event.LifecycleStageFocused) + sendLifecycle(lifecycle.StageFocused) donec := make(chan struct{}) go func() { @@ -65,7 +68,7 @@ func main(f func(App)) { tc = ticker.C case <-tc: tc = nil - eventsIn <- event.Draw{} + eventsIn <- paint.Event{} } C.processEvents() } @@ -76,17 +79,17 @@ func onResize(w, h int) { // TODO(nigeltao): don't assume 72 DPI. DisplayWidth and DisplayWidthMM // is probably the best place to start looking. pixelsPerPt = 1 - eventsIn <- event.Config{ + eventsIn <- config.Event{ Width: geom.Pt(w), Height: geom.Pt(h), PixelsPerPt: pixelsPerPt, } } -func sendTouch(c event.Change, x, y float32) { - eventsIn <- event.Touch{ - ID: 0, // TODO: button?? - Change: c, +func sendTouch(t touch.Type, x, y float32) { + eventsIn <- touch.Event{ + Sequence: 0, // TODO: button?? + Type: t, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: geom.Pt(y / pixelsPerPt), @@ -95,13 +98,13 @@ func sendTouch(c event.Change, x, y float32) { } //export onTouchStart -func onTouchStart(x, y float32) { sendTouch(event.ChangeOn, x, y) } +func onTouchStart(x, y float32) { sendTouch(touch.TypeStart, x, y) } //export onTouchMove -func onTouchMove(x, y float32) { sendTouch(event.ChangeNone, x, y) } +func onTouchMove(x, y float32) { sendTouch(touch.TypeMove, x, y) } //export onTouchEnd -func onTouchEnd(x, y float32) { sendTouch(event.ChangeOff, x, y) } +func onTouchEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } var stopped bool @@ -111,6 +114,6 @@ func onStop() { return } stopped = true - sendLifecycle(event.LifecycleStageDead) + sendLifecycle(lifecycle.StageDead) eventsIn <- stopPumping{} } diff --git a/event/config/config.go b/event/config/config.go new file mode 100644 index 0000000..c8ba189 --- /dev/null +++ b/event/config/config.go @@ -0,0 +1,27 @@ +// Copyright 2015 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. + +// Package config defines an event for the dimensions and physical resolution +// of the app's window. +// +// See the golang.org/x/mobile/event package for details on the event model. +package config // import "golang.org/x/mobile/event/config" + +import ( + "golang.org/x/mobile/geom" +) + +// Event holds the dimensions and physical resolution of the app's window. +type Event struct { + // Width and Height are the window's dimensions. + Width, Height geom.Pt + + // PixelsPerPt is the window's physical resolution. It is the number of + // pixels in a single geom.Pt, from the golang.org/x/mobile/geom package. + // + // There are a wide variety of pixel densities in existing phones and + // tablets, so apps should be written to expect various non-integer + // PixelsPerPt values. In general, work in geom.Pt. + PixelsPerPt float32 +} diff --git a/event/event.go b/event/event.go index 8da763a..b2f7fce 100644 --- a/event/event.go +++ b/event/event.go @@ -2,15 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package event defines mobile app events, such as user input events. +// Package event defines a model for mobile app events, such as user input +// events. // // An event is represented by the empty interface type interface{}. Any value -// can be an event. This package defines a number of commonly used events used -// by the golang.org/x/mobile/app package: -// - Config -// - Draw -// - Lifecycle -// - Touch +// can be an event. Packages under this directory define a number of commonly +// used events used by the golang.org/x/mobile/app package: +// - golang.org/x/mobile/event/config.Event +// - golang.org/x/mobile/event/lifecycle.Event +// - golang.org/x/mobile/event/paint.Event +// - golang.org/x/mobile/event/touch.Event // Other packages may define their own events, and post them onto an app's // event channel. // @@ -28,19 +29,6 @@ // } package event // import "golang.org/x/mobile/event" -// The best source on android input events is the NDK: include/android/input.h -// -// iOS event handling guide: -// https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS - -// TODO: keyboard events. - -import ( - "fmt" - - "golang.org/x/mobile/geom" -) - var filters []func(interface{}) interface{} // Filter calls each registered filter function in sequence. @@ -60,148 +48,3 @@ func Filter(event interface{}) interface{} { func RegisterFilter(f func(interface{}) interface{}) { filters = append(filters, f) } - -// Change is a change in state, such as key or mouse button being up (off) or -// down (on). Some events with a Change-typed field can have no change in -// state, such as a key repeat or a mouse or touch drag. -type Change uint32 - -func (c Change) String() string { - switch c { - case ChangeOn: - return "on" - case ChangeOff: - return "off" - } - return "none" -} - -const ( - ChangeNone Change = 0 - ChangeOn Change = 1 - ChangeOff Change = 2 -) - -// Config holds the dimensions and physical resolution of the app's window. -type Config struct { - // Width and Height are the window's dimensions. - Width, Height geom.Pt - - // PixelsPerPt is the window's physical resolution. It is the number of - // pixels in a single geom.Pt, from the golang.org/x/mobile/geom package. - // - // There are a wide variety of pixel densities in existing phones and - // tablets, so apps should be written to expect various non-integer - // PixelsPerPt values. In general, work in geom.Pt. - PixelsPerPt float32 -} - -// Draw indicates that the app is ready to draw the next frame of the GUI. A -// frame is completed by calling the App's EndDraw method. -type Draw struct{} - -// Lifecycle is a lifecycle change from an old stage to a new stage. -type Lifecycle struct { - From, To LifecycleStage -} - -// Crosses returns whether the transition from From to To crosses the stage s: -// - It returns ChangeOn if it does, and the Lifecycle change is positive. -// - It returns ChangeOff if it does, and the Lifecycle change is negative. -// - Otherwise, it returns ChangeNone. -// See the documentation for LifecycleStage for more discussion of positive and -// negative changes, and crosses. -func (l Lifecycle) Crosses(s LifecycleStage) Change { - switch { - case l.From < s && l.To >= s: - return ChangeOn - case l.From >= s && l.To < s: - return ChangeOff - } - return ChangeNone -} - -// LifecycleStage is a stage in the app's lifecycle. The values are ordered, so -// that a lifecycle change from stage From to stage To implicitly crosses every -// stage in the range (min, max], exclusive on the low end and inclusive on the -// high end, where min is the minimum of From and To, and max is the maximum. -// -// The documentation for individual stages talk about positive and negative -// crosses. A positive Lifecycle change is one where its From stage is less -// than its To stage. Similarly, a negative Lifecycle change is one where From -// is greater than To. Thus, a positive Lifecycle change crosses every stage in -// the range (From, To] in increasing order, and a negative Lifecycle change -// crosses every stage in the range (To, From] in decreasing order. -type LifecycleStage uint32 - -// TODO: how does iOS map to these stages? What do cross-platform mobile -// abstractions do? - -const ( - // LifecycleStageDead is the zero stage. No Lifecycle change crosses this - // stage, but: - // - A positive change from this stage is the very first lifecycle change. - // - A negative change to this stage is the very last lifecycle change. - LifecycleStageDead LifecycleStage = iota - - // LifecycleStageAlive means that the app is alive. - // - A positive cross means that the app has been created. - // - A negative cross means that the app is being destroyed. - // Each cross, either from or to LifecycleStageDead, will occur only once. - // On Android, these correspond to onCreate and onDestroy. - LifecycleStageAlive - - // LifecycleStageVisible means that the app window is visible. - // - A positive cross means that the app window has become visible. - // - A negative cross means that the app window has become invisible. - // On Android, these correspond to onStart and onStop. - // On Desktop, an app window can become invisible if e.g. it is minimized, - // unmapped, or not on a visible workspace. - LifecycleStageVisible - - // LifecycleStageFocused means that the app window has the focus. - // - A positive cross means that the app window has gained the focus. - // - A negative cross means that the app window has lost the focus. - // On Android, these correspond to onResume and onFreeze. - LifecycleStageFocused -) - -func (l LifecycleStage) String() string { - switch l { - case LifecycleStageDead: - return "LifecycleStageDead" - case LifecycleStageAlive: - return "LifecycleStageAlive" - case LifecycleStageVisible: - return "LifecycleStageVisible" - case LifecycleStageFocused: - return "LifecycleStageFocused" - default: - return fmt.Sprintf("LifecycleStageInvalid:%d", l) - } -} - -// On Android, Touch is an AInputEvent with AINPUT_EVENT_TYPE_MOTION: -// - ChangeOn is an AMOTION_EVENT_ACTION_DOWN. -// - ChangeNone is an AMOTION_EVENT_ACTION_MOVE. -// - ChangeOff is an AMOTION_EVENT_ACTION_UP. -// -// On iOS, Touch is the UIEvent delivered to a UIView: -// - ChangeOn is a call to touchesBegan. -// - ChangeNone is a call to touchesMoved. -// - ChangeOff is a call to touchesEnded. - -// Touch is a user touch event. -// -// The same ID is shared by all events in a sequence. A sequence starts with a -// single ChangeOn (a touch start), is followed by zero or more ChangeNones -// (touch moves), and ends with a single ChangeOff (a touch end). An ID -// distinguishes concurrent sequences but is subsequently reused. -type Touch struct { - ID TouchSequenceID - Change Change - Loc geom.Point -} - -// TouchSequenceID identifies a sequence of Touch events. -type TouchSequenceID int64 diff --git a/event/lifecycle/lifecycle.go b/event/lifecycle/lifecycle.go new file mode 100644 index 0000000..c5a9065 --- /dev/null +++ b/event/lifecycle/lifecycle.go @@ -0,0 +1,124 @@ +// Copyright 2015 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. + +// Package lifecycle defines an event for an app's lifecycle. +// +// The app lifecycle consists of moving back and forth between an ordered +// sequence of stages. For example, being at a stage greater than or equal to +// StageVisible means that the app is visible on the screen. +// +// A lifecycle event is a change from one stage to another, which crosses every +// intermediate stage. For example, changing from StageAlive to StageFocused +// implicitly crosses StageVisible. +// +// Crosses can be in a positive or negative direction. A positive crossing of +// StageFocused means that the app has gained the focus. A negative crossing +// means it has lost the focus. +// +// See the golang.org/x/mobile/event package for details on the event model. +package lifecycle // import "golang.org/x/mobile/event/lifecycle" + +import ( + "fmt" +) + +// Cross is whether a lifecycle stage was crossed. +type Cross uint32 + +func (c Cross) String() string { + switch c { + case CrossOn: + return "on" + case CrossOff: + return "off" + } + return "none" +} + +const ( + CrossNone Cross = 0 + CrossOn Cross = 1 + CrossOff Cross = 2 +) + +// Event is a lifecycle change from an old stage to a new stage. +type Event struct { + From, To Stage +} + +// Crosses returns whether the transition from From to To crosses the stage s: +// - It returns CrossOn if it does, and the lifecycle change is positive. +// - It returns CrossOff if it does, and the lifecycle change is negative. +// - Otherwise, it returns CrossNone. +// See the documentation for Stage for more discussion of positive and negative +// crosses. +func (e Event) Crosses(s Stage) Cross { + switch { + case e.From < s && e.To >= s: + return CrossOn + case e.From >= s && e.To < s: + return CrossOff + } + return CrossNone +} + +// Stage is a stage in the app's lifecycle. The values are ordered, so that a +// lifecycle change from stage From to stage To implicitly crosses every stage +// in the range (min, max], exclusive on the low end and inclusive on the high +// end, where min is the minimum of From and To, and max is the maximum. +// +// The documentation for individual stages talk about positive and negative +// crosses. A positive lifecycle change is one where its From stage is less +// than its To stage. Similarly, a negative lifecycle change is one where From +// is greater than To. Thus, a positive lifecycle change crosses every stage in +// the range (From, To] in increasing order, and a negative lifecycle change +// crosses every stage in the range (To, From] in decreasing order. +type Stage uint32 + +// TODO: how does iOS map to these stages? What do cross-platform mobile +// abstractions do? + +const ( + // StageDead is the zero stage. No lifecycle change crosses this stage, + // but: + // - A positive change from this stage is the very first lifecycle change. + // - A negative change to this stage is the very last lifecycle change. + StageDead Stage = iota + + // StageAlive means that the app is alive. + // - A positive cross means that the app has been created. + // - A negative cross means that the app is being destroyed. + // Each cross, either from or to StageDead, will occur only once. + // On Android, these correspond to onCreate and onDestroy. + StageAlive + + // StageVisible means that the app window is visible. + // - A positive cross means that the app window has become visible. + // - A negative cross means that the app window has become invisible. + // On Android, these correspond to onStart and onStop. + // On Desktop, an app window can become invisible if e.g. it is minimized, + // unmapped, or not on a visible workspace. + StageVisible + + // StageFocused means that the app window has the focus. + // - A positive cross means that the app window has gained the focus. + // - A negative cross means that the app window has lost the focus. + // On Android, these correspond to onResume and onFreeze. + StageFocused +) + +func (s Stage) String() string { + switch s { + case StageDead: + return "StageDead" + case StageAlive: + return "StageAlive" + case StageVisible: + return "StageVisible" + case StageFocused: + return "StageFocused" + default: + return fmt.Sprintf("lifecycle.Stage(%d)", s) + } +} diff --git a/event/paint/paint.go b/event/paint/paint.go new file mode 100644 index 0000000..532fc49 --- /dev/null +++ b/event/paint/paint.go @@ -0,0 +1,17 @@ +// Copyright 2015 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. + +// Package paint defines an event for the app being ready to paint. +// +// See the golang.org/x/mobile/event package for details on the event model. +package paint // import "golang.org/x/mobile/event/paint" + +// TODO: rename App.EndDraw to App.EndPaint. +// +// This is "package paint", not "package draw", to avoid conflicting with the +// standard library's "image/draw" package. + +// Event indicates that the app is ready to paint the next frame of the GUI. A +// frame is completed by calling the App's EndDraw method. +type Event struct{} diff --git a/event/touch/touch.go b/event/touch/touch.go new file mode 100644 index 0000000..d7f79e6 --- /dev/null +++ b/event/touch/touch.go @@ -0,0 +1,71 @@ +// Copyright 2015 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. + +// Package touch defines an event for touch input. +// +// See the golang.org/x/mobile/event package for details on the event model. +package touch // import "golang.org/x/mobile/event/touch" + +// The best source on android input events is the NDK: include/android/input.h +// +// iOS event handling guide: +// https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS + +import ( + "fmt" + + "golang.org/x/mobile/geom" +) + +// Event is a touch event. +// +// The same Sequence is shared by all events in a sequence. A sequence starts +// with a single TypeStart, is followed by zero or more TypeMoves, and ends +// with a single TypeEnd. A Sequence distinguishes concurrent sequences but its +// value is subsequently reused. +type Event struct { + Sequence Sequence + Type Type + Loc geom.Point +} + +// Sequence identifies a sequence of touch events. +type Sequence int64 + +// Type describes the type of a touch event. +type Type byte + +const ( + // TypeStart is a user first touching the device. + // + // On Android, this is a AMOTION_EVENT_ACTION_DOWN. + // On iOS, this is a call to touchesBegan. + TypeStart Type = iota + + // TypeMove is a user dragging across the device. + // + // A TypeMove is delivered between a TypeStart and TypeEnd. + // + // On Android, this is a AMOTION_EVENT_ACTION_MOVE. + // On iOS, this is a call to touchesMoved. + TypeMove + + // TypeEnd is a user no longer touching the device. + // + // On Android, this is a AMOTION_EVENT_ACTION_UP. + // On iOS, this is a call to touchesEnded. + TypeEnd +) + +func (t Type) String() string { + switch t { + case TypeStart: + return "start" + case TypeMove: + return "move" + case TypeEnd: + return "end" + } + return fmt.Sprintf("touch.Type(%d)", t) +} diff --git a/example/audio/main.go b/example/audio/main.go index e5e3ec4..614e4c6 100644 --- a/example/audio/main.go +++ b/example/audio/main.go @@ -39,7 +39,7 @@ import ( "golang.org/x/mobile/app" "golang.org/x/mobile/asset" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/app/debug" "golang.org/x/mobile/exp/audio" "golang.org/x/mobile/exp/f32" @@ -65,13 +65,13 @@ var ( func main() { app.Run(app.Callbacks{ - Start: start, - Stop: stop, - Draw: draw, + Start: onStart, + Stop: onStop, + Draw: onDraw, }) } -func start() { +func onStart() { rc, err := asset.Open("boing.wav") if err != nil { log.Fatal(err) @@ -82,11 +82,11 @@ func start() { } } -func stop() { +func onStop() { player.Close() } -func draw(c event.Config) { +func onDraw(c config.Event) { if scene == nil { loadScene(c) } @@ -104,7 +104,7 @@ func newNode() *sprite.Node { return n } -func loadScene(c event.Config) { +func loadScene(c config.Event) { gopher := loadGopher() scene = &sprite.Node{} eng.Register(scene) diff --git a/example/basic/main.go b/example/basic/main.go index ca52e86..13c2678 100644 --- a/example/basic/main.go +++ b/example/basic/main.go @@ -31,7 +31,8 @@ import ( "log" "golang.org/x/mobile/app" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/touch" "golang.org/x/mobile/exp/app/debug" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/gl/glutil" @@ -52,15 +53,15 @@ var ( func main() { app.Run(app.Callbacks{ - Start: start, - Stop: stop, - Draw: draw, - Touch: touch, - Config: config, + Start: onStart, + Stop: onStop, + Draw: onDraw, + Touch: onTouch, + Config: onConfig, }) } -func start() { +func onStart() { var err error program, err = glutil.CreateProgram(vertexShader, fragmentShader) if err != nil { @@ -80,20 +81,20 @@ func start() { // Can this be an event.Register call now?? } -func stop() { +func onStop() { gl.DeleteProgram(program) gl.DeleteBuffer(buf) } -func config(new, old event.Config) { +func onConfig(new, old config.Event) { touchLoc = geom.Point{new.Width / 2, new.Height / 2} } -func touch(t event.Touch, c event.Config) { +func onTouch(t touch.Event, c config.Event) { touchLoc = t.Loc } -func draw(c event.Config) { +func onDraw(c config.Event) { gl.ClearColor(1, 0, 0, 1) gl.Clear(gl.COLOR_BUFFER_BIT) diff --git a/example/network/main.go b/example/network/main.go index 7dda8d9..287f628 100644 --- a/example/network/main.go +++ b/example/network/main.go @@ -41,7 +41,7 @@ import ( "net/http" "golang.org/x/mobile/app" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/app/debug" "golang.org/x/mobile/gl" ) @@ -51,7 +51,7 @@ func main() { go checkNetwork() app.Run(app.Callbacks{ - Draw: draw, + Draw: onDraw, }) } @@ -70,7 +70,7 @@ func checkNetwork() { ok = true } -func draw(c event.Config) { +func onDraw(c config.Event) { select { case <-determined: if ok { diff --git a/example/sprite/main.go b/example/sprite/main.go index 1ff14dc..a9f2fe8 100644 --- a/example/sprite/main.go +++ b/example/sprite/main.go @@ -36,7 +36,7 @@ import ( "golang.org/x/mobile/app" "golang.org/x/mobile/asset" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/app/debug" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/sprite" @@ -53,11 +53,11 @@ var ( func main() { app.Run(app.Callbacks{ - Draw: draw, + Draw: onDraw, }) } -func draw(c event.Config) { +func onDraw(c config.Event) { if scene == nil { loadScene() } diff --git a/exp/app/debug/fps.go b/exp/app/debug/fps.go index e25014a..9ec5166 100644 --- a/exp/app/debug/fps.go +++ b/exp/app/debug/fps.go @@ -12,7 +12,7 @@ import ( "sync" "time" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/gl/glutil" "golang.org/x/mobile/geom" ) @@ -21,12 +21,12 @@ var lastDraw = time.Now() var fps struct { mu sync.Mutex - c event.Config + c config.Event m *glutil.Image } // DrawFPS draws the per second framerate in the bottom-left of the screen. -func DrawFPS(c event.Config) { +func DrawFPS(c config.Event) { const imgW, imgH = 7*(fontWidth+1) + 1, fontHeight + 2 fps.mu.Lock() diff --git a/exp/gl/glutil/glimage.go b/exp/gl/glutil/glimage.go index d7756e4..cfc6f99 100644 --- a/exp/gl/glutil/glimage.go +++ b/exp/gl/glutil/glimage.go @@ -14,6 +14,8 @@ import ( "sync" "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" + "golang.org/x/mobile/event/lifecycle" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" @@ -32,11 +34,11 @@ var glimage struct { func init() { event.RegisterFilter(func(e interface{}) interface{} { - if e, ok := e.(event.Lifecycle); ok { - switch e.Crosses(event.LifecycleStageVisible) { - case event.ChangeOn: + if e, ok := e.(lifecycle.Event); ok { + switch e.Crosses(lifecycle.StageVisible) { + case lifecycle.CrossOn: start() - case event.ChangeOff: + case lifecycle.CrossOff: stop() } } @@ -223,7 +225,7 @@ func (img *Image) Delete() { // Draw draws the srcBounds part of the image onto a parallelogram, defined by // three of its corners, in the current GL framebuffer. -func (img *Image) Draw(c event.Config, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { +func (img *Image) Draw(c config.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) { // TODO(crawshaw): Adjust viewport for the top bar on android? gl.UseProgram(glimage.program) tex := texmap.get(*img.key) diff --git a/exp/gl/glutil/glimage_test.go b/exp/gl/glutil/glimage_test.go index 582f1d8..cfce3f8 100644 --- a/exp/gl/glutil/glimage_test.go +++ b/exp/gl/glutil/glimage_test.go @@ -18,7 +18,7 @@ import ( "runtime" "testing" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/geom" "golang.org/x/mobile/gl" ) @@ -65,7 +65,7 @@ func TestImage(t *testing.T) { ptW = geom.Pt(50) ptH = geom.Pt(50) ) - cfg := event.Config{ + cfg := config.Event{ Width: ptW, Height: ptH, PixelsPerPt: float32(pixW) / float32(ptW), diff --git a/exp/sprite/glsprite/glsprite.go b/exp/sprite/glsprite/glsprite.go index b8f023b..e1bc4b6 100644 --- a/exp/sprite/glsprite/glsprite.go +++ b/exp/sprite/glsprite/glsprite.go @@ -12,7 +12,7 @@ import ( "image" "image/draw" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/gl/glutil" "golang.org/x/mobile/exp/sprite" @@ -91,7 +91,7 @@ func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) { e.nodes[n.EngineFields.Index].relTransform = m } -func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg event.Config) { +func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg config.Event) { e.absTransforms = append(e.absTransforms[:0], f32.Affine{ {1, 0, 0}, {0, 1, 0}, @@ -99,7 +99,7 @@ func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg event.Config) { e.render(scene, t, cfg) } -func (e *engine) render(n *sprite.Node, t clock.Time, cfg event.Config) { +func (e *engine) render(n *sprite.Node, t clock.Time, cfg config.Event) { if n.EngineFields.Index == 0 { panic("glsprite: sprite.Node not registered") } diff --git a/exp/sprite/portable/affine_test.go b/exp/sprite/portable/affine_test.go index 490e19c..74f0de2 100644 --- a/exp/sprite/portable/affine_test.go +++ b/exp/sprite/portable/affine_test.go @@ -14,7 +14,7 @@ import ( "os" "testing" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/geom" ) @@ -38,7 +38,7 @@ func TestAffine(t *testing.T) { ptW = geom.Pt(50) ptH = geom.Pt(50) ) - cfg := event.Config{ + cfg := config.Event{ Width: ptW, Height: ptH, PixelsPerPt: float32(pixW) / float32(ptW), diff --git a/exp/sprite/portable/portable.go b/exp/sprite/portable/portable.go index 02675dc..162ef88 100644 --- a/exp/sprite/portable/portable.go +++ b/exp/sprite/portable/portable.go @@ -13,7 +13,7 @@ import ( "image" "image/draw" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/sprite" "golang.org/x/mobile/exp/sprite/clock" @@ -92,7 +92,7 @@ func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) { e.nodes[n.EngineFields.Index].relTransform = m } -func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg event.Config) { +func (e *engine) Render(scene *sprite.Node, t clock.Time, cfg config.Event) { // Affine transforms are done in geom.Pt. When finally drawing // the geom.Pt onto an image.Image we need to convert to system // pixels. We scale by cfg.PixelsPerPt to do this. diff --git a/exp/sprite/sprite.go b/exp/sprite/sprite.go index 1c96e5b..3028a75 100644 --- a/exp/sprite/sprite.go +++ b/exp/sprite/sprite.go @@ -28,7 +28,7 @@ import ( "image" "image/draw" - "golang.org/x/mobile/event" + "golang.org/x/mobile/event/config" "golang.org/x/mobile/exp/f32" "golang.org/x/mobile/exp/sprite/clock" ) @@ -60,7 +60,7 @@ type Engine interface { // Render renders the scene arranged at the given time, for the given // window configuration (dimensions and resolution). - Render(scene *Node, t clock.Time, c event.Config) + Render(scene *Node, t clock.Time, c config.Event) } // A Node is a renderable element and forms a tree of Nodes.