1
0
Fork 0

Adding upstream version 0.0~git20250520.a1d9079+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 19:46:29 +02:00
parent 590ac7ff5f
commit 20149b7f3a
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
456 changed files with 70406 additions and 0 deletions

178
example/basic/main.go Normal file
View file

@ -0,0 +1,178 @@
// 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.
//go:build darwin || linux || windows
// An app that draws a green triangle on a red background.
//
// In order to build this program as an Android APK, using the gomobile tool.
//
// See http://godoc.org/golang.org/x/mobile/cmd/gomobile to install gomobile.
//
// Get the basic example and use gomobile to build or install it on your device.
//
// $ go get -d golang.org/x/mobile/example/basic
// $ gomobile build golang.org/x/mobile/example/basic # will build an APK
//
// # plug your Android device to your computer or start an Android emulator.
// # if you have adb installed on your machine, use gomobile install to
// # build and deploy the APK to an Android target.
// $ gomobile install golang.org/x/mobile/example/basic
//
// Switch to your device or emulator to start the Basic application from
// the launcher.
// You can also run the application on your desktop by running the command
// below. (Note: It currently doesn't work on Windows.)
//
// $ go install golang.org/x/mobile/example/basic && basic
package main
import (
"encoding/binary"
"log"
"golang.org/x/mobile/app"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"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"
"golang.org/x/mobile/gl"
)
var (
images *glutil.Images
fps *debug.FPS
program gl.Program
position gl.Attrib
offset gl.Uniform
color gl.Uniform
buf gl.Buffer
green float32
touchX float32
touchY float32
)
func main() {
app.Main(func(a app.App) {
var glctx gl.Context
var sz size.Event
for e := range a.Events() {
switch e := a.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff:
onStop(glctx)
glctx = nil
}
case size.Event:
sz = e
touchX = float32(sz.WidthPx / 2)
touchY = float32(sz.HeightPx / 2)
case paint.Event:
if glctx == nil || e.External {
// As we are actively painting as fast as
// we can (usually 60 FPS), skip any paint
// events sent by the system.
continue
}
onPaint(glctx, sz)
a.Publish()
// Drive the animation by preparing to paint the next frame
// after this one is shown.
a.Send(paint.Event{})
case touch.Event:
touchX = e.X
touchY = e.Y
}
}
})
}
func onStart(glctx gl.Context) {
var err error
program, err = glutil.CreateProgram(glctx, vertexShader, fragmentShader)
if err != nil {
log.Printf("error creating GL program: %v", err)
return
}
buf = glctx.CreateBuffer()
glctx.BindBuffer(gl.ARRAY_BUFFER, buf)
glctx.BufferData(gl.ARRAY_BUFFER, triangleData, gl.STATIC_DRAW)
position = glctx.GetAttribLocation(program, "position")
color = glctx.GetUniformLocation(program, "color")
offset = glctx.GetUniformLocation(program, "offset")
images = glutil.NewImages(glctx)
fps = debug.NewFPS(images)
}
func onStop(glctx gl.Context) {
glctx.DeleteProgram(program)
glctx.DeleteBuffer(buf)
fps.Release()
images.Release()
}
func onPaint(glctx gl.Context, sz size.Event) {
glctx.ClearColor(1, 0, 0, 1)
glctx.Clear(gl.COLOR_BUFFER_BIT)
glctx.UseProgram(program)
green += 0.01
if green > 1 {
green = 0
}
glctx.Uniform4f(color, 0, green, 0, 1)
glctx.Uniform2f(offset, touchX/float32(sz.WidthPx), touchY/float32(sz.HeightPx))
glctx.BindBuffer(gl.ARRAY_BUFFER, buf)
glctx.EnableVertexAttribArray(position)
glctx.VertexAttribPointer(position, coordsPerVertex, gl.FLOAT, false, 0, 0)
glctx.DrawArrays(gl.TRIANGLES, 0, vertexCount)
glctx.DisableVertexAttribArray(position)
fps.Draw(sz)
}
var triangleData = f32.Bytes(binary.LittleEndian,
0.0, 0.4, 0.0, // top left
0.0, 0.0, 0.0, // bottom left
0.4, 0.0, 0.0, // bottom right
)
const (
coordsPerVertex = 3
vertexCount = 3
)
const vertexShader = `#version 100
uniform vec2 offset;
attribute vec4 position;
void main() {
// offset comes in with x/y values between 0 and 1.
// position bounds are -1 to 1.
vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0);
gl_Position = position + offset4;
}`
const fragmentShader = `#version 100
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}`

10
example/basic/main_x.go Normal file
View file

@ -0,0 +1,10 @@
// 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.
//go:build !darwin && !linux && !windows
package main
func main() {
}

View file

@ -0,0 +1,11 @@
Go bind android app example
Run
$ gomobile bind -o app/hello.aar golang.org/x/mobile/example/bind/hello
and import this project in Android Studio. If you prefer the command
line, use gradle to build directly.
Note that you need to run gomobile bind again every time you make a
change to Go code.

View file

@ -0,0 +1,34 @@
/*
* 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.
*/
apply plugin: 'com.android.application'
repositories {
flatDir {
dirs '.'
}
}
android {
compileSdkVersion 27
defaultConfig {
applicationId "org.golang.example.android"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:22.1.1'
implementation (name:'hello', ext:'aar')
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.golang.example.bind" >
<application
android:allowBackup="true"
android:label="GoBindExample">
<activity
android:name=".MainActivity"
android:label="GoBindExample" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,29 @@
/*
* 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 org.golang.example.bind;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import hello.Hello;
public class MainActivity extends Activity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.mytextview);
// Call Go function.
String greetings = Hello.greetings("Android and Gopher");
mTextView.setText(greetings);
}
}

View file

@ -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. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/mytextview" />
</RelativeLayout>

View file

@ -0,0 +1,8 @@
<!-- 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. -->
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View file

@ -0,0 +1,21 @@
/* 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.
*/
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}

View file

@ -0,0 +1,5 @@
/* 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.
*/
include ':app'

View file

@ -0,0 +1,12 @@
// 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 hello is a trivial package for gomobile bind example.
package hello
import "fmt"
func Greetings(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}

View file

@ -0,0 +1,2 @@
The sprites were created by Renee French and are distributed
under the Creative Commons Attributions 3.0 license.

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

356
example/flappy/game.go Normal file
View file

@ -0,0 +1,356 @@
// 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.
//go:build darwin || linux
package main
import (
"image"
"log"
"math"
"math/rand"
_ "image/png"
"golang.org/x/mobile/asset"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
)
const (
tileWidth, tileHeight = 16, 16 // width and height of each tile
tilesX, tilesY = 16, 16 // number of horizontal tiles
gopherTile = 1 // which tile the gopher is standing on (0-indexed)
initScrollV = 1 // initial scroll velocity
scrollA = 0.001 // scroll acceleration
gravity = 0.1 // gravity
jumpV = -5 // jump velocity
flapV = -1.5 // flap velocity
deadScrollA = -0.01 // scroll deceleration after the gopher dies
deadTimeBeforeReset = 240 // how long to wait before restarting the game
groundChangeProb = 5 // 1/probability of ground height change
groundWobbleProb = 3 // 1/probability of minor ground height change
groundMin = tileHeight * (tilesY - 2*tilesY/5)
groundMax = tileHeight * tilesY
initGroundY = tileHeight * (tilesY - 1)
climbGrace = tileHeight / 3 // gopher won't die if it hits a cliff this high
)
type Game struct {
gopher struct {
y float32 // y-offset
v float32 // velocity
atRest bool // is the gopher on the ground?
flapped bool // has the gopher flapped since it became airborne?
dead bool // is the gopher dead?
deadTime clock.Time // when the gopher died
}
scroll struct {
x float32 // x-offset
v float32 // velocity
}
groundY [tilesX + 3]float32 // ground y-offsets
groundTex [tilesX + 3]int // ground texture
lastCalc clock.Time // when we last calculated a frame
}
func NewGame() *Game {
var g Game
g.reset()
return &g
}
func (g *Game) reset() {
g.gopher.y = 0
g.gopher.v = 0
g.scroll.x = 0
g.scroll.v = initScrollV
for i := range g.groundY {
g.groundY[i] = initGroundY
g.groundTex[i] = randomGroundTexture()
}
g.gopher.atRest = false
g.gopher.flapped = false
g.gopher.dead = false
g.gopher.deadTime = 0
}
func (g *Game) Scene(eng sprite.Engine) *sprite.Node {
texs := loadTextures(eng)
scene := &sprite.Node{}
eng.Register(scene)
eng.SetTransform(scene, f32.Affine{
{1, 0, 0},
{0, 1, 0},
})
newNode := func(fn arrangerFunc) {
n := &sprite.Node{Arranger: arrangerFunc(fn)}
eng.Register(n)
scene.AppendChild(n)
}
// The ground.
for i := range g.groundY {
i := i
// The top of the ground.
newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
eng.SetSubTex(n, texs[g.groundTex[i]])
eng.SetTransform(n, f32.Affine{
{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
{0, tileHeight, g.groundY[i]},
})
})
// The earth beneath.
newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
eng.SetSubTex(n, texs[texEarth])
eng.SetTransform(n, f32.Affine{
{tileWidth, 0, float32(i)*tileWidth - g.scroll.x},
{0, tileHeight * tilesY, g.groundY[i] + tileHeight},
})
})
}
// The gopher.
newNode(func(eng sprite.Engine, n *sprite.Node, t clock.Time) {
a := f32.Affine{
{tileWidth * 2, 0, tileWidth*(gopherTile-1) + tileWidth/8},
{0, tileHeight * 2, g.gopher.y - tileHeight + tileHeight/4},
}
var x int
switch {
case g.gopher.dead:
x = frame(t, 16, texGopherDead1, texGopherDead2)
animateDeadGopher(&a, t-g.gopher.deadTime)
case g.gopher.v < 0:
x = frame(t, 4, texGopherFlap1, texGopherFlap2)
case g.gopher.atRest:
x = frame(t, 4, texGopherRun1, texGopherRun2)
default:
x = frame(t, 8, texGopherRun1, texGopherRun2)
}
eng.SetSubTex(n, texs[x])
eng.SetTransform(n, a)
})
return scene
}
// frame returns the frame for the given time t
// when each frame is displayed for duration d.
func frame(t, d clock.Time, frames ...int) int {
total := int(d) * len(frames)
return frames[(int(t)%total)/int(d)]
}
func animateDeadGopher(a *f32.Affine, t clock.Time) {
dt := float32(t)
a.Scale(a, 1+dt/20, 1+dt/20)
a.Translate(a, 0.5, 0.5)
a.Rotate(a, dt/math.Pi/-8)
a.Translate(a, -0.5, -0.5)
}
type arrangerFunc func(e sprite.Engine, n *sprite.Node, t clock.Time)
func (a arrangerFunc) Arrange(e sprite.Engine, n *sprite.Node, t clock.Time) { a(e, n, t) }
const (
texGopherRun1 = iota
texGopherRun2
texGopherFlap1
texGopherFlap2
texGopherDead1
texGopherDead2
texGround1
texGround2
texGround3
texGround4
texEarth
)
func randomGroundTexture() int {
return texGround1 + rand.Intn(4)
}
func loadTextures(eng sprite.Engine) []sprite.SubTex {
a, err := asset.Open("sprite.png")
if err != nil {
log.Fatal(err)
}
defer a.Close()
m, _, err := image.Decode(a)
if err != nil {
log.Fatal(err)
}
t, err := eng.LoadTexture(m)
if err != nil {
log.Fatal(err)
}
const n = 128
// The +1's and -1's in the rectangles below are to prevent colors from
// adjacent textures leaking into a given texture.
// See: http://stackoverflow.com/questions/19611745/opengl-black-lines-in-between-tiles
return []sprite.SubTex{
texGopherRun1: sprite.SubTex{t, image.Rect(n*0+1, 0, n*1-1, n)},
texGopherRun2: sprite.SubTex{t, image.Rect(n*1+1, 0, n*2-1, n)},
texGopherFlap1: sprite.SubTex{t, image.Rect(n*2+1, 0, n*3-1, n)},
texGopherFlap2: sprite.SubTex{t, image.Rect(n*3+1, 0, n*4-1, n)},
texGopherDead1: sprite.SubTex{t, image.Rect(n*4+1, 0, n*5-1, n)},
texGopherDead2: sprite.SubTex{t, image.Rect(n*5+1, 0, n*6-1, n)},
texGround1: sprite.SubTex{t, image.Rect(n*6+1, 0, n*7-1, n)},
texGround2: sprite.SubTex{t, image.Rect(n*7+1, 0, n*8-1, n)},
texGround3: sprite.SubTex{t, image.Rect(n*8+1, 0, n*9-1, n)},
texGround4: sprite.SubTex{t, image.Rect(n*9+1, 0, n*10-1, n)},
texEarth: sprite.SubTex{t, image.Rect(n*10+1, 0, n*11-1, n)},
}
}
func (g *Game) Press(down bool) {
if g.gopher.dead {
// Player can't control a dead gopher.
return
}
if down {
switch {
case g.gopher.atRest:
// Gopher may jump from the ground.
g.gopher.v = jumpV
case !g.gopher.flapped:
// Gopher may flap once in mid-air.
g.gopher.flapped = true
g.gopher.v = flapV
}
} else {
// Stop gopher rising on button release.
if g.gopher.v < 0 {
g.gopher.v = 0
}
}
}
func (g *Game) Update(now clock.Time) {
if g.gopher.dead && now-g.gopher.deadTime > deadTimeBeforeReset {
// Restart if the gopher has been dead for a while.
g.reset()
}
// Compute game states up to now.
for ; g.lastCalc < now; g.lastCalc++ {
g.calcFrame()
}
}
func (g *Game) calcFrame() {
g.calcScroll()
g.calcGopher()
}
func (g *Game) calcScroll() {
// Compute velocity.
if g.gopher.dead {
// Decrease scroll speed when the gopher dies.
g.scroll.v += deadScrollA
if g.scroll.v < 0 {
g.scroll.v = 0
}
} else {
// Increase scroll speed.
g.scroll.v += scrollA
}
// Compute offset.
g.scroll.x += g.scroll.v
// Create new ground tiles if we need to.
for g.scroll.x > tileWidth {
g.newGroundTile()
// Check whether the gopher has crashed.
// Do this for each new ground tile so that when the scroll
// velocity is >tileWidth/frame it can't pass through the ground.
if !g.gopher.dead && g.gopherCrashed() {
g.killGopher()
}
}
}
func (g *Game) calcGopher() {
// Compute velocity.
g.gopher.v += gravity
// Compute offset.
g.gopher.y += g.gopher.v
g.clampToGround()
}
func (g *Game) newGroundTile() {
// Compute next ground y-offset.
next := g.nextGroundY()
nextTex := randomGroundTexture()
// Shift ground tiles to the left.
g.scroll.x -= tileWidth
copy(g.groundY[:], g.groundY[1:])
copy(g.groundTex[:], g.groundTex[1:])
last := len(g.groundY) - 1
g.groundY[last] = next
g.groundTex[last] = nextTex
}
func (g *Game) nextGroundY() float32 {
prev := g.groundY[len(g.groundY)-1]
if change := rand.Intn(groundChangeProb) == 0; change {
return (groundMax-groundMin)*rand.Float32() + groundMin
}
if wobble := rand.Intn(groundWobbleProb) == 0; wobble {
return prev + (rand.Float32()-0.5)*climbGrace
}
return prev
}
func (g *Game) gopherCrashed() bool {
return g.gopher.y+tileHeight-climbGrace > g.groundY[gopherTile+1]
}
func (g *Game) killGopher() {
g.gopher.dead = true
g.gopher.deadTime = g.lastCalc
g.gopher.v = jumpV * 1.5 // Bounce off screen.
}
func (g *Game) clampToGround() {
if g.gopher.dead {
// Allow the gopher to fall through ground when dead.
return
}
// Compute the minimum offset of the ground beneath the gopher.
minY := g.groundY[gopherTile]
if y := g.groundY[gopherTile+1]; y < minY {
minY = y
}
// Prevent the gopher from falling through the ground.
maxGopherY := minY - tileHeight
g.gopher.atRest = false
if g.gopher.y >= maxGopherY {
g.gopher.v = 0
g.gopher.y = maxGopherY
g.gopher.atRest = true
g.gopher.flapped = false
}
}

98
example/flappy/main.go Normal file
View file

@ -0,0 +1,98 @@
// 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.
//go:build darwin || linux
// Flappy Gopher is a simple one-button game that uses the
// mobile framework and the experimental sprite engine.
package main
import (
"math/rand"
"time"
"golang.org/x/mobile/app"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/event/touch"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
"golang.org/x/mobile/exp/sprite/glsprite"
"golang.org/x/mobile/gl"
)
func main() {
rand.Seed(time.Now().UnixNano())
app.Main(func(a app.App) {
var glctx gl.Context
var sz size.Event
for e := range a.Events() {
switch e := a.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
glctx, _ = e.DrawContext.(gl.Context)
onStart(glctx)
a.Send(paint.Event{})
case lifecycle.CrossOff:
onStop()
glctx = nil
}
case size.Event:
sz = e
case paint.Event:
if glctx == nil || e.External {
continue
}
onPaint(glctx, sz)
a.Publish()
a.Send(paint.Event{}) // keep animating
case touch.Event:
if down := e.Type == touch.TypeBegin; down || e.Type == touch.TypeEnd {
game.Press(down)
}
case key.Event:
if e.Code != key.CodeSpacebar {
break
}
if down := e.Direction == key.DirPress; down || e.Direction == key.DirRelease {
game.Press(down)
}
}
}
})
}
var (
startTime = time.Now()
images *glutil.Images
eng sprite.Engine
scene *sprite.Node
game *Game
)
func onStart(glctx gl.Context) {
images = glutil.NewImages(glctx)
eng = glsprite.Engine(images)
game = NewGame()
scene = game.Scene(eng)
}
func onStop() {
eng.Release()
images.Release()
game = nil
}
func onPaint(glctx gl.Context, sz size.Event) {
glctx.ClearColor(1, 1, 1, 1)
glctx.Clear(gl.COLOR_BUFFER_BIT)
now := clock.Time(time.Since(startTime) * 60 / time.Second)
game.Update(now)
eng.Render(scene, now, sz)
}

10
example/flappy/main_x.go Normal file
View file

@ -0,0 +1,10 @@
// 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.
//go:build !darwin && !linux
package main
func main() {
}

View file

@ -0,0 +1,38 @@
## Ivy Big Number Calculator (Android)
[Ivy (robpike.io/ivy)](https://robpike.io/ivy) is an interpreter for an APL-like language written in Go.
This repository hosts a minimal Android project used for its [Android version](https://play.google.com/store/apps/details?id=org.golang.ivy&hl=en_US&gl=US).
### How to build
Requirements
- Go 1.17 or newer
- Android SDK
- Android NDK
- `golang.org/x/mobile/cmd/gomobile`
The `gomobile` command respects the `ANDROID_HOME` and `ANDROID_NDK_HOME` environment variables. If `gomobile` can't find your SDK and NDK, you can set these environment variables to specify their locations:
```
export ANDROID_HOME=/path/to/sdk-directory
export ANDROID_NDK_HOME=/path/to/ndk-directory
```
From this directory, run:
```sh
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gobind@latest
# Make sure `gomobile` and `gobind` is in your `PATH`.
gomobile bind -o app/ivy.aar robpike.io/ivy/mobile
```
Open this directory from Android Studio, and build.
`robpike.io/ivy` and `golang.org/x/mobile` are required dependencies of this main module. In order to update them:
```
go get -d golang.org/x/mobile@latest
go get -d robpike.io/ivy/mobile
go mod tidy
```

View file

@ -0,0 +1,25 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
defaultConfig {
applicationId 'org.golang.ivy'
minSdkVersion 15
targetSdkVersion 30
versionCode 4
versionName '1.4.0'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.0.0'
implementation files('ivy.aar')
}

View file

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/hakim/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View file

@ -0,0 +1,13 @@
package org.golang.ivy;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.golang.ivy"
android:installLocation="auto">
<!-- undo gradle's auto addition of unrequested permissions
http://stackoverflow.com/questions/27410382/unrequested-permissions-not-declared-in-androidmanifest-crashlytics-maybe -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:theme="@style/IvyAppTheme"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"
android:fullBackupContent="true">
<activity
android:name=".MainActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:windowSoftInputMode="adjustResize|stateUnchanged|stateAlwaysVisible"
android:configChanges="orientation|screenSize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Help"
android:label="@string/title_activity_help"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.golang.ivy.MainActivity" />
</activity>
<activity
android:name=".AboutIvy"
android:label="@string/title_activity_about"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.golang.ivy.MainActivity" />
</activity>
</application>
</manifest>

View file

@ -0,0 +1,21 @@
<!--
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.
-->
<html>
<body>
<center>
<h2>Ivy</h2><p>
Based on <a href='https://robpike.io/ivy'>robpike.io/ivy</a>
<p>
Copyright 2015 The <a href='https://golang.org/LICENSE'>Go Authors</a>.
<br>
The Ivy mascot is designed by
<a href='https://reneefrench.blogspot.com/'>Renee French</a>
and licensed under the
<a href='https://creativecommons.org/licenses/by/3.0/us/'>Creative Commons Attribution 3.0 license</a>.
</center>
</body>
</html>

View file

@ -0,0 +1,263 @@
# This is a demo of ivy. Type a newline to advance to each new step. Type one now.
# At any time, type the word "quit" or EOF to end the demo and return to ivy.
# Each step in the demo is one line of input followed by some output from ivy. Type a newline now to see.
2+2
# The first line you see above (2+2) is input; the next (4) is output from a running ivy.
# Comments start with # and produce no output.
# Whenever you like, you can type an expression yourself. Try typing 2*3 now, followed by two newlines:
# Keep typing newlines; the ivy demo is about to start.
# Arithmetic has the obvious operations: + - * etc. ** is exponentiation. mod is modulo.
23
23 + 45
23 * 45
23 - 45
7 ** 3
7 mod 3
# Operator precedence is unusual.
# Unary operators operate on everything to the right.
# Binary operators operate on the item immediately to the left, and everything to the right.
2*3+4 # Parsed as 2*(3+4), not the usual (2*3)+4.
2**2+3 # 2**5, not (2**2) + 3
(2**2)+3 # Use parentheses if you need to group differently.
# Ivy can do rational arithmetic, so 1/3 is really 1/3, not 0.333....
1/3
1/3 + 4/5
1/3 ** 2 # We'll see non-integral exponents later.
# Even when a number is input in floating notation, it is still an exact rational number inside.
1.2
# In fact, ivy is a "bignum" calculator that can handle huge numbers and rationals made of huge numbers.
1e10 # Still an integer.
1e100 # Still an integer.
1e10/3 # Not an integer, but an exact rational.
3/1e10 # Not an integer, but an exact rational.
2**64 # They can get big.
2**640 # They can get really big.
# They can get really really big. Type a newline to see 2**6400 scroll by.
2**6400
# Ivy also has characters, which represent a Unicode code point.
'x'
char 0x61 # char is an operator: character with given value.
char 0x1f4a9
code '💩' # char's inverse, the value of given character, here printed in decimal.
# Everything in ivy can be placed into a vector.
# Vectors are written and displayed with spaces between the elements.
1 2 3
1 4/3 5/3 (2+1/3)
# Note that without the parens this becomes (1 4/3 5/3 2)+1/3
1 4/3 5/3 2+1/3
# Vectors of characters print without quotes or spaces.
'h' 'e' 'l' 'l' 'o'
# This is a nicer way to write 'h' 'e' 'l' 'l' 'o'. It means the same.
'hello'
# Arithmetic works elementwise on vectors.
1 2 3 + 4 5 6
# Arithmetic between scalar and vector also works, either way.
23 + 1 2 3
1 2 3 + 23 # Note the grouping: vector is a single value.
# More fun with scalar and vector.
1 << 1 2 3 4 5
(1 << 1 2 3 4 5) == (2 ** 1 2 3 4 5) # Note: true is 1, false is 0.
# iota is an "index generator": It counts from 1.
iota 10
2 ** iota 5
(1 << iota 100) == 2 ** iota 100
2 ** -1 + iota 32 # Again, see how the precedence rules work.
# The take operator removes n items from the beginning of the vector.
3 take iota 10
-3 take iota 10 # Negative n takes from the end.
# Drop is the other half: it drops n from the vector.
3 drop iota 10
-3 drop iota 10 # Negative n drops from the end.
6 drop 'hello world'
# Reduction
iota 15
# Add them up:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15
# Automate this by reducing + over the vector, like this:
+/iota 15
# We can reduce using any binary operator. This is factorial:
1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10
*/iota 10
*/iota 100
# Type this: */iota 10000
# That printed using floating-point notation for manageability but it is still an integer inside.
# max and min are binary operators that do the obvious. (Use semicolons to separate expressions.)
3 max 7; 'is max and'; 3 min 7; 'is min'
# Like all binary arithmetic operators, max applies elementwise.
2 3 4 max 4 3 2
# Reduce using max to find maximum element in vector.
max/2 34 42 233 2 2 521 14 1 4 1 55 133
# Ivy allows multidimensional arrays. The binary shape operator, rho, builds them.
# Dimension (which may be a vector) on the left, data on the right.
5 rho 1
5 5 rho 1
5 5 rho 25
5 5 rho iota 25
3 5 5 rho iota 125
# Unary rho tells us the shape of an item.
x = 3 5 rho iota 15; x
rho x
x = 3 5 5 rho iota 75; x
rho x
# Arithmetic on matrices works as you would expect by now.
x/2
x**2
x**3
x**10
# Inner product is written with a . between the operators.
# This gives dot product: multiply corresponding elements and add the result.
1 2 3 4 +.* 2 3 4 5
# Any operator works. How many items are the same?
(1 2 3) +.== (1 3 3)
# How many differ?
(1 2 3) +.!= (1 3 3)
# Outer product generates a matrix of all combinations applying the binary operator.
(iota 5) o.* -1 + iota 5
# That's a letter 'o', dot, star.
# Any operator works; here is how to make an identity matrix.
x = iota 5; x o.== x
# Assignment is an operator, so you can save an intermediate expression.
x o.== x = iota 5
# Random numbers: Use a unary ? to roll an n-sided die from 1 to n.
?100
?100
?20 rho 6 # 20 rolls of a 6-sided die.
x = ?20 rho 6 # Remember one set of rolls.
x
# Indexing is easy.
x[1]
x[1 19 3] # You can index with a vector.
# The up and down operators generate index vectors that would sort the input.
up x
x[up x]
x[down x]
'hello world'[up 'hello world']
'hello world'[down 'hello world']
# More rolls of a die.
?10 rho 6
# Remember a set of rolls.
x = ?10 rho 6; x
# The outer product of == and the integers puts 1 in each row where that value appeared.
# Compare the last row of the next result to the 6s in x.
(iota 6) o.== x
# Count the number of times each value appears by reducing the matrix horizontally.
+/(iota 6) o.== x
# Do it for a much larger set of rolls: is the die fair?
+/(iota 6) o.== ?60000 rho 6
# Remember that ivy is a big number calculator.
*/iota 100
2**64
2**iota 64
-1+2**63
# Settings are made and queried with a leading right paren. )help helps with settings and other commands.
)help
# Use )base to switch input and output to base 16.
)base 16
)base # The input and output for settings is always base 10.
# _ is a variable that holds the most recently evaluated expression. It remembers our 63-bit number.
_
1<<iota 10 # 16 powers of two, base 16.
(2**40)-1 # The largest 64-bit number base 16.
)obase 10 # Output base 10, input base still 16.
)base
# The largest 63-bit number base 10.
-1+2**40 # The largest 64-bit number base 10.
-1+2**3F # The largest 63-bit number base 10.
# Go back to base 10 input and output.
)base 10
# Rationals can be very big too.
(2**1e3)/(3**1e2)
# Such output can be unwieldy. Change the output format using a Printf string.
)format '%.12g'
_
# We need more precision.
)format "%.100g" # Double quotes work too; there's no difference.
_
)format '%#x'
_
)format '%.12g' # A nice format, easily available by running ivy -g.
_
(3 4 rho iota 12)/4
# Irrational functions cannot be represented precisely by rational numbers.
# Ivy stores irrational results in high-precision (default 256-bit) floating point numbers.
sqrt 2
# pi and e are built-in, high-precision constants.
pi
e
)format "%.100g"
pi
)format '%.12g'
pi
# Exponentials and logarithms.
2**1/2 # Note: Non-integral exponent generates irrational result.
e**1e6
log e**1e6
log e**1e8
log 1e1000000 # Yes, that is 10 to the millionth power.
# Transcendentals. (The low bit isn't always right...)
sin pi/2
cos .25*pi * -1 + iota 9
log iota 6
# Successive approximations to e. (We force the calculation to use float using the "float" unary operator. Why?)
(float 1+10**-iota 9) ** 10**iota 9
# Default precision is 256 bits of mantissa. We can go up to 10000.
)prec 3350 # Units are bits, not digits. 2 log 10 == 3.321. Add a few more bits for floating point errors.
e
)format '%.1000g' # Units are digits. (Sorry for the inconsistency.)
e
pi
sqrt 2
e**1e6
log e**1e6
(2**1e3)/(3**1e2)
# User-defined operators are declared as unary or binary (or both). This one computes the (unary) average.
op avg x = (+/x)/rho x
avg iota 100
# Here is a binary operator.
op n largest x = n take x[down x]
3 largest ? 100 rho 1000
4 largest 'hello world'
# Population count. Use encode to turn the value into a string of bits. Use log to decide how many.
op a base b = ((ceil b log a) rho b) encode a
7 base 2
op popcount n = +/n base 2
popcount 7
popcount 1e6
popcount 1e100
# Here is one to sum the digits. The unary operator text turns its argument into text, like sprintf.
op sumdigits x = t = text x; +/(code (t in '0123456789') sel t) - code '0'
# Break it down: The sel operator selects from the right based on the non-zero elements in the left.
# The in operator generates a selector by choosing only the bytes that are ASCII digits.
sumdigits 99
sumdigits iota 10
sumdigits '23 skidoo' # Note: It counts only the digits.
# The binary text operator takes a format string (% optional) on the left and formats the value.
'%x' text 1234
# We can use this for another version of popcount: %b is binary.
op popcount n = +/'1' == '%b' text n
popcount 7
popcount 1e6
popcount 1e100
# A classic (expensive!) algorithm to count primes.
op primes N = (not T in T o.* T) sel T = 1 drop iota N
# The assignment to T gives 2..N. We use outer product to build an array of all products.
# Then we find all elements of T that appear in the product matrix, invert that, and select from the original.
primes 100
# A final trick.
# The binary ? operator "deals": x?y selects at random x distinct integers from 1..y inclusive.
5?10
# We can use this to shuffle a deck of cards. The suits are ♠♡♣♢, the values
# A234567890JQK (using 0 for 10, for simplicity).
# Create the deck using outer product with the ravel operator:
"A234567890JQK" o., "♠♡♣♢"
# To shuffle it, ravel into into a vector and index that by 1 through 52, shuffled.
(, "A234567890JQK" o., "♠♡♣♢")[52?52]
# There is no looping construct in ivy, but there is a conditional evaluator.
# Within a user-defined operator, one can write a condition expression
# using a binary operator, ":". If the left-hand operand is true (integer non-zero),
# the user-defined operator will return the right-hand operand as its
# result; otherwise execution continues.
op a gcd b = a == b: a; a > b: b gcd a-b; a gcd b-a
1562 gcd !11
# That's it! Have fun.
# For more information visit https://pkg.go.dev/robpike.io/ivy

View file

@ -0,0 +1,61 @@
<!--
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.
-->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" charset="UTF-8">
<style>
body {
font-family: sans-serif;
}
div {
padding: 1;
white-space: pre-wrap;
font-family: "Roboto Mono", monospace;
}
.comment {
color: grey;
}
.expr {
font-weight: bold;
}
.flow-hide {
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
overflow: hidden;
}
.flow-show {
word-break: break-all;
}
</style>
<script>
function flowClick(el) {
el.classList.toggle("flow-hide");
el.classList.toggle("flow-show");
}
function appendDiv(txt, tag) {
var el = document.createElement("div");
el.innerHTML = txt;
if (tag == "comment") {
el.classList.add("comment");
} else if (tag == "expr") {
el.classList.add("expr");
} else {
el.classList.add("flow-show");
el.onclick = function() {
flowClick(el);
};
}
document.body.appendChild(el);
}
</script>
<body>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View file

@ -0,0 +1,56 @@
/*
* 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 org.golang.ivy;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
/*
* Handles About menu item.
*/
public class AboutIvy extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.activity_about,
(ViewGroup) findViewById(R.id.about_layout));
WebView webView = (WebView) layout.findViewById(R.id.about_ivy);
webView.setWebViewClient(new WebViewClient(){
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url != null && (url.startsWith("https://") || url.startsWith("http://"))) {
view.getContext().startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
} else {
return false;
}
}
});
webView.getSettings().setDefaultTextEncodingName("utf-8");
webView.loadUrl("file:///android_asset/aboutivy.html");
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
onBackPressed(); // back to parent.
return true;
}
}

View file

@ -0,0 +1,54 @@
/**
* 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 org.golang.ivy;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import mobile.Mobile;
/*
* Displays the help message for Ivy.
*/
public class Help extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help);
WebView webView = (WebView) findViewById(R.id.help_webview);
webView.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// we are not a browser; redirect the request to proper apps.
if (url != null) {
view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
return true;
}
});
webView.getSettings().setDefaultTextEncodingName("utf-8");
// mobile.Mobile was generated using gomobile bind robpike.io/ivy/mobile.
String helpMsg = Mobile.help();
// loadData has a rendering bug: https://code.google.com/p/android/issues/detail?id=6965
webView.loadDataWithBaseURL("http://pkg.go.dev/robpike.io/ivy", helpMsg, "text/html", "UTF-8", null);
webView.setBackgroundColor(getResources().getColor(R.color.body));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
onBackPressed(); // back to parent.
return true;
}
}

View file

@ -0,0 +1,356 @@
/*
* 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 org.golang.ivy;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ScrollView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
// This is in ivy.aar.
import mobile.Mobile;
/*
* Main activity that consists of an edit view to accept the expression
* and a web view to display output of the expression.
*/
public class MainActivity extends AppCompatActivity {
final String DEMO_SCRIPT = "demo.ivy"; // in assets directory.
final String DEBUG_TAG = "Ivy";
private WebView mWebView;
private EditText mEditText;
private ScrollView mScroller;
private BufferedReader mDemo;
private ImageButton mOKButton; // enabled only in demo mode.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mScroller = (ScrollView) findViewById(R.id.scroller);
mWebView = (WebView) findViewById(R.id.webView);
mEditText = (EditText) findViewById(R.id.editText);
mOKButton = (ImageButton) findViewById(R.id.imageButton);
mOKButton.setVisibility(View.GONE);
if (savedInstanceState != null) {
mWebView.restoreState(savedInstanceState);
} else {
clear();
}
configureWebView(mWebView);
mEditText.requestFocus();
mEditText.setOnKeyListener(new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
callIvy();
return true;
}
return false;
}
});
mOKButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
callIvy();
}
});
/* For webview debugging - visit chrome://inspect/#devices */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE))
{ WebView.setWebContentsDebuggingEnabled(true); }
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
mWebView.saveState(outState);
super.onSaveInstanceState(outState);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mWebView.restoreState(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return true;
}
private long mLastPress = 0;
@Override
public void onBackPressed() {
// TODO: store and restore the state across app restarts.
long currentTime = System.currentTimeMillis();
if(currentTime - mLastPress > 6000){
Toast.makeText(getBaseContext(), "Press back again to exit.\nAll app state will be lost upon exit.", Toast.LENGTH_LONG).show();
mLastPress = currentTime;
}else{
super.onBackPressed();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_about:
startActivity(new Intent(this, AboutIvy.class));
return true;
case R.id.action_help:
startActivity(new Intent(this, Help.class));
return true;
case R.id.action_clear:
clear();
return true;
case R.id.action_demo:
loadDemo();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void clear() {
// As described in https://code.google.com/p/android/issues/detail?id=18726
// clearing the contents of the webview doesn't shrink the webview size in some
// old versions of Android. (e.g. Moto X running 4.4.4). I tried various techniques
// suggested in the Internet, but nothing worked except creating a new webview.
WebView newView = new WebView(this);
newView.setLayoutParams(mWebView.getLayoutParams());
newView.setId(R.id.webView);
configureWebView(newView);
mScroller.removeView(mWebView);
mWebView.destroy();
mWebView = newView;
mWebView.loadUrl("file:///android_asset/tape.html");
mWebView.setBackgroundColor(getResources().getColor(R.color.body));
mScroller.addView(mWebView);
mEditText.setText("");
Mobile.reset();
unloadDemo();
}
void configureWebView(WebView webView) {
// We enable javascript, but disallow any url loading.
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient() {
// Disallow arbitrary contents loaded into our own webview.
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.getContext().startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}
});
webView.setFocusable(false);
webView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
// It's possible that the layout is not complete.
// In that case we will get all zero values for the positions. Ignore this case.
if (left == 0 && top == 0 && right == 0 && bottom == 0) {
return;
}
scrollToBottom();
}
});
}
private String escapeHtmlTags(final String s) {
// Leaves entities (&-prefixed) alone unlike TextUtils.htmlEncode
// (https://github.com/aosp-mirror/platform_frameworks_base/blob/d59921149bb5948ffbcb9a9e832e9ac1538e05a0/core/java/android/text/TextUtils.java#L1361).
// Ivy mobile.Eval result may include encoding starting with &.
StringBuilder sb = new StringBuilder();
char c;
for (int i = 0; i < s.length(); i++) {
c = s.charAt(i);
switch (c) {
case '<':
sb.append("&lt;"); //$NON-NLS-1$
break;
case '>':
sb.append("&gt;"); //$NON-NLS-1$
break;
case '"':
sb.append("&quot;"); //$NON-NLS-1$
break;
case '\'':
//http://www.w3.org/TR/xhtml1
// The named character reference &apos; (the apostrophe, U+0027) was introduced in
// XML 1.0 but does not appear in HTML. Authors should therefore use &#39; instead
// of &apos; to work as expected in HTML 4 user agents.
sb.append("&#39;"); //$NON-NLS-1$
break;
default:
sb.append(c);
}
}
return sb.toString();
}
private void appendShowText(final String s, final String tag) {
mWebView.loadUrl("javascript:appendDiv('" + TextUtils.htmlEncode(s).replaceAll("(\r\n|\n)", "<br />") + "', '" + tag + "')");
mWebView.setBackgroundColor(getResources().getColor(R.color.body));
}
private void appendShowPreformattedText(final String s, final String tag) {
mWebView.loadUrl("javascript:appendDiv('" + escapeHtmlTags(s).replaceAll("\r?\n", "<br/>") + "', '" + tag + "')");
mWebView.setBackgroundColor(getResources().getColor(R.color.body));
}
private void callIvy() {
String s = mEditText.getText().toString().trim();
if (s != null && !s.isEmpty()) {
appendShowText(s, "expr");
}
if (mDemo != null && s.trim().equals("quit")) {
unloadDemo();
s = " "; // this will clear the text box.
}
new IvyCallTask().execute(s); // where call to Ivy backend occurs.
}
private synchronized void loadDemo() {
try {
if (mDemo == null) {
mDemo = new BufferedReader(new InputStreamReader(getAssets().open(DEMO_SCRIPT), "UTF-8"));
}
mOKButton.setVisibility(View.VISIBLE);
new IvyCallTask().execute("");
} catch (IOException e) {
Toast.makeText(this, "Failed to load Demo script.\nContact the app author.", Toast.LENGTH_SHORT);
}
}
private synchronized void unloadDemo() {
if (mDemo == null) { return; }
try {
mDemo.close();
} catch (IOException e) {
Log.d(DEBUG_TAG, e.toString());
}
mDemo = null;
mOKButton.setVisibility(View.GONE);
}
private synchronized String readDemo() {
if (mDemo == null) { return null; }
try {
return mDemo.readLine();
} catch (IOException e) {
unloadDemo();
}
return null;
}
private void scrollToBottom() {
mScroller.post(new Runnable() {
public void run() {
mScroller.smoothScrollTo(0, mWebView.getBottom());
}
});
}
// AsyncTask that evaluates the expression (string), and returns the strings
// to display in the web view and the edit view respectively.
private class IvyCallTask extends AsyncTask<String, Void, Pair<String, String> > {
private String ivyEval(final String expr) {
try {
// mobile.Mobile was generated using
// gomobile bind -javapkg=org.golang.ivy robpike.io/ivy/mobile
return Mobile.eval(expr); // Gobind-generated method.
} catch (Exception e) {
return "error: "+e.getMessage();
}
}
// doInBackground checks the demo script (if the passed-in param is empty),
// or returns the ivy evaluation result.
@Override
protected Pair<String, String> doInBackground(String ...param) {
final String expr = param[0];
// TODO: cancel, timeout
if (expr == null || expr.isEmpty()) {
return checkDemo();
}
return Pair.create(ivyEval(expr), "");
}
// checkDemo reads the demo script and returns the comment, and the next expression.
protected Pair<String, String> checkDemo() {
String showText = null;
while (true) {
String s = readDemo();
if (s == null) {
break;
}
if (s.startsWith("# ")) {
return Pair.create(s, null);
}
return Pair.create(null, s);
}
return null;
}
@Override
protected void onPostExecute(final Pair<String, String> result) {
if (result == null || (result.first == null && result.second == null)) {
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
String showText = result.first;
if (showText != null) {
final String tag = (showText.startsWith("#")) ? "comment" : "result";
appendShowPreformattedText(showText, tag);
}
String editText = result.second;
if (editText != null) {
mEditText.setText(editText);
}
}
});
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape android:shape="oval">
<solid android:color="@color/blue"/>
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="oval">
<solid android:color="@color/button_material_dark"/>
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/about_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EEFFFFFF"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"
android:weightSum="1"
tools:context="org.golang.ivy.About">
<ImageView
android:id="@+id/ivysketch"
android:src="@drawable/ivyabout"
android:layout_gravity="center_horizontal"
android:layout_width="256dp"
android:layout_height="256dp"
android:contentDescription="@string/ivysketch_desc" />
<WebView
android:id="@+id/about_ivy"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/app_name"
android:textSize="@dimen/abc_text_size_headline_material" />
</LinearLayout>

View file

@ -0,0 +1,14 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="match_parent"
tools:context="org.golang.ivy.Help"
android:background="@color/body">
<WebView
android:id="@+id/help_webview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/body" />
</RelativeLayout>

View file

@ -0,0 +1,69 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="@+id/layout"
android:background="@color/body">
<ScrollView
android:id="@+id/scroller"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbars="vertical"
android:fillViewport="false"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_above="@+id/layout_bottom"
android:padding="@dimen/abc_control_padding_material">
<WebView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/webView"
android:gravity="left|bottom"
android:textIsSelectable="true"
android:clickable="false"
android:background="@color/body" />
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:id="@+id/layout_bottom"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true">
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/editText"
android:gravity="top|left|start"
android:textStyle="normal"
android:inputType="textVisiblePassword"
android:hint="@string/editTextHelp"/>
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageButton"
android:background="@drawable/circle_shape"
android:src="@drawable/ic_done_white_24dp"
android:contentDescription="@string/ok"
android:onClick="onClick"
android:clickable="true"
android:padding="@dimen/abc_control_padding_material"
android:layout_margin="@dimen/abc_control_padding_material" />
</LinearLayout>
</RelativeLayout>

View file

@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.golang.ivy.About">
<item
android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ivyapp="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_clear"
android:title="@string/action_clear"
ivyapp:showAsAction="always" />
<item
android:id="@+id/action_demo"
android:title="@string/action_demo"
ivyapp:showAsAction="collapseActionView" />
<item
android:id="@+id/action_help"
android:title="@string/action_help"
ivyapp:showAsAction="collapseActionView" />
<item
android:id="@+id/action_about"
android:title="@string/action_about"
ivyapp:showAsAction="collapseActionView" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ok">OK</string>
</resources>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="body">#ffffeb</color>
<color name="sky">#e2f6ff</color>
<color name="blue">#1997d4</color>
<color name="white">#f9f9f9</color>
<color name="black">#000000</color>
</resources>

View file

@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View file

@ -0,0 +1,51 @@
<resources>
<string name="app_name">Ivy</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="action_help">Help</string>
<string name="action_about">About</string>
<string name="action_clear">Clear</string>
<string name="action_demo">Demo</string>
<string name="about_details">by Go Authors</string>
<string name="title_activity_about">About Ivy</string>
<string name="title_activity_help">Help</string>
<string name="ivy_sketch">ivy_sketch</string>
<string name="editTextHelp">type an expression</string>
<string name="ivysketch_desc">ivy sketch</string>
<string name="ok">OK</string>
<string-array name="expression_array">
<item>ceil</item>
<item>floor</item>
<item>rho</item>
<item>abs</item>
<item>iota</item>
<item>sgn</item>
<item>rev</item>
<item>flip</item>
<item>up</item>
<item>down</item>
<item>div</item>
<item>idiv</item>
<item>max</item>
<item>min</item>
<item>rho</item>
<item>take</item>
<item>drop</item>
<item>mod</item>
<item>imod</item>
<item>or</item>
<item>and</item>
<item>nor</item>
<item>nand</item>
<item>xor</item>
<item>def</item>
<item>) format</item>
<item>) op</item>
<item>) base</item>
<item>) origin</item>
</string-array>
</resources>

View file

@ -0,0 +1,16 @@
<resources>
<style name="IvyAppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorControlActivated">@color/sky</item>
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryDark">@color/blue</item>
<item name="actionBarStyle">@style/MyActionBarLogo</item>
</style>
<style name="MyActionBarLogo" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="background">@color/blue</item>
<item name="logo">@mipmap/ic_launcher</item>
<item name="displayOptions">useLogo|showHome</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
}
}
allprojects {
repositories {
mavenCentral()
google()
}
}

View file

@ -0,0 +1 @@
include ':app'

7
example/ivy/doc.go Normal file
View file

@ -0,0 +1,7 @@
// Copyright 2021 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.
// This is an empty package.
// We have this package to convince this is buildable and go mod tidy can work.
package dummy

15
example/ivy/go.mod Normal file
View file

@ -0,0 +1,15 @@
module golang.org/x/mobile/example/ivy
go 1.23.0
require (
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7
robpike.io/ivy v0.2.7
)
require (
golang.org/x/mod v0.4.2 // indirect
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
golang.org/x/tools v0.1.2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
)

40
example/ivy/go.sum Normal file
View file

@ -0,0 +1,40 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7 h1:CyFUjc175y/mbMjxe+WdqI72jguLyjQChKCDe9mfTvg=
golang.org/x/mobile v0.0.0-20210924032853-1c027f395ef7/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
robpike.io/ivy v0.2.7 h1:XVYgWSm7THVm1bk1jfRBB9xhso459T22gbpotHbhk7M=
robpike.io/ivy v0.2.7/go.mod h1:6B/DGaft5rvYiF7MgCTiZAAvH5W7vtu0eS2BW77updo=

27
example/ivy/ios/README.md Normal file
View file

@ -0,0 +1,27 @@
# Ivy iOS App source
This directory contains the source code to the Ivy iOS app.
To build, first create the Mobile.xcframework out of the Go
implementation of Ivy. Run:
```
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gobind@latest
```
to install `gomobile` and `gobind`. Then:
```
mkdir work; cd work
go mod init work
go get -d golang.org/x/mobile/bind@latest
go get -d robpike.io/ivy/mobile
gomobile bind -target=ios,iossimulator,maccatalyst,macos robpike.io/ivy/mobile robpike.io/ivy/demo
```
Place the Mobile.xcframework directory in this directory, and
then open ivy.xcodeproj in Xcode.
You have to specify Development Team for code signing certificate in:
Project Settings -> Targets -> Signing & Capabilities -> Signing -> Team.

View file

@ -0,0 +1,391 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
7DC2F7EA26DFD9870026EBED /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DC2F7E826DFCF750026EBED /* WebKit.framework */; };
7DC2F7ED26DFD9890026EBED /* Mobile.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4C2833A1B98889100878964 /* Mobile.xcframework */; };
B461D25D1B31B27700EC4870 /* tape.html in Resources */ = {isa = PBXBuildFile; fileRef = B461D25C1B31B27700EC4870 /* tape.html */; };
B48878331B2E714100C7CC3C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B488780C1B2D1C3F00C7CC3C /* AppDelegate.m */; };
B48878341B2E714100C7CC3C /* IvyController.m in Sources */ = {isa = PBXBuildFile; fileRef = B488780F1B2D1C3F00C7CC3C /* IvyController.m */; };
B48878351B2E714100C7CC3C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B48878091B2D1C3F00C7CC3C /* main.m */; };
B48878371B2E714100C7CC3C /* Suggestion.m in Sources */ = {isa = PBXBuildFile; fileRef = B48878311B2DF1B200C7CC3C /* Suggestion.m */; };
B48878381B2E719000C7CC3C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B48878111B2D1C3F00C7CC3C /* Main.storyboard */; };
B48878391B2E719000C7CC3C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B48878141B2D1C3F00C7CC3C /* Images.xcassets */; };
B488783D1B2F9CD500C7CC3C /* DocsController.m in Sources */ = {isa = PBXBuildFile; fileRef = B488783C1B2F9CD500C7CC3C /* DocsController.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
7DC2F7E826DFCF750026EBED /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
B461D25C1B31B27700EC4870 /* tape.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = tape.html; sourceTree = "<group>"; };
B48878041B2D1C3F00C7CC3C /* ivy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ivy.app; sourceTree = BUILT_PRODUCTS_DIR; };
B48878081B2D1C3F00C7CC3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B48878091B2D1C3F00C7CC3C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
B488780B1B2D1C3F00C7CC3C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
B488780C1B2D1C3F00C7CC3C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
B488780E1B2D1C3F00C7CC3C /* IvyController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IvyController.h; sourceTree = "<group>"; };
B488780F1B2D1C3F00C7CC3C /* IvyController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IvyController.m; sourceTree = "<group>"; };
B48878121B2D1C3F00C7CC3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
B48878141B2D1C3F00C7CC3C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
B48878301B2DF1B200C7CC3C /* Suggestion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Suggestion.h; sourceTree = "<group>"; };
B48878311B2DF1B200C7CC3C /* Suggestion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Suggestion.m; sourceTree = "<group>"; };
B488783B1B2F9CD500C7CC3C /* DocsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocsController.h; sourceTree = "<group>"; };
B488783C1B2F9CD500C7CC3C /* DocsController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DocsController.m; sourceTree = "<group>"; };
B4C2833A1B98889100878964 /* Mobile.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mobile.xcframework; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
B48878011B2D1C3F00C7CC3C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7DC2F7ED26DFD9890026EBED /* Mobile.xcframework in Frameworks */,
7DC2F7EA26DFD9870026EBED /* WebKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7DC2F7E726DFCF740026EBED /* Frameworks */ = {
isa = PBXGroup;
children = (
7DC2F7E826DFCF750026EBED /* WebKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
B48877FB1B2D1C3F00C7CC3C = {
isa = PBXGroup;
children = (
B4C2833A1B98889100878964 /* Mobile.xcframework */,
B48878061B2D1C3F00C7CC3C /* ivy */,
B48878051B2D1C3F00C7CC3C /* Products */,
7DC2F7E726DFCF740026EBED /* Frameworks */,
);
sourceTree = "<group>";
};
B48878051B2D1C3F00C7CC3C /* Products */ = {
isa = PBXGroup;
children = (
B48878041B2D1C3F00C7CC3C /* ivy.app */,
);
name = Products;
sourceTree = "<group>";
};
B48878061B2D1C3F00C7CC3C /* ivy */ = {
isa = PBXGroup;
children = (
B488780B1B2D1C3F00C7CC3C /* AppDelegate.h */,
B488780C1B2D1C3F00C7CC3C /* AppDelegate.m */,
B488780E1B2D1C3F00C7CC3C /* IvyController.h */,
B488780F1B2D1C3F00C7CC3C /* IvyController.m */,
B488783B1B2F9CD500C7CC3C /* DocsController.h */,
B488783C1B2F9CD500C7CC3C /* DocsController.m */,
B48878301B2DF1B200C7CC3C /* Suggestion.h */,
B48878311B2DF1B200C7CC3C /* Suggestion.m */,
B461D25C1B31B27700EC4870 /* tape.html */,
B48878111B2D1C3F00C7CC3C /* Main.storyboard */,
B48878141B2D1C3F00C7CC3C /* Images.xcassets */,
B48878071B2D1C3F00C7CC3C /* Supporting Files */,
);
path = ivy;
sourceTree = "<group>";
};
B48878071B2D1C3F00C7CC3C /* Supporting Files */ = {
isa = PBXGroup;
children = (
B48878081B2D1C3F00C7CC3C /* Info.plist */,
B48878091B2D1C3F00C7CC3C /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
B48878031B2D1C3F00C7CC3C /* ivy */ = {
isa = PBXNativeTarget;
buildConfigurationList = B48878271B2D1C3F00C7CC3C /* Build configuration list for PBXNativeTarget "ivy" */;
buildPhases = (
B48878001B2D1C3F00C7CC3C /* Sources */,
B48878011B2D1C3F00C7CC3C /* Frameworks */,
B48878021B2D1C3F00C7CC3C /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ivy;
productName = ivy;
productReference = B48878041B2D1C3F00C7CC3C /* ivy.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
B48877FC1B2D1C3F00C7CC3C /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "The Go Authors";
TargetAttributes = {
B48878031B2D1C3F00C7CC3C = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = B48877FF1B2D1C3F00C7CC3C /* Build configuration list for PBXProject "ivy" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = B48877FB1B2D1C3F00C7CC3C;
productRefGroup = B48878051B2D1C3F00C7CC3C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
B48878031B2D1C3F00C7CC3C /* ivy */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
B48878021B2D1C3F00C7CC3C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B48878381B2E719000C7CC3C /* Main.storyboard in Resources */,
B48878391B2E719000C7CC3C /* Images.xcassets in Resources */,
B461D25D1B31B27700EC4870 /* tape.html in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
B48878001B2D1C3F00C7CC3C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B48878331B2E714100C7CC3C /* AppDelegate.m in Sources */,
B488783D1B2F9CD500C7CC3C /* DocsController.m in Sources */,
B48878341B2E714100C7CC3C /* IvyController.m in Sources */,
B48878351B2E714100C7CC3C /* main.m in Sources */,
B48878371B2E714100C7CC3C /* Suggestion.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
B48878111B2D1C3F00C7CC3C /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
B48878121B2D1C3F00C7CC3C /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
B48878251B2D1C3F00C7CC3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 x86_64 armv7";
};
name = Debug;
};
B48878261B2D1C3F00C7CC3C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VALID_ARCHS = "arm64 x86_64 armv7";
};
name = Release;
};
B48878281B2D1C3F00C7CC3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ivy/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.google.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = ivy;
STRIP_STYLE = debugging;
SUPPORTS_MACCATALYST = YES;
TARGETED_DEVICE_FAMILY = "1,2,6";
VALID_ARCHS = "arm64 x86_64 armv7";
};
name = Debug;
};
B48878291B2D1C3F00C7CC3C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ivy/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.google.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = ivy;
STRIP_STYLE = debugging;
SUPPORTS_MACCATALYST = YES;
TARGETED_DEVICE_FAMILY = "1,2,6";
VALID_ARCHS = "arm64 x86_64 armv7";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
B48877FF1B2D1C3F00C7CC3C /* Build configuration list for PBXProject "ivy" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B48878251B2D1C3F00C7CC3C /* Debug */,
B48878261B2D1C3F00C7CC3C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B48878271B2D1C3F00C7CC3C /* Build configuration list for PBXNativeTarget "ivy" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B48878281B2D1C3F00C7CC3C /* Debug */,
B48878291B2D1C3F00C7CC3C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = B48877FC1B2D1C3F00C7CC3C /* Project object */;
}

View file

@ -0,0 +1,14 @@
// 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.
#import <WebKit/WebKit.h>
#import "IvyController.h"
#import "Suggestion.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate, UITextFieldDelegate, WKUIDelegate>
@property(strong, nonatomic) UIWindow *window;
@end

View file

@ -0,0 +1,35 @@
// 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.
#import "AppDelegate.h"
#import "IvyController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
}
- (void)applicationWillTerminate:(UIApplication *)application {
}
@end

View file

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="mTw-C8-NzX">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<accessibilityOverrides isEnabled="YES"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Docs-->
<scene sceneID="qSe-m0-5Rh">
<objects>
<viewController title="Docs" id="rfr-rm-AXI" customClass="DocsController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="TeY-hL-zeC"/>
<viewControllerLayoutGuide type="bottom" id="Yrx-qe-pYd"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="a4n-1z-obZ">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView opaque="NO" tag="11" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xlZ-tR-6QP">
<rect key="frame" x="20" y="96" width="374" height="758"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
</subviews>
<color key="backgroundColor" red="0.9882352941176471" green="0.98039215686274506" blue="0.81176470588235294" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="xlZ-tR-6QP" firstAttribute="trailing" secondItem="a4n-1z-obZ" secondAttribute="trailingMargin" id="GP2-Wr-Bh9"/>
<constraint firstItem="xlZ-tR-6QP" firstAttribute="top" secondItem="TeY-hL-zeC" secondAttribute="bottom" constant="8" symbolic="YES" id="Vvo-yF-z4a"/>
<constraint firstItem="Yrx-qe-pYd" firstAttribute="top" secondItem="xlZ-tR-6QP" secondAttribute="bottom" constant="8" symbolic="YES" id="afC-pF-Eom"/>
<constraint firstItem="xlZ-tR-6QP" firstAttribute="leading" secondItem="a4n-1z-obZ" secondAttribute="leadingMargin" id="wxq-1W-9XD"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Documentation" id="lf7-H3-aZF"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HMz-gF-Hp5" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1724.6305418719212" y="384.80000000000001"/>
</scene>
<!--Ivy Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController title="Ivy Controller" id="BYZ-38-t0r" customClass="IvyController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleAspectFit" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Cn1-rU-W3R">
<rect key="frame" x="20" y="831" width="374" height="31"/>
<subviews>
<textField opaque="NO" clipsSubviews="YES" tag="1" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Type an expression" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="9SS-TP-C7c" userLabel="InputField">
<rect key="frame" x="0.0" y="0.0" width="301.5" height="31"/>
<color key="backgroundColor" red="0.98823529409999999" green="0.98039215690000003" blue="0.81176470590000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<rect key="contentStretch" x="1" y="1" width="1" height="1"/>
<fontDescription key="fontDescription" name="Menlo-Bold" family="Menlo" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" keyboardAppearance="alert" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections>
<action selector="okPressed:" destination="BYZ-38-t0r" eventType="primaryActionTriggered" id="3BK-xS-ize"/>
<action selector="okPressed:" destination="BYZ-38-t0r" eventType="editingDidEnd" id="91o-Gh-hrf"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eO6-bM-CwW">
<rect key="frame" x="301.5" y="0.0" width="72.5" height="31"/>
<accessibility key="accessibilityConfiguration" label="OK"/>
<constraints>
<constraint firstAttribute="height" constant="31" id="1nb-CV-bfg"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Button" image="return" catalog="system"/>
<connections>
<action selector="okPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="258-zN-fwW"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="9SS-TP-C7c" firstAttribute="trailing" secondItem="eO6-bM-CwW" secondAttribute="leading" id="9Fe-Sf-U5p"/>
<constraint firstItem="eO6-bM-CwW" firstAttribute="top" secondItem="9SS-TP-C7c" secondAttribute="top" id="9hv-aL-4z2"/>
<constraint firstItem="eO6-bM-CwW" firstAttribute="trailing" secondItem="Cn1-rU-W3R" secondAttribute="trailing" id="A4G-YY-4Gn"/>
<constraint firstItem="9SS-TP-C7c" firstAttribute="leading" secondItem="Cn1-rU-W3R" secondAttribute="leading" id="i2m-BU-Q4a"/>
</constraints>
</stackView>
<wkWebView opaque="NO" tag="2" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" layoutMarginsFollowReadableWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ga1-Py-9re">
<rect key="frame" x="20" y="88" width="374" height="737"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
</subviews>
<color key="backgroundColor" red="0.9882352941176471" green="0.98039215686274506" blue="0.81176470588235294" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Cn1-rU-W3R" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="7DU-Hc-uk7"/>
<constraint firstItem="Cn1-rU-W3R" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="8mr-vY-R1v"/>
<constraint firstItem="ga1-Py-9re" firstAttribute="bottom" secondItem="Cn1-rU-W3R" secondAttribute="top" constant="-6" id="BX0-OH-IJ4"/>
<constraint firstItem="ga1-Py-9re" firstAttribute="trailing" secondItem="8bC-Xf-vdC" secondAttribute="trailingMargin" id="Fxh-TE-BtG"/>
<constraint firstItem="Cn1-rU-W3R" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="LPE-LO-nct"/>
<constraint firstItem="ga1-Py-9re" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="topMargin" id="p9M-sp-ZwC"/>
<constraint firstItem="ga1-Py-9re" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="pOi-3s-hrr"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Ivy" id="9fD-A5-WTg">
<barButtonItem key="leftBarButtonItem" title="Clear" id="70x-xp-aiX">
<connections>
<action selector="clear:" destination="BYZ-38-t0r" id="n7k-fg-dil"/>
</connections>
</barButtonItem>
<rightBarButtonItems>
<barButtonItem title="Help" id="aDa-GJ-VpX">
<connections>
<segue destination="rfr-rm-AXI" kind="show" id="kuc-xA-dyM"/>
</connections>
</barButtonItem>
<barButtonItem title="Demo" id="ZN2-PQ-aPW">
<connections>
<action selector="demo:" destination="BYZ-38-t0r" id="NpG-yh-wMO"/>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
<connections>
<outlet property="bottomConstraint" destination="8mr-vY-R1v" id="BqX-Do-vtq"/>
<outlet property="input" destination="9SS-TP-C7c" id="jUA-25-4WI"/>
<outlet property="okButton" destination="eO6-bM-CwW" id="o9U-hD-NDr"/>
<outlet property="tape" destination="ga1-Py-9re" id="00M-9S-MAl"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="989.85507246376824" y="382.36607142857139"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="ZgV-45-Pf8">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="mTw-C8-NzX" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" opaque="NO" contentMode="scaleToFill" id="aev-Tm-XK3">
<rect key="frame" x="0.0" y="44" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="backgroundColor" red="1" green="1" blue="0.80392156859999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="1" green="1" blue="0.80392156859999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<navigationBarAppearance key="standardAppearance">
<color key="backgroundColor" red="1" green="1" blue="0.80392156859999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBarAppearance>
<navigationBarAppearance key="compactAppearance">
<color key="backgroundColor" red="1" green="1" blue="0.80392156859999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBarAppearance>
<navigationBarAppearance key="scrollEdgeAppearance">
<color key="backgroundColor" red="1" green="1" blue="0.80392156859999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</navigationBarAppearance>
</navigationBar>
<nil name="viewControllers"/>
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="smI-B7-WbU">
<autoresizingMask key="autoresizingMask"/>
</toolbar>
<connections>
<segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="Nyy-d4-AXi"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="i1l-Sg-DC5" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="271" y="386"/>
</scene>
</scenes>
<resources>
<image name="return" catalog="system" width="128" height="101"/>
</resources>
</document>

View file

@ -0,0 +1,11 @@
// 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.
#import <WebKit/WebKit.h>
// DocsController displays the documentation page.
@interface DocsController : UIViewController
@end

View file

@ -0,0 +1,42 @@
// 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.
#import "DocsController.h"
#import "mobile/Mobile.h"
@interface DocsController () {
WKWebView *webView;
}
@end
@implementation DocsController
- (void)viewDidLoad {
[super viewDidLoad];
webView = (WKWebView *)[self.view viewWithTag:11];
NSString *helpHTML = MobileHelp();
NSRange r = [helpHTML rangeOfString:@"<head>"];
NSString *html = [helpHTML substringToIndex:r.location];
// With the following meta tag, WKWebView displays the fonts more nicely.
NSString *meta = @"<meta name='viewport' \
content='width=device-width, "
@"initial-scale=1.0, maximum-scale=1.0, \
minimum-scale=1.0, "
@"user-scalable=no'>";
html = [html stringByAppendingString:@"<head>"];
html = [html stringByAppendingString:meta];
html = [html stringByAppendingString:[helpHTML substringFromIndex:r.location]];
[webView loadHTMLString:html baseURL:NULL];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end

View file

@ -0,0 +1,300 @@
{
"images" : [
{
"filename" : "icon-40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "icon-58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "icon-80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "icon-120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "icon-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "icon-40.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "icon-58.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "icon-80.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "icon-152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "icon-167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "icon-120.png",
"idiom" : "car",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "icon-180.png",
"idiom" : "car",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "icon-48.png",
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "24x24",
"subtype" : "38mm"
},
{
"filename" : "icon-55.png",
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "27.5x27.5",
"subtype" : "42mm"
},
{
"filename" : "icon-58.png",
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "icon-87.png",
"idiom" : "watch",
"role" : "companionSettings",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "watch",
"role" : "notificationCenter",
"scale" : "2x",
"size" : "33x33",
"subtype" : "45mm"
},
{
"filename" : "icon-80.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "40x40",
"subtype" : "38mm"
},
{
"filename" : "icon-88.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "44x44",
"subtype" : "40mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "46x46",
"subtype" : "41mm"
},
{
"filename" : "icon-100.png",
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "50x50",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "appLauncher",
"scale" : "2x",
"size" : "51x51",
"subtype" : "45mm"
},
{
"filename" : "icon-172.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "86x86",
"subtype" : "38mm"
},
{
"filename" : "icon-196.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "98x98",
"subtype" : "42mm"
},
{
"filename" : "icon-216.png",
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "108x108",
"subtype" : "44mm"
},
{
"idiom" : "watch",
"role" : "quickLook",
"scale" : "2x",
"size" : "117x117",
"subtype" : "45mm"
},
{
"filename" : "icon-1024.png",
"idiom" : "watch-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"filename" : "icon-16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon-32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon-32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon-128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon-256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon-256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon-512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon-512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon-1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Ivy</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.google.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>6</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>Launch</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIRequiresFullScreen</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,25 @@
// 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.
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
#import "Suggestion.h"
// IvyController displays the main app view.
@interface IvyController : UIViewController <UITextFieldDelegate, WKUIDelegate, SuggestionDelegate>
@property(weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;
// A text input field coupled to an output "tape", rendered with a WKWebView.
@property(weak, nonatomic) IBOutlet UITextField *input;
@property(strong, nonatomic) Suggestion *suggestionView;
@property(weak, nonatomic) IBOutlet WKWebView *tape;
@property(weak, nonatomic) IBOutlet UIButton *okButton;
- (IBAction)clear:(id)sender;
- (IBAction)demo:(id)sender;
- (IBAction)okPressed:(id)sender;
@end

View file

@ -0,0 +1,216 @@
// 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.
#import "IvyController.h"
#import "mobile/Mobile.h"
@interface IvyController ()
@end
@implementation IvyController {
NSArray *demo_lines;
int demo_index;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.input.delegate = self;
self.input.autocorrectionType = UITextAutocorrectionTypeNo;
self.input.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
self.suggestionView = [[Suggestion alloc] init];
self.suggestionView.delegate = self;
self.tape.UIDelegate = self;
self->demo_lines = NULL;
[self.okButton setTitle:@"" forState:UIControlStateNormal];
[self.okButton setHidden:TRUE];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(textDidChange:)
name:UITextFieldTextDidChangeNotification
object:self.input];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
[self.input becomeFirstResponder];
[self clear:NULL];
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if ([textField isEqual:self.input]) {
textField.inputAccessoryView = self.suggestionView;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
[textField reloadInputViews];
}
return YES;
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
if ([textField isEqual:self.input]) {
textField.inputAccessoryView = nil;
[textField reloadInputViews];
}
return YES;
}
- (void)textDidChange:(NSNotification *)notif {
[self.suggestionView suggestFor:self.input.text];
}
- (void)suggestionReplace:(NSString *)text {
self.input.text = text;
[self.suggestionView suggestFor:text];
}
- (void)keyboardWillShow:(NSNotification *)aNotification {
// Move the input text field up, as the keyboard has taken some of the screen.
NSDictionary *info = [aNotification userInfo];
CGRect kbFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSNumber *duration = [info objectForKey:UIKeyboardAnimationDurationUserInfoKey];
UIViewAnimationCurve keyboardTransitionAnimationCurve;
[[info valueForKey:UIKeyboardAnimationCurveUserInfoKey]
getValue:&keyboardTransitionAnimationCurve];
UIViewAnimationOptions options =
keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16;
[UIView animateWithDuration:duration.floatValue
delay:0
options:options
animations:^{
self.bottomConstraint.constant = 0 - kbFrame.size.height;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
[self scrollTapeToBottom];
}];
}
- (void)keyboardWillHide:(NSNotification *)aNotification {
// Move the input text field back down.
NSDictionary *info = [aNotification userInfo];
NSNumber *duration = [info objectForKey:UIKeyboardAnimationDurationUserInfoKey];
UIViewAnimationCurve keyboardTransitionAnimationCurve;
[[info valueForKey:UIKeyboardAnimationCurveUserInfoKey]
getValue:&keyboardTransitionAnimationCurve];
UIViewAnimationOptions options =
keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16;
int offset = self.input.inputAccessoryView != NULL ? self.suggestionView.frame.size.height : 0;
[UIView animateWithDuration:duration.floatValue
delay:0
options:options
animations:^{
self.bottomConstraint.constant = 0 - offset;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
[self scrollTapeToBottom];
}];
}
- (void)enterPressed {
NSString *text = self.input.text;
if ([text isEqual:@""]) {
if (self->demo_lines == NULL) {
return;
}
while (demo_index < self->demo_lines.count) {
NSString *line = self->demo_lines[self->demo_index++];
if ([line hasPrefix:@"#"]) {
[self appendTape:line tag:@"comment"];
} else {
self.input.text = line;
break;
}
}
} else if (self->demo_lines != NULL && [text isEqual:@"quit"]) {
[self unloadDemo];
} else {
[self appendTape:text tag:@"expr"];
NSString *expr = [text stringByAppendingString:@"\n"];
NSError *err;
NSString *result = MobileEval(expr, &err);
if (err != nil) {
result = err.description;
}
result = [result stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
result = [result stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"];
result = [result stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"];
NSMutableArray *lines = (NSMutableArray *)[result componentsSeparatedByString:@"\n"];
for (NSMutableString *line in lines) {
if ([line hasPrefix:@"#"])
[self appendTape:line tag:@"comment"];
else
[self appendTape:line tag:@"result"];
}
self.input.text = @"";
}
[self.input becomeFirstResponder];
}
- (void)scrollTapeToBottom {
NSString *scroll = @"window.scrollBy(0, document.body.offsetHeight);";
[self.tape evaluateJavaScript:scroll completionHandler:nil];
}
- (void)appendTape:(NSString *)text tag:(NSString *)tag {
NSString *injectSrc = @"appendDiv('%@','%@');";
NSString *runToInject = [NSString stringWithFormat:injectSrc, text, tag];
[self.tape evaluateJavaScript:runToInject completionHandler:nil];
[self scrollTapeToBottom];
}
- (void)loadDemo {
[self.okButton setHidden:FALSE];
NSString *text = DemoText();
self->demo_lines =
[text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
self->demo_index = 0;
self.input.text = @"";
self.input.enablesReturnKeyAutomatically = TRUE;
[self enterPressed];
}
- (void)unloadDemo {
[self.okButton setHidden:TRUE];
self.input.enablesReturnKeyAutomatically = FALSE;
self->demo_lines = NULL;
self.input.text = @"";
}
- (IBAction)okPressed:(id)sender {
[self enterPressed];
}
- (IBAction)demo:(id)sender {
if (self->demo_lines) { // demo already running
[self enterPressed];
} else {
[self loadDemo];
}
}
- (IBAction)clear:(id)sender {
[self unloadDemo];
NSString *string = [NSString
stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"tape" ofType:@"html"]
encoding:NSUTF8StringEncoding
error:NULL];
[self.tape loadHTMLString:string baseURL:NULL];
}
@end

View file

@ -0,0 +1,22 @@
// 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.
#import <UIKit/UIKit.h>
@protocol SuggestionDelegate <NSObject>
@required
- (void)suggestionReplace:(NSString *)text;
@end
@interface Suggestion : UIInputView
- (instancetype)init;
- (instancetype)initWithFrame:(CGRect)frame;
- (void)suggestFor:(NSString *)text;
@property(weak) id<SuggestionDelegate> delegate;
@end

View file

@ -0,0 +1,164 @@
// 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.
#import "Suggestion.h"
#define maxSuggestions 4 + 3
@implementation Suggestion {
NSString *text;
NSRange range;
NSMutableOrderedSet *options;
NSMutableArray *buttons;
NSArray *possibleSuggestions;
NSCharacterSet *breakingChars;
}
- (instancetype)init {
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
self = [self initWithFrame:CGRectMake(0.0f, 0.0f, screenWidth, 36.0f)];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame inputViewStyle:UIInputViewStyleKeyboard];
if (self) {
possibleSuggestions = @[
@")base ", @")debug ", @")format ", @")maxdigits ", @")op ", @")origin ", @")prec ",
@")prompt ", @")seed ", @"cos ", @"iota ", @"log ", @"max ", @"min ", @"pi ", @"rho ",
@"sin ", @"sqrt ", @"tan "
];
breakingChars = [NSCharacterSet characterSetWithCharactersInString:@"/+-*,^|= "];
options = [[NSMutableOrderedSet alloc] initWithCapacity:maxSuggestions];
buttons = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.05f];
[self setSuggestions:nil];
}
return self;
}
- (void)suggestFor:(NSString *)t {
text = t;
range = [text rangeOfCharacterFromSet:breakingChars options:NSBackwardsSearch];
if (range.location == NSNotFound) {
range.location = 0;
range.length = text.length;
} else {
if (range.location > 0 && [text characterAtIndex:range.location - 1] == ')') {
// Special case for suggestions that start with ") ".
range.location -= 1;
range.length++;
} else {
range.location += 1;
range.length -= 0;
}
}
range.length = text.length - range.location;
if (range.length == 0) {
[self setSuggestions:nil];
} else {
NSString *prefix = [text substringWithRange:range];
// TODO: make not so slow.
NSArray *suggestions = @[];
for (NSString *suggestion in possibleSuggestions) {
if ([suggestion hasPrefix:prefix] && prefix.length < suggestion.length) {
suggestions = [suggestions arrayByAddingObject:suggestion];
}
}
if (suggestions.count > 3) {
suggestions = nil;
}
[self setSuggestions:suggestions];
}
[self setNeedsLayout];
}
- (void)setSuggestions:(NSArray *)suggestions {
[options removeAllObjects];
if ([suggestions respondsToSelector:@selector(countByEnumeratingWithState:objects:count:)]) {
for (NSString *suggestion in suggestions) {
if (options.count < maxSuggestions) {
[options addObject:suggestion];
} else {
break;
}
}
}
}
- (void)layoutSubview:(NSString *)t at:(CGFloat)x width:(CGFloat)w {
UIButton *b = [[UIButton alloc] initWithFrame:CGRectMake(x, 0.0f, w, self.bounds.size.height)];
[b setTitle:t forState:UIControlStateNormal];
b.titleLabel.adjustsFontSizeToFitWidth = YES;
b.titleLabel.textAlignment = NSTextAlignmentCenter;
[b setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[b addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:b];
if (x > 0) {
UIView *line =
[[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 0.5f, self.bounds.size.height)];
line.backgroundColor = [UIColor colorWithRed:0.984 green:0.977 blue:0.81 alpha:1.0];
[b addSubview:line];
}
[buttons addObject:b];
}
- (void)layoutSubviews {
for (UIView *subview in buttons) {
[subview removeFromSuperview];
}
[buttons removeAllObjects];
CGFloat symbolWidth = 40.0f;
[self layoutSubview:@"+" at:0 * symbolWidth width:symbolWidth];
[self layoutSubview:@"-" at:1 * symbolWidth width:symbolWidth];
[self layoutSubview:@"*" at:2 * symbolWidth width:symbolWidth];
[self layoutSubview:@"/" at:3 * symbolWidth width:symbolWidth];
for (int i = 0; i < options.count; i++) {
NSString *suggestion = options[i];
CGFloat width = (self.bounds.size.width - (4 * symbolWidth)) / options.count;
CGFloat x = (4 * symbolWidth) + (i * width);
[self layoutSubview:suggestion at:x width:width];
}
}
- (void)buttonTouched:(UIButton *)button {
NSTimeInterval duration = 0.08f;
[UIView animateWithDuration:duration
animations:^{
[button setBackgroundColor:[UIColor whiteColor]];
if ([self.delegate respondsToSelector:@selector(suggestionReplace:)]) {
NSString *t = self->text;
if (t == nil) {
t = @"";
}
if (button.currentTitle.length == 1) {
// Special case for +, -, *, /.
t = [t stringByAppendingString:button.currentTitle];
} else {
t = [self->text stringByReplacingCharactersInRange:self->range
withString:button.currentTitle];
}
[self performSelector:@selector(suggestionReplace:)
withObject:t
afterDelay:duration * 0.8f];
}
[button performSelector:@selector(setBackgroundColor:)
withObject:[UIColor clearColor]
afterDelay:duration];
}];
}
- (void)suggestionReplace:(NSString *)t {
[self.delegate performSelector:@selector(suggestionReplace:) withObject:t];
}
@end

View file

@ -0,0 +1,18 @@
/* Class = "UITextField"; placeholder = "Type an expression"; ObjectID = "9SS-TP-C7c"; */
"9SS-TP-C7c.placeholder" = "Type an expression";
/* Class = "UIViewController"; title = "Ivy Controller"; ObjectID = "BYZ-38-t0r"; */
"BYZ-38-t0r.title" = "Ivy Controller";
/* Class = "UIBarButtonItem"; title = "Help"; ObjectID = "GSn-BW-al6"; */
"GSn-BW-al6.title" = "Help";
/* Class = "UINavigationItem"; title = "Ivy"; ObjectID = "KhW-J4-UcU"; */
"KhW-J4-UcU.title" = "Ivy";
/* Class = "UINavigationItem"; title = "Documentation"; ObjectID = "lf7-H3-aZF"; */
"lf7-H3-aZF.title" = "Documentation";
/* Class = "UIViewController"; title = "Docs"; ObjectID = "rfr-rm-AXI"; */
"rfr-rm-AXI.title" = "Docs";

View file

@ -0,0 +1,11 @@
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[])
{
@autoreleasepool
{
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}

Some files were not shown because too many files have changed in this diff Show more