710 lines
16 KiB
Objective-C
710 lines
16 KiB
Objective-C
/*
|
|
|
|
MyOpenGLView.m
|
|
|
|
Oolite
|
|
Copyright (C) 2004-2008 Giles C Williams and contributors
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#import "MyOpenGLView.h"
|
|
|
|
#import "GameController.h"
|
|
#import "Universe.h"
|
|
#import "Entity.h"
|
|
#import "PlanetEntity.h"
|
|
#import "ResourceManager.h"
|
|
#import "GuiDisplayGen.h"
|
|
#import <Carbon/Carbon.h>
|
|
#import "JoystickHandler.h"
|
|
|
|
|
|
static NSString * kOOLogKeyCodeOutOfRange = @"input.keyMapping.codeOutOfRange";
|
|
static NSString * kOOLogKeyUp = @"input.keyMapping.keyPress.keyUp";
|
|
static NSString * kOOLogKeyDown = @"input.keyMapping.keyPress.keyDown";
|
|
|
|
|
|
@interface MyOpenGLView(Internal)
|
|
|
|
- (int) translateKeyCode: (int) input;
|
|
- (void)performLateSetup;
|
|
|
|
@end
|
|
|
|
|
|
@implementation MyOpenGLView
|
|
|
|
- (id) initWithFrame:(NSRect)frameRect
|
|
{
|
|
// Pixel Format Attributes for the View-based (non-FullScreen) NSOpenGLContext
|
|
NSOpenGLPixelFormatAttribute attrs[] =
|
|
{
|
|
// // Specify that we want a full-screen OpenGL context.
|
|
// NSOpenGLPFAFullScreen,
|
|
// and that we want a windowed OpenGL context.
|
|
NSOpenGLPFAWindow,
|
|
|
|
// We may be on a multi-display system (and each screen may be driven by a different renderer), so we need to specify which screen we want to take over.
|
|
// For this demo, we'll specify the main screen.
|
|
NSOpenGLPFAScreenMask, CGDisplayIDToOpenGLDisplayMask(kCGDirectMainDisplay),
|
|
|
|
// Specifying "NoRecovery" gives us a context that cannot fall back to the software renderer.
|
|
//This makes the View-based context a compatible with the fullscreen context, enabling us to use the "shareContext"
|
|
// feature to share textures, display lists, and other OpenGL objects between the two.
|
|
NSOpenGLPFANoRecovery,
|
|
|
|
// Attributes Common to FullScreen and non-FullScreen
|
|
NSOpenGLPFACompliant,
|
|
|
|
NSOpenGLPFAColorSize, 32,
|
|
NSOpenGLPFADepthSize, 32,
|
|
NSOpenGLPFADoubleBuffer,
|
|
NSOpenGLPFAAccelerated,
|
|
0
|
|
};
|
|
|
|
// Create our non-FullScreen pixel format.
|
|
NSOpenGLPixelFormat* pixelFormat = [[[NSOpenGLPixelFormat alloc] initWithAttributes:attrs] autorelease];
|
|
|
|
self = [super initWithFrame:frameRect pixelFormat:pixelFormat];
|
|
|
|
virtualJoystickPosition = NSMakePoint(0.0,0.0);
|
|
|
|
typedString = [[NSMutableString alloc] initWithString:@""];
|
|
allowingStringInput = gvStringInputNo;
|
|
isAlphabetKeyDown = NO;
|
|
|
|
timeIntervalAtLastClick = [NSDate timeIntervalSinceReferenceDate];
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc
|
|
{
|
|
if (typedString)
|
|
[typedString release];
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
- (void) setStringInput: (enum StringInput) value
|
|
{
|
|
allowingStringInput = value;
|
|
}
|
|
|
|
|
|
- (void) allowStringInput: (BOOL) value
|
|
{
|
|
if (value)
|
|
allowingStringInput = gvStringInputAlpha;
|
|
else
|
|
allowingStringInput = gvStringInputNo;
|
|
}
|
|
|
|
-(enum StringInput) allowingStringInput
|
|
{
|
|
return allowingStringInput;
|
|
}
|
|
|
|
|
|
- (NSString *) typedString
|
|
{
|
|
return typedString;
|
|
}
|
|
|
|
|
|
- (void) resetTypedString
|
|
{
|
|
[typedString setString:@""];
|
|
}
|
|
|
|
|
|
- (void) setTypedString:(NSString*) value
|
|
{
|
|
[typedString setString:value];
|
|
}
|
|
|
|
|
|
- (NSSize) viewSize
|
|
{
|
|
return viewSize;
|
|
}
|
|
|
|
|
|
- (GLfloat) display_z
|
|
{
|
|
return display_z;
|
|
}
|
|
|
|
|
|
- (GameController *)gameController
|
|
{
|
|
return gameController;
|
|
}
|
|
|
|
|
|
- (void) setGameController:(GameController *) controller
|
|
{
|
|
gameController = controller;
|
|
}
|
|
|
|
|
|
- (void) updateScreen
|
|
{
|
|
[self drawRect:NSMakeRect(0, 0, viewSize.width, viewSize.height)];
|
|
}
|
|
|
|
|
|
- (void) drawRect:(NSRect)rect
|
|
{
|
|
if ((viewSize.width != [self frame].size.width)||(viewSize.height != [self frame].size.height)) // resized
|
|
{
|
|
m_glContextInitialized = NO;
|
|
viewSize = [self frame].size;
|
|
}
|
|
|
|
if (!m_glContextInitialized) [self initialiseGLWithSize:viewSize];
|
|
|
|
// do all the drawing!
|
|
if (UNIVERSE != nil) [UNIVERSE drawUniverse];
|
|
else
|
|
{
|
|
// not set up yet, draw a black screen
|
|
glClearColor(0.0, 0.0, 0.0, 0.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
[[self openGLContext] flushBuffer];
|
|
}
|
|
|
|
|
|
- (void) initialiseGLWithSize:(NSSize) v_size
|
|
{
|
|
GLfloat sun_ambient[] = {0.0, 0.0, 0.0, 1.0};
|
|
GLfloat sun_diffuse[] = {1.0, 1.0, 1.0, 1.0};
|
|
GLfloat sun_specular[] = {1.0, 1.0, 1.0, 1.0};
|
|
GLfloat sun_center_position[] = {4000000.0, 0.0, 0.0, 1.0};
|
|
GLfloat stars_ambient[] = {0.25, 0.2, 0.25, 1.0};
|
|
|
|
viewSize = v_size;
|
|
if (viewSize.width/viewSize.height > 4.0/3.0)
|
|
display_z = 480.0 * viewSize.width/viewSize.height;
|
|
else
|
|
display_z = 640.0;
|
|
|
|
float ratio = 0.5;
|
|
float aspect = viewSize.height/viewSize.width;
|
|
|
|
glShadeModel(GL_FLAT);
|
|
glClearColor(0.0, 0.0, 0.0, 0.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
[[self openGLContext] flushBuffer];
|
|
|
|
glClearDepth(MAX_CLEAR_DEPTH);
|
|
glViewport( 0, 0, viewSize.width, viewSize.height);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity(); // reset matrix
|
|
glFrustum( -ratio, ratio, -aspect*ratio, aspect*ratio, 1.0, MAX_CLEAR_DEPTH); // set projection matrix
|
|
|
|
glMatrixMode( GL_MODELVIEW);
|
|
|
|
glEnable( GL_DEPTH_TEST); // depth buffer
|
|
glDepthFunc( GL_LESS); // depth buffer
|
|
|
|
glFrontFace( GL_CCW); // face culling - front faces are AntiClockwise!
|
|
glCullFace( GL_BACK); // face culling
|
|
glEnable( GL_CULL_FACE); // face culling
|
|
|
|
glEnable( GL_BLEND); // alpha blending
|
|
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha blending
|
|
|
|
if (UNIVERSE)
|
|
{
|
|
[UNIVERSE setLighting];
|
|
}
|
|
else
|
|
{
|
|
glLightfv(GL_LIGHT1, GL_AMBIENT, sun_ambient);
|
|
glLightfv(GL_LIGHT1, GL_SPECULAR, sun_specular);
|
|
glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_diffuse);
|
|
glLightfv(GL_LIGHT1, GL_POSITION, sun_center_position);
|
|
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, stars_ambient);
|
|
|
|
glEnable(GL_LIGHT1); // lighting
|
|
|
|
}
|
|
glEnable(GL_LIGHTING); // lighting
|
|
|
|
|
|
// world's simplest OpenGL optimisations...
|
|
#if GL_APPLE_transform_hint
|
|
glHint(GL_TRANSFORM_HINT_APPLE, GL_FASTEST);
|
|
#endif
|
|
|
|
glDisable(GL_NORMALIZE);
|
|
glDisable(GL_RESCALE_NORMAL);
|
|
|
|
m_glContextInitialized = YES;
|
|
}
|
|
|
|
|
|
- (void) snapShot
|
|
{
|
|
int w = viewSize.width;
|
|
int h = viewSize.height;
|
|
|
|
if (w & 3)
|
|
w = w + 4 - (w & 3);
|
|
|
|
long nPixels = w * h + 1;
|
|
|
|
unsigned char *red = (unsigned char *) malloc( nPixels);
|
|
unsigned char *green = (unsigned char *) malloc( nPixels);
|
|
unsigned char *blue = (unsigned char *) malloc( nPixels);
|
|
|
|
NSString *filepath = [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent];
|
|
int imageNo = 0;
|
|
NSString *pathToPic = nil;
|
|
|
|
do
|
|
{
|
|
imageNo++;
|
|
pathToPic = [filepath stringByAppendingPathComponent:[NSString stringWithFormat:@"oolite-%03d.png",imageNo]];
|
|
} while ([[NSFileManager defaultManager] fileExistsAtPath:pathToPic]);
|
|
|
|
OOLog(@"snapshot", @">>>>> Snapshot %d x %d file path chosen = %@", w, h, pathToPic);
|
|
|
|
NSBitmapImageRep* bitmapRep =
|
|
[[NSBitmapImageRep alloc]
|
|
initWithBitmapDataPlanes:NULL // --> let the class allocate it
|
|
pixelsWide: w
|
|
pixelsHigh: h
|
|
bitsPerSample: 8 // each component is 8 bits (1 byte)
|
|
samplesPerPixel: 3 // number of components (R, G, B)
|
|
hasAlpha: NO // no transparency
|
|
isPlanar: NO // data integrated into single plane
|
|
colorSpaceName: NSDeviceRGBColorSpace
|
|
bytesPerRow: 3*w // can no longer let the class figure it out
|
|
bitsPerPixel: 24 // can no longer let the class figure it out
|
|
];
|
|
|
|
unsigned char *pixels = [bitmapRep bitmapData];
|
|
|
|
glReadPixels(0,0, w,h, GL_RED, GL_UNSIGNED_BYTE, red);
|
|
glReadPixels(0,0, w,h, GL_GREEN, GL_UNSIGNED_BYTE, green);
|
|
glReadPixels(0,0, w,h, GL_BLUE, GL_UNSIGNED_BYTE, blue);
|
|
|
|
int x,y;
|
|
for (y = 0; y < h; y++)
|
|
{
|
|
long index = (h - y - 1)*w;
|
|
for (x = 0; x < w; x++) // set bitmap pixels
|
|
{
|
|
*pixels++ = red[index];
|
|
*pixels++ = green[index];
|
|
*pixels++ = blue[index++];
|
|
}
|
|
}
|
|
|
|
[[bitmapRep representationUsingType:NSPNGFileType properties:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSImageInterlaced, NULL]]
|
|
writeToFile:pathToPic atomically:YES]; // save PNG representation of image
|
|
|
|
// free allocated objects and memory
|
|
[bitmapRep release];
|
|
free(red);
|
|
free(green);
|
|
free(blue);
|
|
|
|
}
|
|
|
|
|
|
- (BOOL) acceptsFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (void) keyUp:(NSEvent *)theEvent
|
|
{
|
|
NSString *stringValue = nil;
|
|
int key;
|
|
int keycode;
|
|
|
|
stringValue = [theEvent charactersIgnoringModifiers];
|
|
|
|
/* Bug: exception when releasing accent key.
|
|
Analysis: Dead keys (accents and similar) return an empty string.
|
|
Fix: reject zero-length strings. This is the Wrong Thing - we should
|
|
really be using KeyTranslate()/UCKeyTranslate() to find out what the
|
|
string would be if you pressed the key and then space.
|
|
-- Ahruman 20070714
|
|
*/
|
|
if ([stringValue length] < 1) return;
|
|
|
|
key = [stringValue characterAtIndex:0];
|
|
keycode = [theEvent keyCode] & 255;
|
|
|
|
supressKeys = NO;
|
|
|
|
key = keycodetrans[keycode]; // retrieve the character we got for pressing the hardware at key location 'keycode'
|
|
|
|
OOLog(kOOLogKeyUp, @"Key up: stringValue = \"%@\", keyCode = %d, key = %u", stringValue, keycode, key);
|
|
|
|
isAlphabetKeyDown = NO;
|
|
if ((key >= 0)&&(key < [self numKeys])&&(keys[key]))
|
|
{
|
|
keys[key] = NO;
|
|
}
|
|
else
|
|
{
|
|
if (key > [self numKeys])
|
|
OOLog(kOOLogKeyCodeOutOfRange, @"Translated key: %d out of range", key);
|
|
}
|
|
}
|
|
|
|
|
|
- (void) keyDown:(NSEvent *)theEvent
|
|
{
|
|
NSString *stringValue = nil;
|
|
int key;
|
|
int keycode;
|
|
|
|
stringValue = [theEvent charactersIgnoringModifiers];
|
|
|
|
/* Bug: exception when pressing accent key.
|
|
Analysis: Dead keys (accents and similar) return an empty string.
|
|
Fix: reject zero-length strings. This is the Wrong Thing - we should
|
|
really be using KeyTranslate()/UCKeyTranslate() to find out what the
|
|
string would be if you pressed the key and then space.
|
|
-- Ahruman 20070714
|
|
*/
|
|
if ([stringValue length] < 1) return;
|
|
|
|
key = [stringValue characterAtIndex:0];
|
|
keycode = [theEvent keyCode] & 255;
|
|
|
|
key = [self translateKeyCode:key];
|
|
|
|
OOLog(kOOLogKeyDown, @"Key down: stringValue = \"%@\", keyCode = %d, key = %u", stringValue, keycode, key);
|
|
|
|
keycodetrans[keycode] = key; // record the chracter we got for pressing the hardware at key location 'keycode'
|
|
|
|
if ((key >= 0)&&(key < [self numKeys])&&(!keys[key]))
|
|
{
|
|
keys[key] = YES;
|
|
|
|
if (allowingStringInput)
|
|
{
|
|
// limited input for planet find screen
|
|
if (allowingStringInput == gvStringInputAlpha)
|
|
{
|
|
if (((key > 64)&&(key < 91))||((key > 96)&&(key < 123)))
|
|
{
|
|
// alphanumeric
|
|
isAlphabetKeyDown = YES;
|
|
// convert to lowercase
|
|
[typedString appendFormat:@"%c", (key | 64)];
|
|
}
|
|
else
|
|
isAlphabetKeyDown = NO;
|
|
if (key == NSDeleteCharacter)
|
|
{
|
|
//delete
|
|
[typedString setString:@""];
|
|
}
|
|
}
|
|
|
|
// full input for load-save screen
|
|
if (allowingStringInput == gvStringInputAll)
|
|
{
|
|
if ((key > 31)&&(key < 123))
|
|
{
|
|
// alphanumeric
|
|
isAlphabetKeyDown = YES;
|
|
// convert to lowercase
|
|
[typedString appendFormat:@"%c", key];
|
|
}
|
|
else
|
|
isAlphabetKeyDown = NO;
|
|
if ((key == NSDeleteCharacter) && [typedString length])
|
|
{
|
|
//delete
|
|
[typedString deleteCharactersInRange:NSMakeRange([typedString length] - 1, 1)];
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (key > [self numKeys])
|
|
OOLog(kOOLogKeyCodeOutOfRange, @"Translated key: %d out of range", key);
|
|
}
|
|
}
|
|
|
|
/* Capture shift, ctrl, opt and command press & release */
|
|
- (void)flagsChanged:(NSEvent *)theEvent
|
|
{
|
|
int flags = [theEvent modifierFlags];
|
|
opt = (flags & NSAlternateKeyMask) ? YES : NO;
|
|
ctrl = (flags & NSControlKeyMask) ? YES : NO;
|
|
command = (flags & NSCommandKeyMask) ? YES : NO;
|
|
shift = ( flags & NSShiftKeyMask ) ? YES : NO;
|
|
|
|
}
|
|
|
|
|
|
- (void)mouseDown:(NSEvent *)theEvent
|
|
{
|
|
if (doubleClick)
|
|
{
|
|
doubleClick = NO;
|
|
keys[gvMouseDoubleClick] = NO;
|
|
}
|
|
keys[gvMouseLeftButton] = YES; // 'a' down
|
|
}
|
|
|
|
|
|
- (void)mouseUp:(NSEvent *)theEvent
|
|
{
|
|
NSTimeInterval timeBetweenClicks = [NSDate timeIntervalSinceReferenceDate] - timeIntervalAtLastClick;
|
|
timeIntervalAtLastClick += timeBetweenClicks;
|
|
|
|
if (!doubleClick)
|
|
{
|
|
doubleClick = (timeBetweenClicks < MOUSE_DOUBLE_CLICK_INTERVAL); // One fifth of a second
|
|
keys[gvMouseDoubleClick] = doubleClick;
|
|
}
|
|
|
|
keys[gvMouseLeftButton] = NO; // 'a' up
|
|
}
|
|
|
|
|
|
- (void)mouseMoved:(NSEvent *)theEvent
|
|
{
|
|
double mx = [theEvent locationInWindow].x - viewSize.width/2.0;
|
|
double my = [theEvent locationInWindow].y - viewSize.height/2.0;
|
|
|
|
if (display_z > 640.0)
|
|
{
|
|
mx /= viewSize.width * MAIN_GUI_PIXEL_WIDTH / display_z;
|
|
my /= viewSize.height;
|
|
}
|
|
else
|
|
{
|
|
mx /= MAIN_GUI_PIXEL_WIDTH * viewSize.width / 640.0;
|
|
my /= MAIN_GUI_PIXEL_HEIGHT * viewSize.width / 640.0;
|
|
}
|
|
|
|
[self setVirtualJoystick:mx :-my];
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
/* Turn the Cocoa ArrowKeys into our arrow key constants. */
|
|
- (int) translateKeyCode: (int) input
|
|
{
|
|
int key = input;
|
|
switch ( input )
|
|
{
|
|
case NSUpArrowFunctionKey:
|
|
key = gvArrowKeyUp;
|
|
break;
|
|
|
|
case NSDownArrowFunctionKey:
|
|
key = gvArrowKeyDown;
|
|
break;
|
|
|
|
case NSLeftArrowFunctionKey:
|
|
key = gvArrowKeyLeft;
|
|
break;
|
|
|
|
case NSRightArrowFunctionKey:
|
|
key = gvArrowKeyRight;
|
|
break;
|
|
|
|
case NSF1FunctionKey:
|
|
key = gvFunctionKey1;
|
|
break;
|
|
|
|
case NSF2FunctionKey:
|
|
key = gvFunctionKey2;
|
|
break;
|
|
|
|
case NSF3FunctionKey:
|
|
key = gvFunctionKey3;
|
|
break;
|
|
|
|
case NSF4FunctionKey:
|
|
key = gvFunctionKey4;
|
|
break;
|
|
|
|
case NSF5FunctionKey:
|
|
key = gvFunctionKey5;
|
|
break;
|
|
|
|
case NSF6FunctionKey:
|
|
key = gvFunctionKey6;
|
|
break;
|
|
|
|
case NSF7FunctionKey:
|
|
key = gvFunctionKey7;
|
|
break;
|
|
|
|
case NSF8FunctionKey:
|
|
key = gvFunctionKey8;
|
|
break;
|
|
|
|
case NSF9FunctionKey:
|
|
key = gvFunctionKey9;
|
|
break;
|
|
|
|
case NSF10FunctionKey:
|
|
key = gvFunctionKey10;
|
|
break;
|
|
|
|
case NSF11FunctionKey:
|
|
key = gvFunctionKey11;
|
|
break;
|
|
|
|
case NSHomeFunctionKey:
|
|
key = gvHomeKey;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
|
|
- (JoystickHandler *)getStickHandler
|
|
{
|
|
return [JoystickHandler sharedStickHandler];
|
|
}
|
|
|
|
|
|
- (void) setVirtualJoystick:(double) vmx :(double) vmy
|
|
{
|
|
virtualJoystickPosition.x = vmx;
|
|
virtualJoystickPosition.y = vmy;
|
|
}
|
|
|
|
|
|
- (NSPoint) virtualJoystickPosition
|
|
{
|
|
return virtualJoystickPosition;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////
|
|
|
|
- (void) clearKeys
|
|
{
|
|
int i;
|
|
for (i = 0; i < [self numKeys]; i++)
|
|
keys[i] = NO;
|
|
}
|
|
|
|
|
|
- (void) clearMouse
|
|
{
|
|
keys[gvMouseDoubleClick] = NO;
|
|
keys[gvMouseLeftButton] = NO;
|
|
doubleClick = NO;
|
|
}
|
|
|
|
|
|
- (BOOL) isAlphabetKeyDown
|
|
{
|
|
return isAlphabetKeyDown = NO;;
|
|
}
|
|
|
|
// DJS: When entering submenus in the gui, it is not helpful if the
|
|
// key down that brought you into the submenu is still registered
|
|
// as down when we're in. This makes isDown return NO until a key up
|
|
// event has been received from SDL.
|
|
- (void) supressKeysUntilKeyUp
|
|
{
|
|
if (keys[gvMouseDoubleClick] == NO)
|
|
{
|
|
supressKeys = YES;
|
|
[self clearKeys];
|
|
}
|
|
else
|
|
{
|
|
[self clearMouse];
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL) isDown: (int) key
|
|
{
|
|
if( supressKeys )
|
|
return NO;
|
|
if ( key < 0 )
|
|
return NO;
|
|
if ( key >= [self numKeys] )
|
|
return NO;
|
|
return keys[key];
|
|
}
|
|
|
|
|
|
- (BOOL) isOptDown
|
|
{
|
|
return opt;
|
|
}
|
|
|
|
|
|
- (BOOL) isCtrlDown
|
|
{
|
|
return ctrl;
|
|
}
|
|
|
|
|
|
- (BOOL) isCommandDown
|
|
{
|
|
return command;
|
|
}
|
|
|
|
|
|
- (BOOL) isShiftDown
|
|
{
|
|
return shift;
|
|
}
|
|
|
|
|
|
- (int) numKeys
|
|
{
|
|
return NUM_KEYS;
|
|
}
|
|
|
|
|
|
- (BOOL)pollShiftKey
|
|
{
|
|
#define KEYMAP_GET(m, index) ((((uint8_t*)(m))[(index) >> 3] & (1L << ((index) & 7))) ? 1 : 0)
|
|
|
|
KeyMap map;
|
|
|
|
GetKeys(map);
|
|
return KEYMAP_GET(map, 56) || KEYMAP_GET(map, 60); // Left shift or right shift -- although 60 shouldn't occur.
|
|
}
|
|
|
|
@end
|