Version 3 (modified by osg, 7 years ago)

Added link to next tutorial

Handling Keyboard Input

(Wiki editing note: Code needs conversion to osgViewer)
(Wiki editing note: Add link to complete source code at bottom)

Goals

Add the ability to associate keyboard events with specific functions. From a previous tutorial we added the ability to update a tank's turret rotation using an update callback. This tutorial will develop a keyboard interface class that allows us to update the turret's rotation based on user keyboard input.

GUI Event Handlers: GUI Event Adapters and GUI Action Adapters:

Note: This description is derived in large part from the online documentation for osg available here.

The GUIEventHandler class provideds developers with an interface to the windowing sytem's GUI events. The event handler recieves updates in the form of GUIEventAdapter instances. The event handler can also send requests for the GUI system to perform some operation using GUIActionAdapter instances.

Information about GUIEventAdapters instances include the type of event (PUSH, RELEASE, DOUBLECLICK, DRAG, MOVE, KEYDOWN, KEYUP, FRAME, RESIZE, SCROLLUP, SCROLLDOWN, SCROLLLEFT). Depending on the type of GUIEventAdapter, the instance may have additional information associated with it. Mouse events will have x and y positions associated with them. KEYUP and KEYDOWN events will have a key name (ex: 'a', 'F1') associated with them.

The GUIEventHandler uses GUIActionAdapters to request actions of the GUI system. These action include request to redraw - requestRedraw(), request to continually redraw - requestContinuousRedraw(), and request to reposition the cursor - requestWarpPointer(x,y).

The GUIEventHandler class interacts with the GUI primarily with the 'handle' method. The handle method has two arguments: an instance of GUIEventAdapter for receiving updates from the GUI, and a GUIActionAdapter for requesting actions of the GUI. The handle method can examine the type and values associated with the GUIEventAdapter, perform required operations, and make a request of the GUI system using the GUIActionAdapter. The handle method returns boolean variable set to true if the event has been 'handled', false otherwise.
Since there may be more than one GUIEventAdapter associated with a GUI, the return value of this method (and the order of GUIEventAdapters on a veiwer's eventHandlerList) can be used to control handling of single keyboard events multiple times. If a GUIEventHandler returns false, the next GUIEventHandler will also respond to the same keyboard event.

The following example demonstrates how a GUIEventHandler interacts with a GUI system: The TrackballManipulator class (derived from GUIEventHandler) receives updates of mouse events in the form of GUIEventAdapter instances. One of these mouse events is interpreted by the TrackballManipulator class as a 'throw' request. (The user want to throw the model so it will continually spin or move.) On interpreting this event, the TrackBallManipulator sends a request to the GUI system (using a GUIActionAdapter) to start a timer and request to be repeatedly called so it can calculate new model orientation or position data.

Sample keyboard interface class

The following briefly describes a keyboard class that allows users to associate keyboard input with specific functions. When users register keys and associated C++ functions to invoke, appropriate entries are made in a table. This table contains key values ('a','F1',etc.), key state (up or down) and the C++ function to invoke. This essentially allows users to create interaction in terms of "on sensing 'f' key down, perform action 'functionOne'." Because the keyBoardInterface class is derived from the GUIEventHandler class, each time the GUI system senses a GUI event, this classes, 'handle' method will be invoked. When the handle method is invoked, the GUI event's key value and state ('a' key 'UP') is compared to the table entries. If a match is found, the function associated with the key value and state is invoked.
The user registers keys using the addFunction method. There are two versions of this function. The first takes a key and function as arguments. This method is designed for situations where the user is only concerned with the KEY_DOWN events. For example if the user wants to associate the 'a' key down events with a method to toggle the anti-aliasing. The user would not be concerned with an action to take on key up.
In other cases, the user would want distinct actions to be associated with a single key's 'down' and 'up' events. Examples would include controling a first person shooter's motion. On sensing the 'w' key down, the model should acelerate forward. When the 'w' key is released, the motion model should coast to a stop. From a design approach, separate function calls for key up and key down were viewed as preferable. The alternative was to continually trigger the key-down method.

    #ifndef KEYBOARD_HANDLER_H
    #define KEYBOARD_HANDLER_H
    #include <osgGA/GUIEventHandler>

    class keyboardEventHandler : public osgGA::GUIEventHandler
    {
    public:

       typedef void (*functionType) ();
       enum keyStatusType
       {
          KEY_UP, KEY_DOWN 
       };

    // A struct for storing current status of each key and 
    // function to execute. Keep track of key's state to avoid
    // redundant calls. (If the key is already down, don't call the
    // key down method again.)
       struct functionStatusType
       {
          functionStatusType() {keyState = KEY_UP; keyFunction = NULL;}
          functionType keyFunction;
          keyStatusType keyState;
       };

    // Storage for list of registered key, function to execute and 
    // current state of key.
       typedef std::map<int, functionStatusType > keyFunctionMap;

    // Function to associate a key with a function. If the key has not
    // been previously registered, key and function are added to the
    // map of 'key down' events and 'true' is returned. Otherwise, no
    // entry made and false is returned.
       bool addFunction(int whatKey, functionType newFunction);

    // Overloded version allows users to specify if the function should 
    // be associated with KEY_UP or KEY_DOWN event.
       bool addFunction(int whatKey, keyStatusType keyPressStatus, 
          functionType newFunction);

    // The handle method checks the current key down event against 
    // list of registered key/key status entries. If a match is found 
    // and it's a new event (key was not already down) corresponding 
    // function is invoked.
       virtual bool handle(const osgGA::GUIEventAdapter& ea,
          osgGA::GUIActionAdapter&);

    // Overloaded accept method for dealing with event handler visitors
       virtual void accept(osgGA::GUIEventHandlerVisitor& v)
          { v.visit(*this); };

    protected:

    // Storage for registered 'key down' methods and key status
       keyFunctionMap keyFuncMap;

    // Storage for registered 'key up' methods and key status
       keyFunctionMap keyUPFuncMap;
    };
    #endif

Using the keyboard interface class:

    The following provides an example of how to use this class:

    // Create scene and viewer
    // ...

    // Declare and define some functions: 
    // startAction(), stopAction(), toggleSomething()
    // ... 

    // Declare and initialize a instance of the keyboard event 
    // handler.
       keyboardEventHandler* keh = new keyboardEventHandler();

    // Add this event handler to the viewer's event handler list.
    // If we use push_front and our handle method returns 'true' other
    // event handlers will not have the opportunity to resond to this
    // GUI event. We can use push_back if we want to give other handlers
    // first shot at the events, or have the handle method return false.
       viewer.getEventHandlerList().push_front(keh); 

    // Register some keys, associate them with functions.
    // Each time 'a' key is pressed, toggelSomething() is invoked
    // (releasing the 'a' key has no effect.)

       keh->addFunction('a',toggleSomething);

    // Each time the 'j' key is pressed, the startAction method is
    // invoked (for example: acclerate motion model). 
    // Note the second argument is not required.
       keh->addFunction('j',keyboardEventHandler::KEY_DOWN,startAction);

    // Each time the 'j' key is released, the stopAction method is
    // invoked
       keh->addFunction('j',keyboardEventHandler::KEY_UP,stopAction); 

    // Enter a simulation loop
    // ...

Continue with tutorial Basic keyboard input?