From b30fb02b9250d7fefcc05a35fb89b818fe25aae2 Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Fri, 17 Nov 2006 16:22:58 +0000 Subject: [PATCH] From Eric Wing, new ogsimpleviewCocoa example. --- .../English.lproj/InfoPlist.strings | Bin 0 -> 202 bytes examples/osgsimpleviewerCocoa/Info.plist | 28 + .../osgsimpleviewerCocoa/SimpleViewerCocoa.h | 133 +++ .../osgsimpleviewerCocoa/SimpleViewerCocoa.mm | 757 ++++++++++++++++++ examples/osgsimpleviewerCocoa/main.m | 14 + 5 files changed, 932 insertions(+) create mode 100644 examples/osgsimpleviewerCocoa/English.lproj/InfoPlist.strings create mode 100644 examples/osgsimpleviewerCocoa/Info.plist create mode 100644 examples/osgsimpleviewerCocoa/SimpleViewerCocoa.h create mode 100644 examples/osgsimpleviewerCocoa/SimpleViewerCocoa.mm create mode 100644 examples/osgsimpleviewerCocoa/main.m diff --git a/examples/osgsimpleviewerCocoa/English.lproj/InfoPlist.strings b/examples/osgsimpleviewerCocoa/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000000000000000000000000000000000..be1baf7257980d32c024ba8a3bb6f4c2d82cafec GIT binary patch literal 202 zcmW-ZO%B0e6h+V2D#Wl6#K1(1Oh_OP + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + org.openscenegraph.osgsimpleviewerCocoa + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.h b/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.h new file mode 100644 index 000000000..c33185400 --- /dev/null +++ b/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.h @@ -0,0 +1,133 @@ +// +// SimpleViewerCocoa.h +// osgsimpleviewerCocoa +// +// Created by Eric Wing on 11/12/06. +// Copyright 2006. All rights reserved. +// +/* This is the class interface for a custom NSView that interfaces with an osgViewer. + * Because Cocoa is written in Objective-C, but OSG is written in C++, we rely on + * Objective-C++ to make the integration easy. + * + * One thing to remember is that any Objective-C files that include this header + * must also be compiled as Objective-C++ because there are C++ constructs in + * this file (such as namespaces) which the normal Objective-C compiler + * won't understand. (The easy way to do this is rename the .m files to .mm.) + * + * In the event that you have a large, pre-existing code base written in + * pure Objective-C and you find that the header include propagates to a + * large number of your files, forcing you to mark many files to be compiled as + * Objective-C++, and you find that you don't want to change these files because + * they are shared with other pure Obj-C projects, you might consider further + * wrapping the C++ code so there are only C or Obj-C constructs in + * this header. There are several different techniques ranging from, wrapping + * the C++ code in pure C interfaces, to simply using void pointers in this + * file for any C++ pointers and casting appropriately in the implementation + * file. + */ + + +#import + +namespace osgViewer +{ + // Just a forward declaration so I don't need the #include in the header. + class SimpleViewer; +} + +// Subclass NSOpenGLView. We could subclass NSView instead, but NSOpenGLView is easy. +@interface SimpleViewerCocoa : NSOpenGLView +{ + // Note: In Objective-C++, if you use objects instead of pointers as + // member instance variables, you MUST turn on "Call C++ Default Ctors/Dtors in Objective-C". + // -fobjc-call-cxx-cdtors + // This option makes sure constructors and destructors are run. + // This option is only available for gcc 4.0+ (Mac OS X 10.4+) + + // Is SimpleViewer supposed use ref_ptr? (Doesn't look like it to me.) + // If so, remember ref_ptr is an object on the stack and the cdtors option must be activated. + // We could also make simpleViewer an object instead of a pointer, but again, turn on the option. + osgViewer::SimpleViewer* simpleViewer; + + // This timer is used to trigger animation callbacks since everything is event driven. + NSTimer* animationTimer; + + // Flags to help track whether ctrl-clicking or option-clicking is being used + BOOL isUsingCtrlClick; + BOOL isUsingOptionClick; +} + +// My custom static method to create a basic pixel format ++ (NSOpenGLPixelFormat*) basicPixelFormat; + +// Official init methods +- (id) initWithFrame:(NSRect)frame_rect pixelFormat:(NSOpenGLPixelFormat*)pixel_format; +- (id) initWithCoder:(NSCoder*)the_coder; + +// Private init helper methods +- (void) commonInit; +- (void) initOSGViewer; +- (void) initAnimationTimer; + +// Official/Special NSOpenGLView method that gets called for you to prepare your OpenGL state. +- (void) prepareOpenGL; +// Class dealloc method +- (void) dealloc; + +// Official mouse event methods +- (void) mouseDown:(NSEvent*)the_event; +- (void) mouseDragged:(NSEvent*)the_event; +- (void) mouseUp:(NSEvent*)the_event; +- (void) rightMouseDown:(NSEvent*)the_event; +- (void) rightMouseDragged:(NSEvent*)the_event; +- (void) rightMouseUp:(NSEvent*)the_event; +- (void) otherMouseDown:(NSEvent*)the_event; +- (void) otherMouseDragged:(NSEvent*)the_event; +- (void) otherMouseUp:(NSEvent*)the_event; + +// Private setter/getter methods to track ctrl/option-clicking +- (void) setIsUsingCtrlClick:(BOOL)is_using_ctrl_click; +- (BOOL) isUsingCtrlClick; +- (void) setIsUsingOptionClick:(BOOL)is_using_option_click; +- (BOOL) isUsingOptionClick; +// Private helper methods to help deal with mouse events +- (void) doLeftMouseButtonDown:(NSEvent*)the_event; +- (void) doLeftMouseButtonUp:(NSEvent*)the_event; +- (void) doRightMouseButtonDown:(NSEvent*)the_event; +- (void) doRightMouseButtonUp:(NSEvent*)the_event; +- (void) doMiddleMouseButtonDown:(NSEvent*)the_event; +- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number; +- (void) doMiddleMouseButtonUp:(NSEvent*)the_event; +- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number; + +// Official scrollWheel (scrollball) method +- (void) scrollWheel:(NSEvent*)the_event; + +// Official methods for keyboard events +- (BOOL) acceptsFirstResponder; +- (void) keyDown:(NSEvent*)the_event; +- (void) keyUp:(NSEvent*)the_event; + +// My custom method to handle timer callbacks +- (void) animationCallback; + +// Official methods for view stuff and drawing +- (BOOL) isOpaque; +- (void) resizeViewport; +- (void) reshape; +- (void) drawRect:(NSRect)the_rect; + +// Official methods for drag and drop (view as target) +- (unsigned int) draggingEntered:(id )the_sender; +- (void) draggingExited:(id )the_sender; +- (BOOL) prepareForDragOperation:(id )the_sender; +- (BOOL) performDragOperation:(id )the_sender; +- (void) concludeDragOperation:(id )the_sender; + + +// Examples of providing an action to connect to. +- (IBAction) resetPosition:(id)the_sender; +- (IBAction) takeBackgroundColorFrom:(id)the_sender; + +@end + diff --git a/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.mm b/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.mm new file mode 100644 index 000000000..d636982d2 --- /dev/null +++ b/examples/osgsimpleviewerCocoa/SimpleViewerCocoa.mm @@ -0,0 +1,757 @@ +// +// SimpleViewerCocoa.mm +// osgsimpleviewerCocoa +// +// Created by Eric Wing on 11/12/06. +// Copyright 2006. All rights reserved. +// +/* This class demonstrates how to subclass NSOpenGLView to integrate with the + * osgViewer::SimpleViewer. + * This class demonstrates: + * Objective-C++ + * How to subclass NSOpenGLView + * Specifying OpenGL pixel formats + * V-sync + * Timer based animation + * One button and multibutton mice. + * Scroll events (Mighty Mouse, Two-finger trackpads) + * Keyboard events + * Drag and drop (as target) + * Resolution Independent UI (maybe, not Leopard tested, only Tiger Quartz Debug) + * Target-Action (for other widgets to invoke actions on this view) + * + * Things not demonstrated by this view or application example (but would be interesting): + * Cocoa Bindings (highly recommended) + * Core Data (works great with Cocoa Bindings) + * Custom Interface Builder palette with Inspector configurable options + * Shared OpenGLContexts + * More PixelFormat options + * Fullscreen mode + * Low-level CGL access. + * Delegates for your view + * Multithreading + * Drag-and-drop as a source (similar to the target code) + * Copy, Cut, Paste (very similar to drag-and-drop) + * Creating (updating) images of the window for the minimized view in the Dock + * Printing support + * Non-view stuff (Application Delegates, models, controllers) + * Launching by double-clicking a model or drag-and-drop onto Application Icon (non-view) + * Launching via commandline with parameters (non-view) + */ + +/* + * Coding conventions: + * My coding style is slightly different than what you normally see in Cocoa. + * So here is the cheat sheet: + * I hate Hungarian (Microsoft) notation. And prefixed underscore _variables + * are technically reserved by the compiler which is why I avoid them. So... + * Member variables use camelCase (Pascal/Java) + * Local variables use under_scores (Ada) + * For methods, I follow standard Cocoa conventions. + * I tend to keep * with the type (e.g. NSView* foo) instead of with the variable (NSView *foo). + * (I tend to think of the pointer as part of the type.) + * For Obj-C named parameters, I tend to keep the namedParameter and the value + * together instead of separated by spaces + * (e.g. [self initWithX:x_val yVal:y_val zVal:z_val]. + * (When I was first learning Objective-C, this made it easier for me to + * figure out which things * were paired.) + */ +#import "SimpleViewerCocoa.h" + +#include +#include +// Needed to explicitly typecast keys to the OSG type +#include +// Needed to load models +#include + +// Used so I can change the background (clear) color +#include +#include + +// For debugging +//#include + +// osgText is used only to announce that you can use drag-and-drop to load models. +// osgViewer itself does not have a dependency on osgText. +#include +#include +#include + +@implementation SimpleViewerCocoa + +// My simple pixel format definition ++ (NSOpenGLPixelFormat*) basicPixelFormat +{ + NSOpenGLPixelFormatAttribute pixel_attributes[] = + { + NSOpenGLPFAWindow, + NSOpenGLPFADoubleBuffer, // double buffered + NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)32, // depth buffer size in bits + (NSOpenGLPixelFormatAttribute)nil + }; + return [[[NSOpenGLPixelFormat alloc] initWithAttributes:pixel_attributes] autorelease]; +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// Init Stuff ///////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +/* This is the designated initializer for an NSOpenGLView. However, since I'm + * using Interface Builder to help, initWithCoder: is the initializer that gets called. + * But for completeness, I implement this method here. + */ +- (id) initWithFrame:(NSRect)frame_rect pixelFormat:(NSOpenGLPixelFormat*)pixel_format +{ + self = [super initWithFrame:frame_rect pixelFormat:pixel_format]; + if(self) + { + [self commonInit]; + } + return self; +} + +/* Going through the IB palette, this initializer is calling instead of the designated initializer + * initWithFrame:pixelFormat: + * But for some reason, the pixel format set in IB selected seems to be either ignored or is missing + * a value I need. (The depth buffer looks too shallow to me and glErrors are triggered.) + * So I explicitly set the pixel format inside here (overriding the IB palette options). + * This probably should be investigated, but since IB is getting an overhaul for Leopard, + * I'll wait on this for now. + */ +- (id) initWithCoder:(NSCoder*)the_coder +{ + self = [super initWithCoder:the_coder]; + if(self) + { + NSOpenGLPixelFormat* pixel_format = [SimpleViewerCocoa basicPixelFormat]; + [self setPixelFormat:pixel_format]; + [self commonInit]; + } + return self; +} + +// My custom methods to centralize common init stuff +- (void) commonInit +{ + isUsingCtrlClick = NO; + isUsingOptionClick = NO; + + + [self initOSGViewer]; + [self initAnimationTimer]; + + // Register for Drag and Drop + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]]; +} + +// Allocate a SimpleViewer and do basic initialization. No assumption about having an +// a valid OpenGL context is made by this function. +- (void) initOSGViewer +{ +// osg::setNotifyLevel( osg::DEBUG_FP ); + simpleViewer = new osgViewer::SimpleViewer; + // Cocoa follows the same coordinate convention as OpenGL. osgViewer's default is inverted. + simpleViewer->getEventQueue()->getCurrentEventState()->setMouseYOrientation(osgGA::GUIEventAdapter::Y_INCREASING_UPWARDS); + // Use a trackball manipulator...matches nicely with the Mighty Mouse Scrollball. + simpleViewer->setCameraManipulator(new osgGA::TrackballManipulator); +} + +- (void) initAnimationTimer +{ + // Cocoa is event driven, so by default, there is nothing to trigger redraws for animation. + // The easiest way to animate is to set a repeating NSTimer which triggers a redraw. + SEL the_selector; + NSMethodSignature* a_signature; + NSInvocation* an_invocation; + // animationCallback is my animation callback method + the_selector = @selector( animationCallback ); + a_signature = [SimpleViewerCocoa instanceMethodSignatureForSelector:the_selector]; + an_invocation = [NSInvocation invocationWithMethodSignature:a_signature] ; + [an_invocation setSelector:the_selector]; + [an_invocation setTarget:self]; + + animationTimer = [NSTimer + scheduledTimerWithTimeInterval:1.0/60.0 // fps + invocation:an_invocation + repeats:YES]; + [animationTimer retain]; + + // For single threaded apps like this one, + // Cocoa seems to block timers or events sometimes. This can be seen + // when I'm animating (via a timer) and you open an popup box or move a slider. + // Apparently, sheets and dialogs can also block (try printing). + // To work around this, Cocoa provides different run-loop modes. I need to + // specify the modes to avoid the blockage. + // NSDefaultRunLoopMode seems to be the default. I don't think I need to explicitly + // set this one, but just in case, I will set it anyway. + [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSDefaultRunLoopMode]; + // This seems to be the one for preventing blocking on other events (popup box, slider, etc) + [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSEventTrackingRunLoopMode]; + // This seems to be the one for dialogs. + [[NSRunLoop currentRunLoop] addTimer:animationTimer forMode:NSModalPanelRunLoopMode]; +} + +- (void) dealloc +{ + [animationTimer invalidate]; + [animationTimer release]; + delete simpleViewer; + [super dealloc]; +} + +/* NSOpenGLView defines this method to be called (only once) after the OpenGL + * context is created and made the current context. It is intended to be used to setup + * your initial OpenGL state. This seems like a good place to initialize the + * OSG stuff. This method exists in 10.3 and later. If you are running pre-10.3, you + * must manually call this method sometime after the OpenGL context is created and + * made current, or refactor this code. + */ +- (void) prepareOpenGL +{ + [super prepareOpenGL]; + + // The NSOpenGLCPSwapInterval seems to be vsync. If 1, buffers are swapped with vertical refresh. + // If 0, flushBuffer will execute as soon as possible. + long swap_interval = 1 ; + [[self openGLContext] setValues:&swap_interval forParameter:NSOpenGLCPSwapInterval]; + + // This is also might be a good place to setup OpenGL state that OSG doesn't control. + glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + // We need to tell the osgViewer what the viewport size is + [self resizeViewport]; + + + // This is optional: + // This is to setup some default text in the OpenGL view so the + // user knows that they should drag and drop a model into the view. + osg::ref_ptr default_text = new osgText::Text; + default_text->setAlignment(osgText::Text::CENTER_CENTER); + default_text->setBackdropType(osgText::Text::OUTLINE); + default_text->setAxisAlignment(osgText::Text::XZ_PLANE); + default_text->setText("Drag-and-Drop\nyour .osg model here!"); + osg::ref_ptr the_geode = new osg::Geode; + the_geode->addDrawable(default_text.get()); + simpleViewer->setSceneData(the_geode.get()); + +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End Init Stuff ///////////////////////////// +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// Mouse Stuff //////////////////////////////// +//////////////////////////////////////////////////////////////////////// + +- (void) mouseDown:(NSEvent*)the_event +{ + // Because many Mac users have only a 1-button mouse, we should provide ways + // to access the button 2 and 3 actions of osgViewer. + // I will use the Ctrl modifer to represent right-clicking + // and Option modifier to represent middle clicking. + if([the_event modifierFlags] & NSControlKeyMask) + { + [self setIsUsingCtrlClick:YES]; + [self doRightMouseButtonDown:the_event]; + } + else if([the_event modifierFlags] & NSAlternateKeyMask) + { + [self setIsUsingOptionClick:YES]; + [self doMiddleMouseButtonDown:the_event]; + } + else + { + [self doLeftMouseButtonDown:the_event]; + } +} + +- (void) mouseDragged:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + simpleViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y); + [self setNeedsDisplay:YES]; +} + +- (void) mouseUp:(NSEvent*)the_event +{ + // Because many Mac users have only a 1-button mouse, we should provide ways + // to access the button 2 and 3 actions of osgViewer. + // I will use the Ctrl modifer to represent right-clicking + // and Option modifier to represent middle clicking. + if([self isUsingCtrlClick] == YES) + { + [self setIsUsingCtrlClick:NO]; + [self doRightMouseButtonUp:the_event]; + } + if([self isUsingOptionClick] == YES) + { + [self setIsUsingOptionClick:NO]; + [self doMiddleMouseButtonUp:the_event]; + } + else + { + [self doLeftMouseButtonUp:the_event]; + } +} + +- (void) rightMouseDown:(NSEvent*)the_event +{ + [self doRightMouseButtonDown:the_event]; +} + +- (void) rightMouseDragged:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + simpleViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y); + [self setNeedsDisplay:YES]; +} + +- (void) rightMouseUp:(NSEvent*)the_event +{ + [self doRightMouseButtonUp:the_event]; +} + +// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc). +- (void) otherMouseDown:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + // Button 0 is left + // Button 1 is right + // Button 2 is middle + // Button 3 keeps going + // osgViewer expects 1 for left, 3 for right, 2 for middle + // osgViewer has a reversed number mapping for right and middle compared to Cocoa + if([the_event buttonNumber] == 2) + { + [self doMiddleMouseButtonDown:the_event]; + } + else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer + { + [self doExtraMouseButtonDown:the_event buttonNumber:[the_event buttonNumber]]; + } +} + +- (void) otherMouseDragged:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + simpleViewer->getEventQueue()->mouseMotion(converted_point.x, converted_point.y); + [self setNeedsDisplay:YES]; +} + +// "otherMouse" seems to capture middle button and any other buttons beyond (4th, etc). +- (void) otherMouseUp:(NSEvent*)the_event +{ + // Button 0 is left + // Button 1 is right + // Button 2 is middle + // Button 3 keeps going + // osgViewer expects 1 for left, 3 for right, 2 for middle + // osgViewer has a reversed number mapping for right and middle compared to Cocoa + if([the_event buttonNumber] == 2) + { + [self doMiddleMouseButtonUp:the_event]; + } + else // buttonNumber should be 3,4,5,etc; must map to 4,5,6,etc in osgViewer + { + // I don't think osgViewer does anything for these additional buttons, + // but just in case, pass them along. But as a Cocoa programmer, you might + // think about things you can do natively here instead of passing the buck. + } [self doExtraMouseButtonUp:the_event buttonNumber:[the_event buttonNumber]]; +} + +- (void) setIsUsingCtrlClick:(BOOL)is_using_ctrl_click +{ + isUsingCtrlClick = is_using_ctrl_click; +} + +- (BOOL) isUsingCtrlClick +{ + return isUsingCtrlClick; +} + +- (void) setIsUsingOptionClick:(BOOL)is_using_option_click +{ + isUsingOptionClick = is_using_option_click; +} + +- (BOOL) isUsingOptionClick +{ + return isUsingOptionClick; +} + +- (void) doLeftMouseButtonDown:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + if([the_event clickCount] == 1) + { + simpleViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1); + } + else + { + simpleViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 1); + } + [self setNeedsDisplay:YES]; +} + +- (void) doLeftMouseButtonUp:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + simpleViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 1); + [self setNeedsDisplay:YES]; +} + +- (void) doRightMouseButtonDown:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + if([the_event clickCount] == 1) + { + simpleViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 3); + } + else + { + simpleViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 3); + } + [self setNeedsDisplay:YES]; +} + + +- (void) doRightMouseButtonUp:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + simpleViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 3); + [self setNeedsDisplay:YES]; +} + +- (void) doMiddleMouseButtonDown:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + if([the_event clickCount] == 1) + { + simpleViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 2); + } + else + { + simpleViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, 2); + } + [self setNeedsDisplay:YES]; +} + +- (void) doExtraMouseButtonDown:(NSEvent*)the_event buttonNumber:(int)button_number +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + + if([the_event clickCount] == 1) + { + simpleViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, button_number+1); + } + else + { + simpleViewer->getEventQueue()->mouseDoubleButtonPress(converted_point.x, converted_point.y, button_number+1); + } + [self setNeedsDisplay:YES]; +} + + +- (void) doMiddleMouseButtonUp:(NSEvent*)the_event +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. NSPoint the_point = [the_event locationInWindow]; + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + simpleViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, 2); + [self setNeedsDisplay:YES]; +} + +- (void) doExtraMouseButtonUp:(NSEvent*)the_event buttonNumber:(int)button_number +{ + // We must convert the mouse event locations from the window coordinate system to the + // local view coordinate system. NSPoint the_point = [the_event locationInWindow]; + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + simpleViewer->getEventQueue()->mouseButtonRelease(converted_point.x, converted_point.y, button_number+1); + + [self setNeedsDisplay:YES]; +} + +// This is a job for Mighty Mouse! +// For the most fluid experience turn on 360 degree mode availble in 10.4.8+. +// With your Mighty Mouse plugged in, +// open 'Keyboard & Mouse' in 'System Preferences'. +// Select the 'Mouse' tab. +// Under 'Scrolling Options' select '360 degree'. +// That should improve diagonal scrolling. +// You should also be able to use 'two-finger scrolling' on newer laptops. +- (void) scrollWheel:(NSEvent*)the_event +{ + // Unfortunately, it turns out mouseScroll2D doesn't actually do anything. + // The camera manipulators don't seem to implement any code that utilize the scroll values. + // This this call does nothing. +// simpleViewer->getEventQueue()->mouseScroll2D([the_event deltaX], [the_event deltaY]); + + // With the absense of a useful mouseScroll2D API, we can manually simulate the desired effect. + NSPoint the_point = [the_event locationInWindow]; + NSPoint converted_point = [self convertPoint:the_point fromView:nil]; + simpleViewer->getEventQueue()->mouseButtonPress(converted_point.x, converted_point.y, 1); + simpleViewer->getEventQueue()->mouseMotion(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY]); + simpleViewer->getEventQueue()->mouseButtonRelease(converted_point.x + -[the_event deltaX], converted_point.y + [the_event deltaY], 1); + + [self setNeedsDisplay:YES]; +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End Mouse Stuff //////////////////////////// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// Keyboard Stuff ///////////////////////////// +//////////////////////////////////////////////////////////////////////// +// Needed to accept keyboard events +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (void) keyDown:(NSEvent*)the_event +{ + // Do you want characters or charactersIgnoringModifiers? + NSString* event_characters = [the_event characters]; +// NSString* event_characters = [the_event charactersIgnoringModifiers]; + + unichar unicode_character = [event_characters characterAtIndex:0]; +// NSLog(@"unicode_character: %d", unicode_character); + simpleViewer->getEventQueue()->keyPress(static_cast(unicode_character)); + + [self setNeedsDisplay:YES]; +} + +- (void) keyUp:(NSEvent*)the_event +{ + // Do you want characters or charactersIgnoringModifiers? + NSString* event_characters = [the_event characters]; +// NSString* event_characters = [the_event charactersIgnoringModifiers]; + unichar unicode_character = [event_characters characterAtIndex:0]; + simpleViewer->getEventQueue()->keyRelease(static_cast(unicode_character)); + [self setNeedsDisplay:YES]; +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End Keyboard Stuff ///////////////////////// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// View and Draw Stuff //////////////////////// +//////////////////////////////////////////////////////////////////////// + +// This method is periodically called by my timer. +- (void) animationCallback +{ + // Simply notify Cocoa that a drawRect needs to take place. + // Potential optimization is to query the OSG stuff to find out if a redraw is actually necessary. + [self setNeedsDisplay:YES]; +} + +// This is an optional optimization. This states you don't have a transparent view/window. +// Obviously don't use this or set it to NO if you intend for your view to be see-through. +- (BOOL) isOpaque +{ + return YES; +} + +// Resolution Independent UI is coming... (Tiger's Quartz Debug already has the tool.) +// We must think in 'point sizes', not pixel sizes, so a conversion is needed for OpenGL. +- (void) resizeViewport +{ + NSSize size_in_points = [self bounds].size; + // This coordinate system conversion seems to make things work with Quartz Debug. + NSSize size_in_window_coordinates = [self convertSize:size_in_points toView:nil]; + simpleViewer->getEventQueue()->windowResize(0, 0, size_in_window_coordinates.width, size_in_window_coordinates.height); +} + +// For window resize +- (void) reshape +{ + [super reshape]; + [self resizeViewport]; +} + +// This is the code that actually draws. +// Remember you shouldn't call drawRect: directly and should use setNeedsDisplay:YES +// This is so the operating system can optimize when a draw is actually needed. +// (e.g. No sense drawing when the application is hidden.) +- (void) drawRect:(NSRect)the_rect +{ + [[self openGLContext] makeCurrentContext]; + simpleViewer->frame(); + [[self openGLContext] flushBuffer]; +} +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End View and Draw Stuff //////////////////// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// For drag and drop ////////////////////////// +//////////////////////////////////////////////////////////////////////// +- (unsigned int) draggingEntered:(id )the_sender +{ + if([the_sender draggingSource] != self) + { + NSPasteboard* paste_board = [the_sender draggingPasteboard]; + // I respond to filename types or URL types + NSArray* supported_types = [NSArray arrayWithObjects:NSFilenamesPboardType, NSURLPboardType, nil]; + // If any of the supported types are being dragged in, activate the copy operation + NSString* first_type = [paste_board availableTypeFromArray:supported_types]; + if(first_type != nil) + { + return NSDragOperationCopy; + } + } + // Means we don't support this type + return NSDragOperationNone; +} + +// We're not using this method, but here it is as an example. +- (void) draggingExited:(id )the_sender +{ +} + +- (BOOL) prepareForDragOperation:(id )the_sender +{ + return YES; +} + +- (BOOL) performDragOperation:(id )the_sender +{ + NSPasteboard* paste_board = [the_sender draggingPasteboard]; + + + if([[paste_board types] containsObject:NSFilenamesPboardType]) + { + NSArray* file_names = [paste_board propertyListForType:NSFilenamesPboardType]; +// int number_of_files = [file_names count]; + // Exercise for the reader: Try loading all files in the array + NSString* single_file = [file_names objectAtIndex:0]; + osg::ref_ptr loaded_model = osgDB::readNodeFile([single_file UTF8String]); + if(!loaded_model) + { + NSLog(@"File: %@ failed to load", single_file); + return NO; + } + simpleViewer->setSceneData(loaded_model.get()); + return YES; + } + else if([[paste_board types] containsObject:NSURLPboardType]) + { + NSURL* file_url = [NSURL URLFromPasteboard:paste_board]; + // See if the URL is valid file path + if(![file_url isFileURL]) + { + NSLog(@"URL: %@ needs to be a file for readNodeFile()", file_url); + return NO; + } + NSString* file_path = [file_url path]; + osg::ref_ptr loaded_model = osgDB::readNodeFile([file_path UTF8String]); + if(!loaded_model) + { + NSLog(@"URL: %@ failed to load, %@", file_url, file_path); + return NO; + } + simpleViewer->setSceneData(loaded_model.get()); + return YES; + } + return NO; +} + +// This method isn't really needed (I could move setNeedsDisplay up), but is here as an example +- (void) concludeDragOperation:(id )the_sender +{ + [self setNeedsDisplay:YES]; +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End of drag and drop /////////////////////// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// IBAction examples ///////////////////////// +//////////////////////////////////////////////////////////////////////// + +// Connect a button to this to stop and reset the position. +- (IBAction) resetPosition:(id)the_sender +{ + // osgGA::MatrixManipulator* camera_manipulator = simpleViewer->getCameraManipulator(); + // This only resets the position + // camera_manipulator->home(0.0); + + // There is no external API from SimpleViewer that I can see that will stop movement. + // So fake the 'spacebar' to stop things and reset. + simpleViewer->getEventQueue()->keyPress(static_cast(0x20)); + [self setNeedsDisplay:YES]; +} + +// Connect a NSColorWell to this to change color. +// A better way to do this is use Cocoa Bindings because it will automatically +// synchronize between your model and view, but since this demo lacks a model and controller +// aspect it wouldn't do much good. +- (IBAction) takeBackgroundColorFrom:(id)the_sender +{ + NSColor* the_color = [the_sender color]; + osgUtil::SceneView* scene_view = simpleViewer->getSceneView(); + + scene_view->setClearColor( + osg::Vec4( + [the_color redComponent], + [the_color greenComponent], + [the_color blueComponent], + [the_color alphaComponent] + ) + ); + [self setNeedsDisplay:YES]; +} + +//////////////////////////////////////////////////////////////////////// +/////////////////////////// End IBAction examples ///////////////////// +//////////////////////////////////////////////////////////////////////// + +@end diff --git a/examples/osgsimpleviewerCocoa/main.m b/examples/osgsimpleviewerCocoa/main.m new file mode 100644 index 000000000..ebebb4fb7 --- /dev/null +++ b/examples/osgsimpleviewerCocoa/main.m @@ -0,0 +1,14 @@ +// +// main.m +// osgsimpleviewerCocoa +// +// Created by Eric Wing on 11/13/06. +// Copyright __MyCompanyName__ 2006. All rights reserved. +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +}