The mouse and keyboard input for the game window
Adding support for the keys pressed on the keyboard, the buttons on the mouse, or moving the mouse around is a simple copy-and-paste task from the window events – create a member function to be called and add the lambda-encapsulated call to GLFW. The next time you press a key or move the mouse after a successful recompilation, the new callbacks will run.
You can find the enhanced example code in the 05_window_with_input
folder.
Let’s start by retrieving the key presses before we add the keyboard callbacks and functions. After this, we will continue to get mouse events and also add the respective functions for them to the code.
Key code, scan code, and modifiers
To get the events for the keys the user presses or releases on their keyboard, GLFW offers another callback. The following callback for a plain key input receives four values:
glfwSetKeyCallback(window, key_callback);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
These values are listed as follows:
- The ASCII key code of the key
- The (platform-specific) scan code of that key
- The action you carried out (press the key, release it, or hold it until the key repeat starts)
- The status of the modifier key, such as Shift, Ctrl, or Alt
The key can be compared with internal GLFW values such as GLFW_KEY_A
, as they emit the 7-bit ASCII code of the letter you pressed. The function keys, the separate keypad, and the modifier keys return values >256.
The scan code is specific to your system. While it stays the same on your system, the code may differ on another platform. So, hardcoding it into your code is a bad idea.
The action is one of the three values GLFW_PRESS
, GLFW_RELEASE
, or GLFW_REPEAT
, if the key is pressed for longer, but note that the GLFW_REPEAT
action is not issued for all keys.
The modifier status is a bitmap to see whether the users pressed keys such as Shift, Ctrl, or Alt. You can also enable the reporting of Caps Lock and Num Lock – this is not enabled in the normal input mode.
For example, we could add a simple keyboard logging to the code. First, add a new function to the Window.h
header file:
public:
void handleKeyEvents(int key, int scancode, int action,
int mods);
As you can see in the preceding code, we don’t need GLFWwindow
in our functions, as we already saved it as a private data member of the class.
Next, add the callback to the GLFW function using a lambda:
glfwSetKeyCallback(mWindow, [](GLFWwindow *win, int key,
int scancode, int action, int mods) {
auto thisWindow = static_cast<Window*>(
glfwGetWindowUserPointer(win));
thisWindow->handleKeyEvents(key, scancode, action,
mods);
}
);
This is the same as it was for the window event – get the this
pointer of the current instance of the Window
class from the user pointer set by glfwSetWindowUserPointer()
and call the new member functions of the class.
For now, the member function for the keys can be simple:
void Window::handleKeyEvents(int key, int scancode, int action, int mods) {
std::string actionName;
switch (action) {
case GLFW_PRESS:
actionName = "pressed";
break;
case GLFW_RELEASE:
actionName = "released";
break;
case GLFW_REPEAT:
actionName = "repeated";
break;
default:
actionName = "invalid";
break;
}
const char *keyName = glfwGetKeyName(key, 0);
Logger::log(1, "%s: key %s (key %i, scancode %i) %s\n",
__FUNCTION__, keyName, key, scancode,
actionName.c_str());
}
Here, we use a switch()
statement to set a string depending on the action that has occurred and also call glfwGetKeyName()
to get a human-readable name of the key. If no name has been set, it prints out (null)
. You will also see the key code, which is the ASCII code for letters and numbers, as mentioned earlier in this section, and the platform-specific scan code of the key. As a last field, it will print out if the key was pressed, released, or held until the key repeat from the OS started. The default
option is used for completeness here; it should never be called in the current GLFW version as it would indicate a bug.
Different styles of mouse movement
GLFW knows two types of mouse movement: the movement adjusted by the OS and a raw movement.
The first one returns the value with all the optional settings you might have defined, such as mouse acceleration, which speeds up the cursor if you need to move the cursor across the screen.
The following is a callback function, which gets informed if the mouse position changes:
glfwSetCursorPosCallback(window, cursor_position_callback);
void cursor_position_callback(GLFWwindow* window,
double xpos, double ypos)
Alternatively, you can poll the current mouse position in your code manually:
double xpos, ypos;
glfwGetCursorPos(window, &xpos, &ypos);
The raw mode excludes these settings and provides you with the precise level of movement on your desk or mouse mat. To enable raw mode, first, you have to disable the mouse cursor in the window (not only hide it), and then you can try to activate it:
glfwSetInputMode(window, GLFW_CURSOR,
GLFW_CURSOR_DISABLED);
if (glfwRawMouseMotionSupported()) {
glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION,
GLFW_TRUE);
}
To exit raw mode, go back to the normal mouse mode:
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
Keeping both movement styles apart will be interesting for the kind of application we are creating. If we want to adjust the settings using an onscreen menu, having the mouse pointer react like it would in other applications on your computer is perfect. But once we need to rotate or move the model, or change the view in the virtual world, any acceleration could lead to unexpected results. For this kind of mouse movement, we should use the raw mode instead.
To add a mouse button callback, add the function call to Window.h
:
private:
void handleMouseButtonEvents(int button, int action,
int mods);
And in Window.cpp
, add the callback handling and the function itself:
glfwSetMouseButtonCallback(mWindow, [](GLFWwindow *win,
int button, int action, int mods) {
auto thisWindow = static_cast<Window*>(
glfwGetWindowUserPointer(win));
thisWindow->handleMouseButtonEvents(button, action,
mods);
}
);
This is similar to the keyboard callback discussed earlier in this chapter; we get back the pressed button, the action (GLFW_PRESS
or GLFW_RELEASE
), and also any pressed modifiers such as the Shift or Alt keys.
The handler itself is pretty basic in the first version. The first switch()
block is similar to the keyboard function, as it checks whether the button has been pressed or released:
void Window::handleMouseButtonEvents(int button,
int action, int mods) {
std::string actionName;
switch (action) {
case GLFW_PRESS:
actionName = "pressed";
break;
case GLFW_RELEASE:
actionName = "released";
break;
default:
actionName = "invalid";
break;
}
The second switch()
block checks which mouse button was pressed, and it prints out the names of the left, right, or middle buttons. GLFW supports up to eight buttons on the mouse, and more than the basic three are printed out as "other"
:
std::string mouseButtonName;
switch(button) {
case GLFW_MOUSE_BUTTON_LEFT:
mouseButtonName = "left";
break;
case GLFW_MOUSE_BUTTON_MIDDLE:
mouseButtonName = "middle";
break;
case GLFW_MOUSE_BUTTON_RIGHT:
mouseButtonName = "right";
break;
default:
mouseButtonName = "other";
break;
}
Logger::log(1, "%s: %s mouse button (%i) %s\n",
__FUNCTION__, mouseButtonName.c_str(), button,
actionName.c_str());
}
When running the code, you should see messages like this:
init: Window successfully initialized
handleWindowMoveEvents: Window has been moved to 0/248
handleMouseButtonEvents: left mouse button (0) pressed
handleMouseButtonEvents: left mouse button (0) released
handleMouseButtonEvents: middle mouse button (2) pressed
handleMouseButtonEvents: middle mouse button (2) released
handleWindowCloseEvents: Window got close event... bye!
cleanup: Terminating Window
You could add more handlers. The example code also uses the callbacks for mouse movement, which gives you the current mouse position inside the window, and the callback for entering and leaving the window.