//export updateConfig func updateConfig(width, height int) { eventsIn <- config.Event{ Width: geom.Pt(float32(screenScale*width) / pixelsPerPt), Height: geom.Pt(float32(screenScale*height) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } }
//export onResize func onResize(w, h int) { // TODO(nigeltao): don't assume 72 DPI. DisplayWidth / DisplayWidthMM // is probably the best place to start looking. geom.PixelsPerPt = 1 geom.Width = geom.Pt(w) geom.Height = geom.Pt(h) }
func processEvent(e *C.AInputEvent) { switch C.AInputEvent_getType(e) { case C.AINPUT_EVENT_TYPE_KEY: log.Printf("TODO input event: key") 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 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: upDownType = touch.TypeBegin case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: upDownType = touch.TypeEnd } for i, n := C.size_t(0), C.AMotionEvent_getPointerCount(e); i < n; i++ { t := touch.TypeMove if i == upDownIndex { t = upDownType } 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), }, } } default: log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e)) } }
//export onResize 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{ Width: geom.Pt(w), Height: geom.Pt(h), PixelsPerPt: pixelsPerPt, } // This gl.Viewport call has to be in a separate goroutine because any gl // call can block until gl.DoWork is called, but this goroutine is the one // responsible for calling gl.DoWork. // TODO: does this (GL-using) code belong here in the x/mobile/app // package?? See similar TODOs in the Android x/mobile/app implementation. c := make(chan struct{}) go func() { gl.Viewport(0, 0, w, h) close(c) }() for { select { case <-gl.WorkAvailable: gl.DoWork() case <-c: return } } }
func main() { e := Engine{} app.Main(func(a app.App) { var c size.Event for eve := range a.Events() { switch eve := app.Filter(eve).(type) { case lifecycle.Event: switch eve.Crosses(lifecycle.StageVisible) { case lifecycle.CrossOn: e.Start() case lifecycle.CrossOff: e.Stop() } case size.Event: c = eve e.touchLoc = geom.Point{c.WidthPt / 2, c.HeightPt / 2} case paint.Event: e.Draw(c) a.EndPaint(eve) case touch.Event: e.touchLoc = geom.Point{geom.Pt(eve.X), geom.Pt(eve.Y)} } } }) }
func processEvent(cb Callbacks, e *C.AInputEvent) { switch C.AInputEvent_getType(e) { case C.AINPUT_EVENT_TYPE_KEY: log.Printf("TODO input event: key") case C.AINPUT_EVENT_TYPE_MOTION: if cb.Touch == nil { return } x := C.AMotionEvent_getX(e, 0) y := C.AMotionEvent_getY(e, 0) var ty event.TouchType switch C.AMotionEvent_getAction(e) { case C.AMOTION_EVENT_ACTION_DOWN: ty = event.TouchStart case C.AMOTION_EVENT_ACTION_MOVE: ty = event.TouchMove case C.AMOTION_EVENT_ACTION_UP: ty = event.TouchEnd } cb.Touch(event.Touch{ Type: ty, Loc: geom.Point{ X: geom.Pt(float32(x) / geom.PixelsPerPt), Y: geom.Pt(float32(y) / geom.PixelsPerPt), }, }) default: log.Printf("unknown input event, type=%d", C.AInputEvent_getType(e)) } }
//export sendTouch func sendTouch(touch, touchType uintptr, x, y float32) { id := -1 for i, val := range touchIDs { if val == touch { id = i break } } if id == -1 { for i, val := range touchIDs { if val == 0 { touchIDs[i] = touch id = i break } } if id == -1 { panic("out of touchIDs") } } t := touch.Type(touchType) if t == touch.TypeEnd { touchIDs[id] = 0 } eventsIn <- touch.Event{ Sequence: touch.Sequence(id), Type: t, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: geom.Pt(y / pixelsPerPt), }, } }
func windowDrawLoop(cb Callbacks, w *C.ANativeWindow, queue *C.AInputQueue) { C.createEGLWindow(w) // TODO: is the library or the app responsible for clearing the buffers? gl.ClearColor(0, 0, 0, 1) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) C.eglSwapBuffers(C.display, C.surface) if errv := gl.GetError(); errv != gl.NO_ERROR { log.Printf("GL initialization error: %s", errv) } geom.Width = geom.Pt(float32(C.windowWidth) / geom.PixelsPerPt) geom.Height = geom.Pt(float32(C.windowHeight) / geom.PixelsPerPt) for { processEvents(cb, queue) select { case <-windowDestroyed: return default: if cb.Draw != nil { cb.Draw() } C.eglSwapBuffers(C.display, C.surface) } } }
func onPaint(glctx gl.Context, sz size.Event) { glctx.ClearColor(236, 240, 241, 1) glctx.Clear(gl.COLOR_BUFFER_BIT) columnOffset := (sz.WidthPt / 2) - ((geom.Pt(gridColumnsCount) * tileSize) / 2) rowOffset := (sz.HeightPt / 2) - ((geom.Pt(gridrowsCount) * tileSize) / 2) for c := 0; c < gridColumnsCount; c++ { for r := 0; r < gridrowsCount; r++ { t := &grid[((c+1)*(r+1))-1] t.columnOffset = columnOffset t.rowOffset = rowOffset t.x = geom.Pt(c) * tileSize t.y = geom.Pt(r) * tileSize t.Draw(sz) } } }
//export sendTouch func sendTouch(touch, change uintptr, x, y float32) { id := -1 for i, val := range touchIDs { if val == touch { id = i break } } if id == -1 { for i, val := range touchIDs { if val == 0 { touchIDs[i] = touch id = i break } } if id == -1 { panic("out of touchIDs") } } c := event.Change(change) if c == event.ChangeOff { touchIDs[id] = 0 } eventsIn <- event.Touch{ ID: event.TouchSequenceID(id), Change: c, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: geom.Pt(y / pixelsPerPt), }, } }
func main() { defer func() { if err := recover(); err != nil { log.Println(err) } }() e := Engine{} app.Main(func(a app.App) { var c size.Event for eve := range a.Events() { switch eve := app.Filter(eve).(type) { case lifecycle.Event: switch eve.Crosses(lifecycle.StageVisible) { case lifecycle.CrossOn: e.Start() case lifecycle.CrossOff: e.Stop() } case size.Event: c = eve e.touchLoc = geom.Point{0, 0} case paint.Event: e.Draw(c) a.EndPaint(eve) case touch.Event: e.touchLoc = geom.Point{geom.Pt(eve.X), geom.Pt(eve.Y)} } } }) }
func windowDraw(w *C.ANativeWindow, queue *C.AInputQueue, donec chan struct{}) (done bool) { // Android can send a windowRedrawNeeded event any time, including // in the middle of a paint cycle. The redraw event may have changed // the size of the screen, so any partial painting is now invalidated. // We must also not return to Android (via sending on windowRedrawDone) // until a complete paint with the new configuration is complete. // // When a windowRedrawNeeded request comes in, we increment redrawGen // (Gen is short for generation number), and do not make a paint cycle // visible on <-endPaint unless paintGen agrees. If possible, // windowRedrawDone is signalled, allowing onNativeWindowRedrawNeeded // to return. var redrawGen, paintGen uint32 for { processEvents(queue) select { case <-donec: return true case cfg := <-windowConfigChange: // TODO save orientation pixelsPerPt = cfg.pixelsPerPt case w := <-windowRedrawNeeded: sendLifecycle(lifecycle.StageFocused) widthPx := int(C.ANativeWindow_getWidth(w)) heightPx := int(C.ANativeWindow_getHeight(w)) eventsIn <- config.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } if paintGen == 0 { paintGen++ C.createEGLWindow(w) eventsIn <- paint.Event{} } redrawGen++ case <-windowDestroyed: sendLifecycle(lifecycle.StageAlive) return false case <-gl.WorkAvailable: gl.DoWork() case <-endPaint: if paintGen == redrawGen { // eglSwapBuffers blocks until vsync. C.eglSwapBuffers(C.display, C.surface) select { case windowRedrawDone <- struct{}{}: default: } } paintGen = redrawGen eventsIn <- paint.Event{} } } }
//export setGeom func setGeom(ppp float32, width, height int) { pixelsPerPt = ppp windowHeight = geom.Pt(float32(height) / pixelsPerPt) eventsIn <- config.Event{ Width: geom.Pt(float32(width) / pixelsPerPt), Height: windowHeight, PixelsPerPt: pixelsPerPt, } }
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), }, } }
func sendTouch(c event.Change, x, y float32) { eventsIn <- event.Touch{ ID: 0, Change: c, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: windowHeight - geom.Pt(y/pixelsPerPt), }, } }
//export onResize 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 <- config.Event{ Width: geom.Pt(w), Height: geom.Pt(h), PixelsPerPt: pixelsPerPt, } }
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), }, } }
func sendTouch(c event.Change, x, y float32) { eventsIn <- event.Touch{ ID: 0, // TODO: button?? Change: c, Loc: geom.Point{ X: geom.Pt(x / pixelsPerPt), Y: geom.Pt(y / pixelsPerPt), }, } }
//export setGeom func setGeom(pixelsPerPt float32, widthPx, heightPx int) { windowHeightPx = float32(heightPx) eventsIn <- size.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } }
func sendTouch(ty event.TouchType, x, y float32) { events.Lock() events.pending = append(events.pending, event.Touch{ Type: ty, Loc: geom.Point{ X: geom.Pt(x / geom.PixelsPerPt), Y: geom.Height - geom.Pt(y/geom.PixelsPerPt), }, }) events.Unlock() }
//export updateConfig func updateConfig(width, height int) { widthPx := screenScale * width heightPx := screenScale * height eventsIn <- config.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } }
func onTouch(t touch.Event) { if t.Type != touch.TypeBegin { return } touchLoc = geom.Point{X: geom.Pt(t.X), Y: geom.Pt(t.Y)} acceptTouch = true if !activate { activate = true } }
func scrollStats() (avg, stddev geom.Pt) { n := geom.Pt(len(scrollLoc)) for _, x := range scrollLoc { avg += x } avg /= n for _, x := range scrollLoc { stddev += (x - avg) * (x - avg) } return avg, geom.Pt(math.Sqrt(float64(stddev / n))) }
func sendTouch(ty event.TouchType, x, y float32) { events.Lock() events.pending = append(events.pending, event.Touch{ Type: ty, Loc: geom.Point{ X: geom.Pt(x), Y: geom.Pt(y), }, }) events.Unlock() }
//export setGeom func setGeom(ppp float32, widthPx, heightPx int) { pixelsPerPt = ppp windowHeightPx = float32(heightPx) eventsIn <- config.Event{ WidthPx: widthPx, HeightPx: heightPx, WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), PixelsPerPt: pixelsPerPt, } }
//export onResize func onResize(w, h int) { // TODO(nigeltao): don't assume 72 DPI. DisplayWidth and DisplayWidthMM // is probably the best place to start looking. pixelsPerPt := float32(1) eventsIn <- size.Event{ WidthPx: w, HeightPx: h, WidthPt: geom.Pt(w), HeightPt: geom.Pt(h), PixelsPerPt: pixelsPerPt, } }
func nearestKey(loc geom.Point) key { var key key dist := geom.Pt(math.MaxFloat32) for _, k := range keys { kb := k.base() dx := geom.Pt((kb.pitch-pitchOffset)/pitchRange)*geom.Width - loc.X dy := geom.Pt(1-kb.y)*geom.Height - loc.Y d := geom.Pt(math.Hypot(float64(dx), float64(dy))) if d < dist && d < geom.Pt(math.Max(8, kb.size/float64(geom.PixelsPerPt))) { dist = d key = k } } return key }
func sendSizeEvent(hwnd syscall.Handle, r *_RECT) { windowsMu.Lock() w := windows[hwnd] windowsMu.Unlock() width := int(r.Right - r.Left) height := int(r.Bottom - r.Top) // TODO(andlabs): don't assume that PixelsPerPt == 1 w.Send(size.Event{ WidthPx: width, HeightPx: height, WidthPt: geom.Pt(width), HeightPt: geom.Pt(height), PixelsPerPt: 1, }) }
// TODO(crawshaw): It looks like we need a gl.RegisterInit feature. // TODO(crawshaw): The gldebug mode needs to complain loudly when GL functions // are called before init, because often they fail silently. func fpsInit() { font := "" switch runtime.GOOS { case "android": font = "/system/fonts/DroidSansMono.ttf" case "darwin": font = "/Library/Fonts/Andale Mono.ttf" case "linux": font = "/usr/share/fonts/truetype/droid/DroidSansMono.ttf" default: panic(fmt.Sprintf("go.mobile/app/debug: unsupported runtime.GOOS %q", runtime.GOOS)) } b, err := ioutil.ReadFile(font) if err != nil { panic(err) } f, err := freetype.ParseFont(b) if err != nil { panic(err) } monofont.SetFont(f) monofont.SetSrc(image.Black) monofont.SetHinting(freetype.FullHinting) toPx := func(x geom.Pt) int { return int(math.Ceil(float64(geom.Pt(x).Px()))) } fps.Image = glutil.NewImage(toPx(50), toPx(12)) monofont.SetDst(fps.Image.RGBA) monofont.SetClip(fps.Bounds()) monofont.SetDPI(72 * float64(geom.PixelsPerPt)) monofont.SetFontSize(12) }
func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) { // TODO: lifecycle events. newWidth, newHeight := int(ev.Width), int(ev.Height) if w.width == newWidth && w.height == newHeight { return } w.width, w.height = newWidth, newHeight // TODO: don't assume that PixelsPerPt == 1. w.Send(size.Event{ WidthPx: newWidth, HeightPx: newHeight, WidthPt: geom.Pt(newWidth), HeightPt: geom.Pt(newHeight), PixelsPerPt: 1, }) }