Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
178
example/basic/main.go
Normal 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
|
@ -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() {
|
||||
}
|
11
example/bind/android/README
Normal 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.
|
34
example/bind/android/app/build.gradle
Normal 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')
|
||||
}
|
22
example/bind/android/app/src/main/AndroidManifest.xml
Normal 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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
8
example/bind/android/app/src/main/res/values/dimens.xml
Normal 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>
|
21
example/bind/android/build.gradle
Normal 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()
|
||||
}
|
||||
}
|
5
example/bind/android/settings.gradle
Normal 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'
|
12
example/bind/hello/hello.go
Normal 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)
|
||||
}
|
2
example/flappy/assets/README
Normal file
|
@ -0,0 +1,2 @@
|
|||
The sprites were created by Renee French and are distributed
|
||||
under the Creative Commons Attributions 3.0 license.
|
BIN
example/flappy/assets/sprite.png
Normal file
After Width: | Height: | Size: 75 KiB |
356
example/flappy/game.go
Normal 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
|
@ -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
|
@ -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() {
|
||||
}
|
38
example/ivy/android/README.md
Normal 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
|
||||
```
|
25
example/ivy/android/app/build.gradle
Normal 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')
|
||||
}
|
17
example/ivy/android/app/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
|
@ -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);
|
||||
}
|
||||
}
|
52
example/ivy/android/app/src/main/AndroidManifest.xml
Normal 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>
|
21
example/ivy/android/app/src/main/assets/aboutivy.html
Normal 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>
|
263
example/ivy/android/app/src/main/assets/demo.ivy
Normal 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
|
61
example/ivy/android/app/src/main/assets/tape.html
Normal 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>
|
BIN
example/ivy/android/app/src/main/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 77 KiB |
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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("<"); //$NON-NLS-1$
|
||||
break;
|
||||
case '>':
|
||||
sb.append(">"); //$NON-NLS-1$
|
||||
break;
|
||||
case '"':
|
||||
sb.append("""); //$NON-NLS-1$
|
||||
break;
|
||||
case '\'':
|
||||
//http://www.w3.org/TR/xhtml1
|
||||
// The named character reference ' (the apostrophe, U+0027) was introduced in
|
||||
// XML 1.0 but does not appear in HTML. Authors should therefore use ' instead
|
||||
// of ' to work as expected in HTML 4 user agents.
|
||||
sb.append("'"); //$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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
BIN
example/ivy/android/app/src/main/res/drawable-hdpi/actionbar_solid.png
Executable file
After Width: | Height: | Size: 200 B |
After Width: | Height: | Size: 188 B |
BIN
example/ivy/android/app/src/main/res/drawable-mdpi/actionbar_solid.png
Executable file
After Width: | Height: | Size: 158 B |
After Width: | Height: | Size: 139 B |
BIN
example/ivy/android/app/src/main/res/drawable-xhdpi/actionbar_solid.png
Executable file
After Width: | Height: | Size: 271 B |
After Width: | Height: | Size: 199 B |
BIN
example/ivy/android/app/src/main/res/drawable-xxhdpi/actionbar_solid.png
Executable file
After Width: | Height: | Size: 358 B |
After Width: | Height: | Size: 255 B |
|
@ -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>
|
BIN
example/ivy/android/app/src/main/res/drawable/ivyabout.png
Normal file
After Width: | Height: | Size: 277 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
10
example/ivy/android/app/src/main/res/menu/menu_about.xml
Normal 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>
|
24
example/ivy/android/app/src/main/res/menu/menu_main.xml
Normal 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>
|
BIN
example/ivy/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
example/ivy/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -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>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="ok">OK</string>
|
||||
</resources>
|
11
example/ivy/android/app/src/main/res/values/colors.xml
Normal 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>
|
5
example/ivy/android/app/src/main/res/values/dimens.xml
Normal 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>
|
51
example/ivy/android/app/src/main/res/values/strings.xml
Normal 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>
|
16
example/ivy/android/app/src/main/res/values/styles.xml
Normal 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>
|
18
example/ivy/android/build.gradle
Normal 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()
|
||||
}
|
||||
}
|
1
example/ivy/android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
include ':app'
|
7
example/ivy/doc.go
Normal 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
|
@ -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
|
@ -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
|
@ -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.
|
391
example/ivy/ios/ivy.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
14
example/ivy/ios/ivy/AppDelegate.h
Normal 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
|
||||
|
35
example/ivy/ios/ivy/AppDelegate.m
Normal 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
|
||||
|
178
example/ivy/ios/ivy/Base.lproj/Main.storyboard
Normal 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>
|
11
example/ivy/ios/ivy/DocsController.h
Normal 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
|
||||
|
42
example/ivy/ios/ivy/DocsController.m
Normal 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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 427 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 957 B |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 6 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 9.6 KiB |
52
example/ivy/ios/ivy/Info.plist
Normal 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>
|
25
example/ivy/ios/ivy/IvyController.h
Normal 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
|
||||
|
216
example/ivy/ios/ivy/IvyController.m
Normal 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:@"<"];
|
||||
result = [result stringByReplacingOccurrencesOfString:@">" withString:@">"];
|
||||
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
|
||||
|
22
example/ivy/ios/ivy/Suggestion.h
Normal 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
|
||||
|
164
example/ivy/ios/ivy/Suggestion.m
Normal 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
|
||||
|
18
example/ivy/ios/ivy/en.lproj/Main.strings
Normal 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";
|
11
example/ivy/ios/ivy/main.m
Normal 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]));
|
||||
}
|
||||
}
|