blob: 26bc97c2e0486c1caac828634d20aa0a59affaa6 [file] [log] [blame]
// 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.
// Package event defines 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
// Other packages may define their own events, and post them onto an app's
// event channel.
//
// Other packages can also register event filters, e.g. to manage resources in
// response to lifecycle events. Such packages should call:
// event.RegisterFilter(etc)
// in an init function inside that package.
//
// The program code that consumes an app's events is expected to call
// event.Filter on every event they receive, and then switch on its type:
// for e := range a.Events() {
// switch e := event.Filter(e).(type) {
// etc
// }
// }
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.
func Filter(event interface{}) interface{} {
for _, f := range filters {
event = f(event)
}
return event
}
// RegisterFilter registers a 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)
}
// 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
)
// Touch is a user touch event.
//
// The same ID is shared by all events in a sequence. A sequence starts with a
// single TouchStart, is followed by zero or more TouchMoves, and ends with a
// single TouchEnd. An ID distinguishes concurrent sequences but is
// subsequently reused.
//
// On Android, Touch is an AInputEvent with AINPUT_EVENT_TYPE_MOTION.
// On iOS, Touch is the UIEvent delivered to a UIView.
type Touch struct {
ID TouchSequenceID
Type TouchType
Loc geom.Point
}
func (t Touch) String() string {
var ty string
switch t.Type {
case TouchStart:
ty = "start"
case TouchMove:
ty = "move "
case TouchEnd:
ty = "end "
}
return fmt.Sprintf("Touch{ %s, %s }", ty, t.Loc)
}
// TouchSequenceID identifies a sequence of Touch events.
type TouchSequenceID int64
// TODO: change TouchType to Change.
// TouchType describes the type of a touch event.
type TouchType byte
const (
// TouchStart is a user first touching the device.
//
// On Android, this is a AMOTION_EVENT_ACTION_DOWN.
// On iOS, this is a call to touchesBegan.
TouchStart TouchType = iota
// TouchMove is a user dragging across the device.
//
// A TouchMove is delivered between a TouchStart and TouchEnd.
//
// On Android, this is a AMOTION_EVENT_ACTION_MOVE.
// On iOS, this is a call to touchesMoved.
TouchMove
// TouchEnd 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.
TouchEnd
)