Implement input in ui/glfw (#7)

This commit is contained in:
Hajime Hoshi 2014-12-06 03:10:17 +09:00
parent ca97ee6961
commit 7eb6b2f51f
14 changed files with 44 additions and 805 deletions

View File

@ -1,12 +0,0 @@
// -*- objc -*-
#ifndef GO_EBITEN_UI_COCOA_EBITEN_CONTROLLER_H_
#define GO_EBITEN_UI_COCOA_EBITEN_CONTROLLER_H_
#include <Cocoa/Cocoa.h>
@interface EbitenController : NSObject<NSApplicationDelegate>
@end
#endif

View File

@ -1,54 +0,0 @@
// -*- objc -*-
#include "ebiten_game_content_view.h"
#include "input.h"
void ebiten_KeyDown(NSWindow* nativeWindow, int keyCode);
void ebiten_KeyUp(NSWindow* nativeWindow, int keyCode);
void ebiten_MouseStateUpdated(NSWindow* nativeWindow, InputType inputType, int x, int y);
@implementation EbitenGameContentView {
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)isFlipped {
return YES;
}
- (void)keyDown:(NSEvent*)theEvent {
ebiten_KeyDown([self window], [theEvent keyCode]);
}
- (void)keyUp:(NSEvent*)theEvent {
ebiten_KeyUp([self window], [theEvent keyCode]);
}
- (void)mouseDown:(NSEvent*)theEvent {
NSPoint location = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
int x = location.x;
int y = location.y;
ebiten_MouseStateUpdated([self window], InputTypeMouseDown, x, y);
}
- (void)mouseUp:(NSEvent*)theEvent {
(void)theEvent;
NSPoint location = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
int x = location.x;
int y = location.y;
ebiten_MouseStateUpdated([self window], InputTypeMouseUp, x, y);
}
- (void)mouseDragged:(NSEvent*)theEvent {
NSPoint location = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
int x = location.x;
int y = location.y;
ebiten_MouseStateUpdated([self window], InputTypeMouseDragged, x, y);
}
@end

View File

@ -1,13 +0,0 @@
// -*- objc -*-
#ifndef GO_EBITEN_UI_COCOA_EBITEN_GAME_CONTENT_VIEW_H_
#define GO_EBITEN_UI_COCOA_EBITEN_GAME_CONTENT_VIEW_H_
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
@interface EbitenGameContentView : NSView
@end
#endif

View File

@ -1,88 +0,0 @@
// -*- objc -*-
#import "ebiten_game_window.h"
#import "ebiten_game_content_view.h"
@class NSOpenGLContext;
void ebiten_WindowClosed(void* nativeWindow);
@implementation EbitenGameWindow {
@private
NSOpenGLContext* glContext_;
}
- (id)initWithSize:(NSSize)size
glContext:(NSOpenGLContext*)glContext {
self->glContext_ = glContext;
[self->glContext_ retain];
NSUInteger style = (NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask);
NSRect windowRect =
[NSWindow frameRectForContentRect:NSMakeRect(0, 0, size.width, size.height)
styleMask:style];
NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
NSSize screenSize = [screen visibleFrame].size;
NSRect contentRect = NSMakeRect(0, 0, size.width, size.height);
self = [super initWithContentRect:contentRect
styleMask:style
backing:NSBackingStoreBuffered
defer:YES];
if (self != nil) {
[self center];
[self setReleasedWhenClosed:YES];
[self setDelegate:self];
[self setDocumentEdited:YES];
NSRect rect = NSMakeRect(0, 0, size.width, size.height);
NSView* contentView = [[EbitenGameContentView alloc] initWithFrame:rect];
[self setContentView:contentView];
[contentView release];
}
return self;
}
- (void)dealloc {
[self->glContext_ release];
[super dealloc];
}
- (NSOpenGLContext*)glContext {
return self->glContext_;
}
- (BOOL)windowShouldClose:(id)sender {
if ([sender isDocumentEdited]) {
// TODO: add the application's name
NSAlert* alert = [NSAlert new];
[alert setMessageText:@"Quit the game?"];
[alert addButtonWithTitle:@"Quit"];
[alert addButtonWithTitle:@"Cancel"];
[alert setAlertStyle:NSWarningAlertStyle];
SEL selector = @selector(alertDidEnd:returnCode:contextInfo:);
[alert beginSheetModalForWindow:sender
modalDelegate:self
didEndSelector:selector
contextInfo:nil];
[alert release];
}
return NO;
}
- (void)alertDidEnd:(NSAlert*)alert
returnCode:(NSInteger)returnCode
contextInfo:(void*)contextInfo {
if (returnCode == NSAlertFirstButtonReturn) {
[self close];
ebiten_WindowClosed(self);
}
}
- (BOOL)canBecomeMainWindow {
return YES;
}
@end

View File

@ -1,16 +0,0 @@
// -*- objc -*-
#ifndef GO_EBITEN_UI_COCOA_EBITEN_GAME_WINDOW_H_
#define GO_EBITEN_UI_COCOA_EBITEN_GAME_WINDOW_H_
#import <Cocoa/Cocoa.h>
@interface EbitenGameWindow : NSWindow<NSWindowDelegate>
- (id)initWithSize:(NSSize)size
glContext:(NSOpenGLContext*)glContext;
- (NSOpenGLContext*)glContext;
@end
#endif

View File

@ -1,268 +0,0 @@
package cocoa
// #include <stdlib.h>
//
// #include "input.h"
//
// @class EbitenGameWindow;
// @class NSOpenGLContext;
//
// typedef EbitenGameWindow* EbitenGameWindowPtr;
//
// EbitenGameWindow* CreateGameWindow(size_t width, size_t height, const char* title, NSOpenGLContext* glContext);
// NSOpenGLContext* CreateGLContext(NSOpenGLContext* sharedGLContext);
//
// void UseGLContext(NSOpenGLContext* glContext);
// void UnuseGLContext(void);
//
import "C"
import (
"github.com/hajimehoshi/ebiten/graphics"
"github.com/hajimehoshi/ebiten/graphics/opengl"
"github.com/hajimehoshi/ebiten/ui"
"runtime"
"sync"
"time"
"unsafe"
)
type Keys map[ui.Key]struct{}
func newKeys() Keys {
return Keys(map[ui.Key]struct{}{})
}
func (k Keys) clone() Keys {
n := newKeys()
for key, value := range k {
n[key] = value
}
return n
}
func (k Keys) add(key ui.Key) {
k[key] = struct{}{}
}
func (k Keys) remove(key ui.Key) {
delete(k, key)
}
func (k Keys) Includes(key ui.Key) bool {
_, ok := k[key]
return ok
}
type InputState struct {
pressedKeys Keys
mouseX int
mouseY int
}
func (i *InputState) PressedKeys() ui.Keys {
return i.pressedKeys
}
func (i *InputState) MouseX() int {
return i.mouseX
}
func (i *InputState) MouseY() int {
return i.mouseY
}
func (i *InputState) setMouseXY(x, y int) {
i.mouseX = x
i.mouseY = y
}
type GameWindow struct {
width int
height int
scale int
isClosed bool
inputState *InputState
title string
native *C.EbitenGameWindow
funcs chan func(*opengl.Context)
funcsDone chan struct{}
closed chan struct{}
sync.RWMutex
}
var windows = map[*C.EbitenGameWindow]*GameWindow{}
func newGameWindow(width, height, scale int, title string) *GameWindow {
inputState := &InputState{
pressedKeys: newKeys(),
mouseX: -1,
mouseY: -1,
}
return &GameWindow{
width: width,
height: height,
scale: scale,
inputState: inputState,
title: title,
funcs: make(chan func(*opengl.Context)),
funcsDone: make(chan struct{}),
closed: make(chan struct{}),
}
}
func (w *GameWindow) IsClosed() bool {
w.RLock()
defer w.RUnlock()
return w.isClosed
}
func (w *GameWindow) run(sharedGLContext *C.NSOpenGLContext) {
cTitle := C.CString(w.title)
defer C.free(unsafe.Pointer(cTitle))
ch := make(chan struct{})
go func() {
runtime.LockOSThread()
glContext := C.CreateGLContext(sharedGLContext)
w.native = C.CreateGameWindow(
C.size_t(w.width*w.scale),
C.size_t(w.height*w.scale),
cTitle,
glContext)
windows[w.native] = w
close(ch)
C.UseGLContext(glContext)
context := opengl.NewContext(
w.width, w.height, w.scale)
C.UnuseGLContext()
defer func() {
C.UseGLContext(glContext)
context.Dispose()
C.UnuseGLContext()
}()
w.loop(context, glContext)
}()
<-ch
}
func (w *GameWindow) loop(context *opengl.Context, glContext *C.NSOpenGLContext) {
for {
select {
case <-w.closed:
return
case f := <-w.funcs:
// Wait 10 millisecond at least to avoid busy loop.
after := time.After(time.Duration(int64(time.Millisecond) * 10))
C.UseGLContext(glContext)
f(context)
C.UnuseGLContext()
<-after
w.funcsDone <- struct{}{}
}
}
}
func (w *GameWindow) Draw(f func(graphics.Context)) {
select {
case <-w.closed:
return
default:
}
w.useGLContext(func(context *opengl.Context) {
context.Update(f)
})
}
func (w *GameWindow) useGLContext(f func(*opengl.Context)) {
w.funcs <- f
<-w.funcsDone
}
func (w *GameWindow) InputState() ui.InputState {
w.RLock()
defer w.RUnlock()
return &InputState{
pressedKeys: w.inputState.pressedKeys.clone(),
mouseX: w.inputState.mouseX,
mouseY: w.inputState.mouseY,
}
}
var cocoaKeyCodeToKey = map[int]ui.Key{
49: ui.KeySpace,
123: ui.KeyLeft,
124: ui.KeyRight,
125: ui.KeyDown,
126: ui.KeyUp,
}
//export ebiten_KeyDown
func ebiten_KeyDown(nativeWindow C.EbitenGameWindowPtr, keyCode int) {
key, ok := cocoaKeyCodeToKey[keyCode]
if !ok {
return
}
w := windows[nativeWindow]
w.Lock()
defer w.Unlock()
w.inputState.pressedKeys.add(key)
}
//export ebiten_KeyUp
func ebiten_KeyUp(nativeWindow C.EbitenGameWindowPtr, keyCode int) {
key, ok := cocoaKeyCodeToKey[keyCode]
if !ok {
return
}
w := windows[nativeWindow]
w.Lock()
defer w.Unlock()
w.inputState.pressedKeys.remove(key)
}
//export ebiten_MouseStateUpdated
func ebiten_MouseStateUpdated(nativeWindow C.EbitenGameWindowPtr, inputType C.InputType, cx, cy C.int) {
w := windows[nativeWindow]
if inputType == C.InputTypeMouseUp {
w.Lock()
defer w.Unlock()
w.inputState.setMouseXY(-1, -1)
return
}
x, y := int(cx), int(cy)
x /= w.scale
y /= w.scale
if x < 0 {
x = 0
} else if w.width <= x {
x = w.width - 1
}
if y < 0 {
y = 0
} else if w.height <= y {
y = w.height - 1
}
w.Lock()
defer w.Unlock()
w.inputState.setMouseXY(x, y)
}
//export ebiten_WindowClosed
func ebiten_WindowClosed(nativeWindow C.EbitenGameWindowPtr) {
w := windows[nativeWindow]
close(w.closed)
w.Lock()
defer w.Unlock()
w.isClosed = true
delete(windows, nativeWindow)
}

View File

@ -1,10 +0,0 @@
#ifndef GO_EBITEN_UI_COCOA_INPUT_H_
#define GO_EBITEN_UI_COCOA_INPUT_H_
typedef enum {
InputTypeMouseUp,
InputTypeMouseDragged,
InputTypeMouseDown,
} InputType;
#endif

View File

@ -1,106 +0,0 @@
// -*- objc -*-
#include <stdlib.h>
#include <OpenGL/gl.h>
#import "ebiten_game_window.h"
static NSAutoreleasePool* pool = NULL;
void initMenu(void) {
NSString* processName = [[NSProcessInfo processInfo] processName];
NSMenu* menuBar = [NSMenu new];
[NSApp setMainMenu: menuBar];
[menuBar release];
NSMenuItem* rootMenuItem = [NSMenuItem new];
[menuBar addItem:rootMenuItem];
[rootMenuItem release];
NSMenu* appMenu = [NSMenu new];
[rootMenuItem setSubmenu:appMenu];
[appMenu release];
[appMenu addItemWithTitle:[@"Quit " stringByAppendingString:processName]
action:@selector(performClose:)
keyEquivalent:@"q"];
}
void StartApplication(void) {
pool = [NSAutoreleasePool new];
NSApplication* app = [NSApplication sharedApplication];
[app setActivationPolicy:NSApplicationActivationPolicyRegular];
initMenu();
[app finishLaunching];
[NSApp activateIgnoringOtherApps:YES];
}
void DoEvents(void) {
for (;;) {
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event == nil) {
break;
}
[NSApp sendEvent:event];
}
[pool drain];
pool = [NSAutoreleasePool new];
}
void TerminateApplication(void) {
[pool drain];
}
NSOpenGLContext* CreateGLContext(NSOpenGLContext* sharedGLContext) {
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFAWindow,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
NSOpenGLPFADepthSize, 32,
0,
};
NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc]
initWithAttributes:attributes];
NSOpenGLContext* glContext =
[[NSOpenGLContext alloc] initWithFormat:format
shareContext:sharedGLContext];
[format release];
return glContext;
}
EbitenGameWindow* CreateGameWindow(size_t width, size_t height, const char* title, NSOpenGLContext* glContext) {
NSSize size = NSMakeSize(width, height);
EbitenGameWindow* window = [[EbitenGameWindow alloc]
initWithSize:size
glContext:glContext];
[glContext release];
NSString* nsTitle = [[NSString alloc]
initWithUTF8String:title];
[window setTitle: nsTitle];
[nsTitle release];
[window makeKeyAndOrderFront:nil];
[glContext setView:[window contentView]];
return window;
}
void UseGLContext(NSOpenGLContext* glContext) {
CGLContextObj cglContext = [glContext CGLContextObj];
CGLLockContext(cglContext);
[glContext makeCurrentContext];
}
void UnuseGLContext(void) {
NSOpenGLContext* glContext = [NSOpenGLContext currentContext];
[glContext flushBuffer];
[NSOpenGLContext clearCurrentContext];
CGLContextObj cglContext = [glContext CGLContextObj];
CGLUnlockContext(cglContext);
}

View File

@ -1,96 +0,0 @@
package cocoa
// @class NSOpenGLContext;
//
// NSOpenGLContext* CreateGLContext(NSOpenGLContext* sharedGLContext);
// void UseGLContext(NSOpenGLContext* glContext);
// void UnuseGLContext(void);
//
import "C"
import (
"github.com/hajimehoshi/ebiten/graphics"
"github.com/hajimehoshi/ebiten/graphics/opengl"
"image"
"runtime"
)
type sharedContext struct {
inited chan struct{}
funcs chan func()
funcsDone chan struct{}
gameWindows chan *GameWindow
}
func newSharedContext() *sharedContext {
return &sharedContext{
inited: make(chan struct{}),
funcs: make(chan func()),
funcsDone: make(chan struct{}),
gameWindows: make(chan *GameWindow),
}
}
func (t *sharedContext) run() {
var sharedGLContext *C.NSOpenGLContext
go func() {
runtime.LockOSThread()
sharedGLContext = C.CreateGLContext(nil)
close(t.inited)
t.loop(sharedGLContext)
}()
<-t.inited
go func() {
for w := range t.gameWindows {
w.run(sharedGLContext)
}
}()
}
func (t *sharedContext) loop(sharedGLContext *C.NSOpenGLContext) {
for {
select {
case f := <-t.funcs:
C.UseGLContext(sharedGLContext)
f()
C.UnuseGLContext()
t.funcsDone <- struct{}{}
}
}
}
func (t *sharedContext) useGLContext(f func()) {
t.funcs <- f
<-t.funcsDone
}
func (t *sharedContext) createGameWindow(width, height, scale int, title string) *GameWindow {
w := newGameWindow(width, height, scale, title)
go func() {
t.gameWindows <- w
}()
return w
}
func (t *sharedContext) CreateTexture(
img image.Image,
filter graphics.Filter) (graphics.TextureId, error) {
<-t.inited
var id graphics.TextureId
var err error
t.useGLContext(func() {
id, err = opengl.CreateTexture(img, filter)
})
return id, err
}
func (t *sharedContext) CreateRenderTarget(
width, height int,
filter graphics.Filter) (graphics.RenderTargetId, error) {
<-t.inited
var id graphics.RenderTargetId
var err error
t.useGLContext(func() {
id, err = opengl.CreateRenderTarget(width, height, filter)
})
return id, err
}

View File

@ -1,57 +0,0 @@
package cocoa
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Cocoa -framework OpenGL
//
// void StartApplication(void);
// void DoEvents(void);
// void TerminateApplication(void);
//
import "C"
import (
"github.com/hajimehoshi/ebiten/graphics"
"github.com/hajimehoshi/ebiten/ui"
)
type cocoaUI struct {
sharedContext *sharedContext
}
var currentUI *cocoaUI
func getCurrentUI() *cocoaUI {
if currentUI != nil {
return currentUI
}
currentUI = &cocoaUI{}
currentUI.sharedContext = newSharedContext()
return currentUI
}
func UI() ui.UI {
return getCurrentUI()
}
func TextureFactory() graphics.TextureFactory {
return getCurrentUI().sharedContext
}
func (u *cocoaUI) CreateCanvas(width, height, scale int, title string) ui.Canvas {
return u.sharedContext.createGameWindow(width, height, scale, title)
}
func (u *cocoaUI) DoEvents() {
C.DoEvents()
}
func (u *cocoaUI) Start() {
C.StartApplication()
currentUI.sharedContext.run()
}
func (u *cocoaUI) Terminate() {
// TODO: Close existing windows
C.TerminateApplication()
}

View File

@ -1,65 +0,0 @@
package dummy
import (
"github.com/hajimehoshi/ebiten/graphics"
"github.com/hajimehoshi/ebiten/ui"
"image"
)
type TextureFactory struct{}
func (t *TextureFactory) CreateRenderTarget(width, height int, filter graphics.Filter) (graphics.RenderTargetId, error) {
return 0, nil
}
func (t *TextureFactory) CreateTexture(img image.Image, filter graphics.Filter) (graphics.TextureId, error) {
return 0, nil
}
type UI struct{}
func (u *UI) CreateCanvas(widht, height, scale int, title string) ui.Canvas {
return &Canvas{}
}
func (u *UI) Start() {
}
func (u *UI) DoEvents() {
}
func (u *UI)Terminate() {
}
type Keys struct{}
func (k *Keys) Includes(key ui.Key) bool {
return false
}
type InputState struct{}
func (i *InputState) PressedKeys() ui.Keys {
return &Keys{}
}
func (i *InputState) MouseX() int {
return -1
}
func (i *InputState) MouseY() int {
return -1
}
type Canvas struct{}
func (c *Canvas) Draw(func(graphics.Context)) {
}
func (c *Canvas) IsClosed() bool {
return true
}
func (c *Canvas) InputState() ui.InputState {
return &InputState{}
}

View File

@ -10,10 +10,11 @@ import (
) )
type Canvas struct { type Canvas struct {
window *glfw.Window window *glfw.Window
context *opengl.Context inputState *InputState
funcs chan func() context *opengl.Context
funcsDone chan struct{} funcs chan func()
funcsDone chan struct{}
} }
func NewCanvas(width, height, scale int, title string) *Canvas { func NewCanvas(width, height, scale int, title string) *Canvas {
@ -22,15 +23,15 @@ func NewCanvas(width, height, scale int, title string) *Canvas {
panic(err) panic(err)
} }
canvas := &Canvas{ canvas := &Canvas{
window: window, window: window,
funcs: make(chan func()), inputState: newInputState(),
funcsDone: make(chan struct{}), funcs: make(chan func()),
funcsDone: make(chan struct{}),
} }
// For retina displays, recalculate the scale with the framebuffer size. // For retina displays, recalculate the scale with the framebuffer size.
windowWidth, windowHeight := window.GetFramebufferSize() windowWidth, _ := window.GetFramebufferSize()
realScale := windowWidth / width realScale := windowWidth / width
_ = windowHeight
canvas.run() canvas.run()
canvas.use(func() { canvas.use(func() {
@ -51,7 +52,7 @@ func (c *Canvas) IsClosed() bool {
} }
func (c *Canvas) InputState() ui.InputState { func (c *Canvas) InputState() ui.InputState {
return &InputState{newKeys(), -1, -1} return c.inputState
} }
func (c *Canvas) CreateTexture(img image.Image, filter graphics.Filter) (graphics.TextureId, error) { func (c *Canvas) CreateTexture(img image.Image, filter graphics.Filter) (graphics.TextureId, error) {
@ -89,3 +90,7 @@ func (c *Canvas) use(f func()) {
c.funcs <- f c.funcs <- f
<-c.funcsDone <-c.funcsDone
} }
func (c *Canvas) update() {
c.inputState.update(c.window)
}

View File

@ -1,6 +1,7 @@
package glfw package glfw
import ( import (
glfw "github.com/go-gl/glfw3"
"github.com/hajimehoshi/ebiten/ui" "github.com/hajimehoshi/ebiten/ui"
) )
@ -10,14 +11,6 @@ func newKeys() Keys {
return Keys(map[ui.Key]struct{}{}) return Keys(map[ui.Key]struct{}{})
} }
func (k Keys) clone() Keys {
n := newKeys()
for key, value := range k {
n[key] = value
}
return n
}
func (k Keys) add(key ui.Key) { func (k Keys) add(key ui.Key) {
k[key] = struct{}{} k[key] = struct{}{}
} }
@ -37,6 +30,14 @@ type InputState struct {
mouseY int mouseY int
} }
func newInputState() *InputState {
return &InputState{
pressedKeys: newKeys(),
mouseX: -1,
mouseY: -1,
}
}
func (i *InputState) PressedKeys() ui.Keys { func (i *InputState) PressedKeys() ui.Keys {
return i.pressedKeys return i.pressedKeys
} }
@ -48,3 +49,21 @@ func (i *InputState) MouseX() int {
func (i *InputState) MouseY() int { func (i *InputState) MouseY() int {
return i.mouseY return i.mouseY
} }
var glfwKeyCodeToKey = map[glfw.Key]ui.Key{
glfw.KeySpace: ui.KeySpace,
glfw.KeyLeft: ui.KeyLeft,
glfw.KeyRight: ui.KeyRight,
glfw.KeyUp: ui.KeyUp,
glfw.KeyDown: ui.KeyDown,
}
func (i *InputState) update(window *glfw.Window) {
for g, u := range glfwKeyCodeToKey {
if window.GetKey(g) == glfw.Press {
i.pressedKeys.add(u)
} else {
i.pressedKeys.remove(u)
}
}
}

View File

@ -22,7 +22,6 @@ func (u *UI) CreateCanvas(width, height, scale int, title string) ui.Canvas {
panic("glfw.Init() fails") panic("glfw.Init() fails")
} }
glfw.WindowHint(glfw.Resizable, glfw.False) glfw.WindowHint(glfw.Resizable, glfw.False)
//glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI)
u.canvas = NewCanvas(width, height, scale, title) u.canvas = NewCanvas(width, height, scale, title)
return u.canvas return u.canvas
} }
@ -31,7 +30,8 @@ func (u *UI) Start() {
} }
func (u *UI) DoEvents() { func (u *UI) DoEvents() {
glfw.PollEvents() glfw.PollEvents()
u.canvas.update()
} }
func (u *UI) Terminate() { func (u *UI) Terminate() {