Add ebitenmobile command

This works only for iOS so far. I'll implement Java version soon.

Updates #863
This commit is contained in:
Hajime Hoshi 2019-08-17 19:00:37 +09:00
parent 424474f486
commit 9ba113861c
11 changed files with 602 additions and 225 deletions

View File

@ -12,11 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ios
//go:generate file2byteslice -package=main -input=gobind.go -output=gobind.src.go -var gobindsrc
// TODO: Embed this header file to .framework by an original command.
#import <UIKit/UIKit.h>
@interface EbitenViewController : UIViewController
@end
package main

199
cmd/ebitenmobile/gobind.go Normal file
View File

@ -0,0 +1,199 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ebitenmobilegobind
// gobind is a wrapper of the original gobind. This command adds extra files like a view controller.
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"golang.org/x/tools/go/packages"
)
var (
lang = flag.String("lang", "", "")
outdir = flag.String("outdir", "", "")
javaPkg = flag.String("javapkg", "", "")
prefix = flag.String("prefix", "", "")
bootclasspath = flag.String("bootclasspath", "", "")
classpath = flag.String("classpath", "", "")
tags = flag.String("tags", "", "")
)
var usage = `The Gobind tool generates Java language bindings for Go.
For usage details, see doc.go.`
func main() {
flag.Parse()
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
cmd := exec.Command("gobind-original", os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
pkgs, err := packages.Load(nil, flag.Args()[0])
if err != nil {
return err
}
prefixLower := *prefix + pkgs[0].Name
prefixUpper := strings.Title(*prefix) + strings.Title(pkgs[0].Name)
writeFile := func(filename string, content string) error {
if err := ioutil.WriteFile(filepath.Join(*outdir, "src", "gobind", filename), []byte(content), 0644); err != nil {
return err
}
return nil
}
replacePrefixes := func(content string) string {
content = strings.ReplaceAll(content, "{{.PrefixUpper}}", prefixUpper)
content = strings.ReplaceAll(content, "{{.PrefixLower}}", prefixLower)
return content
}
// Add additional files.
langs := strings.Split(*lang, ",")
for _, lang := range langs {
switch lang {
case "objc":
// iOS
if err := writeFile(prefixLower+"ebitenviewcontroller_ios.m", replacePrefixes(objcM)); err != nil {
return err
}
case "java":
// Android
// TODO: Insert a Java file and let the original gobind compile it.
case "go":
// Do nothing.
default:
panic(fmt.Sprintf("unsupported language: %s", lang))
}
}
return nil
}
const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
// +build ios
#import <stdint.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLkit.h>
#import "Ebitenmobileview.objc.h"
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
@end
@implementation {{.PrefixUpper}}EbitenViewController {
GLKView* glkView_;
}
- (GLKView*)glkView {
if (!glkView_) {
glkView_ = [[GLKView alloc] init];
glkView_.multipleTouchEnabled = YES;
}
return glkView_;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.glkView.delegate = (id<GLKViewDelegate>)(self);
[self.view addSubview: self.glkView];
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[self glkView].context = context;
[EAGLContext setCurrentContext:context];
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect viewRect = [[self view] frame];
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height, (id<EbitenmobileviewViewRectSetter>)self);
}
- (void)setViewRect:(long)x y:(long)y width:(long)width height:(long)height {
CGRect glkViewRect = CGRectMake(x, y, width, height);
[[self glkView] setFrame:glkViewRect];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
// TODO: Notify this to Go world?
}
- (void)drawFrame{
[[self glkView] setNeedsDisplay];
}
- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
NSError* err = nil;
EbitenmobileviewUpdate(&err);
if (err != nil) {
NSLog(@"Error: %@", err);
}
}
- (void)updateTouches:(NSSet*)touches {
for (UITouch* touch in touches) {
if (touch.view != [self glkView]) {
continue;
}
CGPoint location = [touch locationInView:touch.view];
EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
}
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
@end
`

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,110 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
)
const gomobileHash = "597adff16ade9d88626f8caea514bb189b8c74ee"
func runGo(args ...string) error {
env := []string{
"GO111MODULE=on",
}
if buildX || buildN {
for _, e := range env {
fmt.Printf("%s ", e)
}
fmt.Print("go")
for _, arg := range args {
fmt.Printf(" %s", arg)
}
fmt.Println()
}
if buildN {
return nil
}
cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), env...)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("go %v failed: %v\n%v", args, string(out), err)
}
return nil
}
func prepareGomobileCommands() error {
tmp, err := ioutil.TempDir("", "ebitenmobile-")
if err != nil {
return err
}
newpath := filepath.Join(tmp, "bin")
if path := os.Getenv("PATH"); path != "" {
newpath += string(filepath.ListSeparator) + path
}
os.Setenv("PATH", newpath)
pwd, err := os.Getwd()
if err != nil {
return err
}
// cd
if buildX {
fmt.Printf("cd %s\n", tmp)
}
if err := os.Chdir(tmp); err != nil {
return err
}
defer func() {
os.Chdir(pwd)
}()
if err := runGo("mod", "init", "ebitenmobiletemporary"); err != nil {
return err
}
if err := runGo("get", "golang.org/x/mobile@"+gomobileHash); err != nil {
return err
}
if err := runGo("build", "-o", filepath.Join("bin", "gomobile"), "golang.org/x/mobile/cmd/gomobile"); err != nil {
return err
}
if err := runGo("build", "-o", filepath.Join("bin", "gobind-original"), "golang.org/x/mobile/cmd/gobind"); err != nil {
return err
}
if err := os.Mkdir("src", 0755); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join("src", "gobind.go"), gobindsrc, 0644); err != nil {
return err
}
if err := runGo("build", "-o", filepath.Join("bin", "gobind"), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil {
return err
}
// TODO: Create a gobind wrapper
return nil
}

222
cmd/ebitenmobile/main.go Normal file
View File

@ -0,0 +1,222 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
"golang.org/x/tools/go/packages"
)
const (
ebitenmobileCommand = "ebitenmobile"
)
func init() {
flag.Usage = func() {
// This message is copied from `gomobile bind -h`
fmt.Fprintf(os.Stderr, "%s bind [-target android|ios] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]", ebitenmobileCommand)
os.Exit(2)
}
flag.Parse()
}
func goEnv(name string) string {
if val := os.Getenv(name); val != "" {
return val
}
val, err := exec.Command("go", "env", name).Output()
if err != nil {
panic(err)
}
return strings.TrimSpace(string(val))
}
const (
// Copied from gomobile.
minAndroidAPI = 15
)
var (
buildA bool // -a
buildI bool // -i
buildN bool // -n
buildV bool // -v
buildX bool // -x
buildO string // -o
buildGcflags string // -gcflags
buildLdflags string // -ldflags
buildTarget string // -target
buildWork bool // -work
buildBundleID string // -bundleid
buildIOSVersion string // -iosversion
buildAndroidAPI int // -androidapi
buildTags string // -tags
bindPrefix string // -prefix
bindJavaPkg string // -javapkg
bindClasspath string // -classpath
bindBootClasspath string // -bootclasspath
)
func main() {
args := flag.Args()
if len(args) < 1 {
flag.Usage()
}
var flagset flag.FlagSet
flagset.StringVar(&buildO, "o", "", "")
flagset.StringVar(&buildGcflags, "gcflags", "", "")
flagset.StringVar(&buildLdflags, "ldflags", "", "")
flagset.StringVar(&buildTarget, "target", "android", "")
flagset.StringVar(&buildBundleID, "bundleid", "", "")
flagset.StringVar(&buildIOSVersion, "iosversion", "7.0", "")
flagset.StringVar(&buildTags, "tags", "", "")
flagset.IntVar(&buildAndroidAPI, "androidapi", minAndroidAPI, "")
flagset.BoolVar(&buildA, "a", false, "")
flagset.BoolVar(&buildI, "i", false, "")
flagset.BoolVar(&buildN, "n", false, "")
flagset.BoolVar(&buildV, "v", false, "")
flagset.BoolVar(&buildX, "x", false, "")
flagset.BoolVar(&buildWork, "work", false, "")
flagset.StringVar(&bindJavaPkg, "javapkg", "", "")
flagset.StringVar(&bindPrefix, "prefix", "", "")
flagset.StringVar(&bindClasspath, "classpath", "", "")
flagset.StringVar(&bindBootClasspath, "bootclasspath", "", "")
flagset.Parse(args[1:])
if err := prepareGomobileCommands(); err != nil {
log.Fatal(err)
}
switch args[0] {
case "bind":
if err := doBind(args, &flagset); err != nil {
log.Fatal(err)
}
default:
flag.Usage()
}
}
func doBind(args []string, flagset *flag.FlagSet) error {
pkgs, err := packages.Load(nil, flagset.Args()[0])
if err != nil {
return err
}
prefixLower := bindPrefix + pkgs[0].Name
prefixUpper := strings.Title(bindPrefix) + strings.Title(pkgs[0].Name)
args = append(args, "github.com/hajimehoshi/ebiten/mobile/ebitenmobileview")
if buildO == "" {
fmt.Fprintln(os.Stderr, "-o must be specified.")
os.Exit(2)
return nil
}
if buildN {
fmt.Print("gomobile")
for _, arg := range args {
fmt.Print(" ", arg)
}
fmt.Println()
return nil
}
cmd := exec.Command("gomobile", args...)
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "GO111MODULE=off")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
os.Exit(err.(*exec.ExitError).ExitCode())
return nil
}
replacePrefixes := func(content string) string {
content = strings.ReplaceAll(content, "{{.PrefixUpper}}", prefixUpper)
content = strings.ReplaceAll(content, "{{.PrefixLower}}", prefixLower)
return content
}
switch buildTarget {
case "android":
// Do nothing.
case "ios":
dir := filepath.Join(buildO, "Versions", "A")
if err := ioutil.WriteFile(filepath.Join(dir, "Headers", prefixUpper+"EbitenViewController.h"), []byte(replacePrefixes(objcH)), 0644); err != nil {
return err
}
// TODO: Remove 'Ebitenmobileview.objc.h' here. Now it is hard since there is a header file importing
// that header file.
fs, err := ioutil.ReadDir(filepath.Join(dir, "Headers"))
if err != nil {
return err
}
var headerFiles []string
for _, f := range fs {
if strings.HasSuffix(f.Name(), ".h") {
headerFiles = append(headerFiles, f.Name())
}
}
w, err := os.OpenFile(filepath.Join(dir, "Modules", "module.modulemap"), os.O_WRONLY, 0644)
if err != nil {
return err
}
defer w.Close()
var mmVals = struct {
Module string
Headers []string
}{
Module: prefixUpper,
Headers: headerFiles,
}
if err := iosModuleMapTmpl.Execute(w, mmVals); err != nil {
return err
}
// TODO: Remove Ebitenmobileview.objc.h?
}
return nil
}
const objcH = `// Code generated by ebitenmobile. DO NOT EDIT.
#import <UIKit/UIKit.h>
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
@end
`
var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
{{range .Headers}} header "{{.}}"
{{end}}
export *
}`))

1
go.mod
View File

@ -16,5 +16,6 @@ require (
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

7
go.sum
View File

@ -24,6 +24,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
@ -34,11 +35,17 @@ golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFm
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9 h1:m1yPOfTCyrqD6atBVpXfysvXc1bhSMdIxBu0JQAM7mQ=
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479 h1:lfN2PY/jymfnxkNHlbBF5DwPsUvhqUnrdgfK01iH2s0=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -1,113 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ebitenmobileview
// #cgo ios LDFLAGS: -framework UIKit -framework GLKit -framework QuartzCore -framework OpenGLES
//
// #include <stdint.h>
import "C"
import (
"math"
"runtime"
"sync"
"github.com/hajimehoshi/ebiten"
)
var theState state
// game is not exported since gomobile complains.
// TODO: Report this error.
type game interface {
Update(*ebiten.Image) error
Layout(viewWidth, viewHeight int) (screenWidth, screenHeight int)
}
type state struct {
game game
running bool
// m is a mutex required for each function.
// For example, on Android, Update can be called from a different thread:
// https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer
m sync.Mutex
}
func SetGame(game game) {
theState.m.Lock()
defer theState.m.Unlock()
theState.game = game
}
//export ebitenLayout
func ebitenLayout(viewWidth, viewHeight C.int, x, y, width, height *C.int) {
theState.m.Lock()
defer theState.m.Unlock()
if theState.game == nil {
panic("ebitenmobileview: SetGame must be called before ebitenLayout")
}
w, h := theState.game.Layout(int(viewWidth), int(viewHeight))
scaleX := float64(viewWidth) / float64(w)
scaleY := float64(viewHeight) / float64(h)
scale := math.Min(scaleX, scaleY)
*width = C.int(math.Ceil(float64(w) * scale))
*height = C.int(math.Ceil(float64(h) * scale))
*x = (viewWidth - *width) / 2
*y = (viewHeight - *height) / 2
if !theState.running {
start(theState.game.Update, w, h, scale)
theState.running = true
}
// TODO: call SetScreenSize
}
//export ebitenUpdate
func ebitenUpdate() *C.char {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
theState.m.Lock()
defer theState.m.Unlock()
if err := update(); err != nil {
// TODO: When to free cstr?
cstr := C.CString(err.Error())
return cstr
}
return nil
}
//export ebitenUpdateTouchesOnIOS
func ebitenUpdateTouchesOnIOS(phase C.int, ptr C.uintptr_t, x, y C.int) {
theState.m.Lock()
defer theState.m.Unlock()
updateTouchesOnIOSImpl(int(phase), int64(ptr), int(x), int(y))
}
//export ebitenUpdateTouchesOnAndroid
func ebitenUpdateTouchesOnAndroid(action C.int, id C.int, x, y C.int) {
theState.m.Lock()
defer theState.m.Unlock()
updateTouchesOnAndroid(int(action), int(id), int(x), int(y))
}

View File

@ -1,105 +0,0 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ios
#import <UIKit/UIKit.h>
#import <GLKit/GLkit.h>
#import "ebitenviewcontroller_ios.h"
#include "_cgo_export.h"
@implementation EbitenViewController {
GLKView* glkView_;
}
- (GLKView*)glkView {
if (!glkView_) {
glkView_ = [[GLKView alloc] init];
glkView_.multipleTouchEnabled = YES;
}
return glkView_;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.glkView.delegate = (id<GLKViewDelegate>)(self);
[self.view addSubview: self.glkView];
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[self glkView].context = context;
[EAGLContext setCurrentContext:context];
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
CGRect viewRect = [[self view] frame];
int x, y, width, height;
ebitenLayout(viewRect.size.width, viewRect.size.height, &x, &y, &width, &height);
CGRect glkViewRect = CGRectMake(x, y, width, height);
[[self glkView] setFrame:glkViewRect];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
// TODO: Notify this to Go world?
}
- (void)drawFrame{
[[self glkView] setNeedsDisplay];
}
- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
const char* err = ebitenUpdate();
if (err != nil) {
NSLog(@"Error: %s", err);
}
}
- (void)updateTouches:(NSSet*)touches {
for (UITouch* touch in touches) {
if (touch.view != [self glkView]) {
continue;
}
CGPoint location = [touch locationInView:touch.view];
ebitenUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
}
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
[self updateTouches:touches];
}
@end

View File

@ -0,0 +1,55 @@
// Copyright 2019 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ebitenmobileview
// #cgo ios LDFLAGS: -framework UIKit -framework GLKit -framework QuartzCore -framework OpenGLES
//
// #include <stdint.h>
import "C"
import (
"math"
"runtime"
"sync"
"github.com/hajimehoshi/ebiten"
)
var theState state
// game is not exported since gomobile complains.
// TODO: Report this error.
type game interface {
Update(*ebiten.Image) error
Layout(viewWidth, viewHeight int) (screenWidth, screenHeight int)
}
type state struct {
game game
running bool
// m is a mutex required for each function.
// For example, on Android, Update can be called from a different thread:
// https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer
m sync.Mutex
}
func SetGame(game game) {
theState.m.Lock()
defer theState.m.Unlock()
theState.game = game
}