-
Notifications
You must be signed in to change notification settings - Fork 12
Using Keyboard And Mouse
Let's get down to the "key" points here.
You can add I/O interaction to any animation that you draw on a Canvas/CartesianCanvas. That includes keyboard and mouse events only.
You may be asking us, "Is that done using magic??”
Well, TSGL makes it fairly easy.
Linux/Mac/WSL/Cygwin users: Follow the steps from the previous tutorials. Name the folder "Tutorial8" and the file "interaction.cpp". Replace "program" in the "TARGET" line of the Makefile with "interaction".
All platforms: Follow the steps in the Building Programs page on how to compile and run the program (this is a single-file program).
#include <tsgl.h>
using namespace tsgl;
int main() {
Canvas c(0, 0, 500, 500, "I/O Example", WHITE);
c.start();
c.wait();
}
Compile and run. A blank screen should appear.
Now, if you've taken a look at the TSGL API documentation for Canvas, you may have seen that it has two methods: bindToButton() and bindToScroll().
These two methods are where the I/O magic happens.
They allow us to bind keys, mouse buttons, and the mouse scroll wheel to a Canvas which will allow us to have I/O capabilities in our animations.
But how do these methods work?
Well, before we answer that, allow us to explain a crucial aspect of these methods: Lambda functions.
Taken from http://en.cppreference.com/w/cpp/language/lambda, a Lambda function: "Constructs a closure: an unnamed function object capable of capturing variables in scope."
What does that mean?
It is essentially an unnamed function that can access any variables that are in scope, and that can be passed as an argument to another function.
"Okay, great" you may be thinking "but how does this relate to the methods?"
In order to handle mouse and/or keyboard I/O, the bindToButton() and bindToScroll() methods each take a Lambda function as an argument. In the bindToButton() method, the Lambda function is associated with a keyboard key or mouse button and an action, which are also passed as arguments. In the bindToScroll() method, the Lambda function is the only argument.
Let's look at an example to make it clear:
#include <tsgl.h>
using namespace tsgl;
int main() {
Canvas c(0, 0, 500, 500, "I/O Example", WHITE);
c.start();
// This variable is in the scope of the Lambda function.
// It can be passed in.
bool leftMouseButtonPressed = false;
// Bind the left mouse button so that when it's pressed
// the boolean is set to true.
c.bindToButton(TSGL_MOUSE_LEFT, TSGL_PRESS,
[&leftMouseButtonPressed]() {
leftMouseButtonPressed = true;
}
);
// Bind the left mouse button again so that when it's released
// the boolean is set to false.
c.bindToButton(TSGL_MOUSE_LEFT, TSGL_RELEASE,
[&leftMouseButtonPressed]() {
leftMouseButtonPressed = false;
}
);
// Drawing loop
Rectangle rec(0, 0, 0, 100, 200, 0, 0, 0, RED);
c.add( &rec );
while(c.isOpen()) {
c.sleep();
if(leftMouseButtonPressed) {
rec.setColor(GREEN);
} else {
rec.setColor(RED);
}
}
c.wait();
}
Recompile and run. A rectangle should appear in red on the screen. Click the screen with the left mouse button. The color of the rectangle should change to green.
The bindToButton() method takes in three parameters:
- A key or button to bind to (e.g.
TSGL_Afor the 'A' key). - An event associated with that key (
TSGL_PRESSfor when it's pressed,TSGL_RELEASEwhen it is released). - An unnamed (Lambda) function that will be passed to
bindToButton()when that method is invoked.
Allow us to explain Lambda functions in a little more depth.
Variables inside the brackets are variables defined outside of the function that are in its scope, and that are being passed into the function. The ampersand makes the variable a reference parameter. In a nutshell, that means that whatever you do to that parameter in the function will affect the variable being passed as the parameter. So, if we passed a boolean switch as the parameter and it has a value of false before the function runs and inside the function, we change the value of the parameter to true, then when the function finishes, the variable switch will have true as its value instead of false.
The variables MUST be passed as reference parameters.
Passing reference variables to the unnamed Lambda function is called "capturing" the parameters.
Now, in the definition of the Lambda function, you can alter the reference parameters so that whenever the Lambda function is invoked the reference parameters are changed to values you want to assign to them.
The mouse click and release event are bound to the Lambda functions that set a boolean variable to true and false (respectively).
"Bound" means that the corresponding Lambda function will be invoked once the action occurs:
- Once the mouse has been clicked, the Lambda function bound to the left mouse button click event will be invoked and the boolean
leftMouseButtonPressedchanges to true. - Once it has been released, the Lambda function bound to the left mouse button click event will be invoked and the boolean
leftMouseButtonPressedchanges to false.
As you can see, whenever the left mouse button is clicked, the boolean variable leftMouseButtonPressed is set to true and when the left mouse button is released it is set to false.
Now, how do we get the Canvas to draw stuff once the mouse button is clicked?
In the animation loop shown below, if leftMouseButtonPressed has been set to true then we can have the Canvas draw a circle that is centered by the mouse's x and y-coordinates:
#include <tsgl.h>
using namespace tsgl;
int main() {
Canvas c(0, 0, 500, 500, "I/O Example", WHITE);
c.start();
//This variable is in the scope of the Lambda function.
//It can be passed in.
bool leftMouseButtonPressed = false;
//Bind the left mouse button so that when it's pressed
//the boolean is set to true.
c.bindToButton(TSGL_MOUSE_LEFT, TSGL_PRESS,
[&leftMouseButtonPressed]() {
leftMouseButtonPressed = true;
}
);
//Bind the left mouse button again so that when it's released
//the boolean is set to false.
c.bindToButton(TSGL_MOUSE_LEFT, TSGL_RELEASE,
[&leftMouseButtonPressed]() {
leftMouseButtonPressed = false;
}
);
//Drawing loop
Rectangle rec(0, 0, 0, 100, 200, 0, 0, 0, RED);
Circle circle(0, 150, 0, 20, 0, 0, 0, BLACK);
c.add( &rec ); c.add( &circle );
while(c.isOpen()) {
c.sleep();
int x = c.getMouseX(), y = c.getMouseY(), z = 1; //Store the x and y-coordinates of the mouse
if(leftMouseButtonPressed) {
rec.setColor(GREEN);
circle.setCenter(x, y, z);
} else {
rec.setColor(RED);
}
}
c.wait();
}
Recompile and run the code. Now, click on the left mouse button while the mouse is on the screen. The black, filled-in circle should appear below your mouse cursor!
You can even hold down the left mouse button and drag the mouse over the screen and the black circle will follow your mouse!
How about an example using the bindToScroll() method instead?
Let's take a look:
#include <tsgl.h>
using namespace tsgl;
int main() {
Canvas c(0, 0, 500, 500, "I/O Example", WHITE);
c.start();
// These variables are in the scope of the Lambda function.
// They can be passed in.
int circleX = 0, circleY = 0, circleZ = 0;
c.bindToScroll([&circleY](double dx, double dy) {
if(dy == 1.0) { // If you move the scroll wheel down...
circleY += 50; // Incement the circle's y-coordinate.
} else { // Else...
circleY -= 50; // Decrement the coordinate.
}
});
// Drawing loop
Circle circle(circleX, circleY, 0, 50, 0, 0, 0, BLACK);
c.add( &circle );
while(c.isOpen()) {
c.sleep();
circle.setCenter(circleX, circleY, circleZ);
}
c.wait();
}
Recompile and run. A black circle should appear on the screen. Scroll up to make the circle go up, scroll down to make the circle go down.
Allow us to explain this example.
The bindToScroll() method takes in a Lambda function, which in turn, takes in two parameters:
- dx, which is an x parameter whose value is set when the mouse is scrolled.
- dy, which is a y parameter whose value is set when the mouse is scrolled.
The parameters dx and dy contain the x and y coordinates of the mouse when the event occurs — when the method is invoked — and can be used to access those coordinates within the body of the method.
The bindToScroll() method MUST take in those two parameters, even if one or both parameters are not used.
In sum, in order to do animations with I/O capabilities, you need to use bindToButton() for keyboard and mouse button events and bindToScroll() for mouse scroll wheel events.
See Keynums.h for constants that are mapped to specific keys on the keyboard as well as are mapped to the mouse buttons and scroll wheel.
The CartesianCanvas handles I/O in exactly the same way as the standard Canvas does (since it is a subclass of the Canvas class).
That completes this tutorial!
Next up, in Command-line arguments, we take a look at using command-line arguments in our animations.
2D
- Using Background
- Using Canvas
- Using Shapes
- Using Text and Images
- Using Colors
- Animation Loops
- Using Functions
- Using Keyboard And Mouse
- Command-line arguments
- Bringing It All Together
3D