SDL C++ Mouse Input Handling Toggle Effect - c++

So I'm working on a tower defense game. And I need to select a tile where the user clicks the mouse (right now i'm just drawing a border around the tile). Now the code I have works sometimes and I'm not sure how to get it to work better. Right now with the code I have when I click on an area, the tile gets selected and sometimes it deselects right away and other times it doesn't. Almost like I'm holding the mouse down and it just keeps "clicking" the mouse. What I need is for it to select the tile without deselecting it no matter how long the mouse button is being pressed. My mouse input class is set up like this:
MouseInput.h
#pragma once
#include <SDL.h>
class MouseInput
{
public:
MouseInput();
~MouseInput();
void Update();
bool GetMouseButton(int index);
SDL_Event GetEvent();
bool GetPressed();
bool GetReleased();
private:
void GetButtonStates();
private:
bool mouseArray[3];
int x, y;
bool justReleased;
bool justPressed;
SDL_Event e;
};
MouseInput.cpp
#include "Input.h"
MouseInput::MouseInput()
:
x(0),
y(0),
justReleased(false),
justPressed(false)
{
SDL_PumpEvents();
for (int i = 0; i < 3; i++)
{
mouseArray[i] = false;
}
}
MouseInput::~MouseInput()
{
}
void MouseInput::Update()
{
SDL_PollEvent(&e);
justReleased = false;
justPressed = false;
if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT)
justPressed = true;
else if (e.type == SDL_MOUSEBUTTONUP && e.button.button == SDL_BUTTON_LEFT)
justReleased = true;
}
bool MouseInput::GetPressed()
{
return justPressed;
}
bool MouseInput::GetReleased()
{
return justReleased;
}
bool MouseInput::GetMouseButton(int index)
{
if (index <= 3 && index >= 1)
{
return mouseArray[index - 1];
}
return false;
}
SDL_Event MouseInput::GetEvent()
{
return e;
}
void MouseInput::GetButtonStates()
{
SDL_PollEvent(&e);
if (e.type == SDL_MOUSEBUTTONDOWN)
{
// 1 = Left; 2 = Center; 3 = Right
mouseArray[e.button.button - 1] = true;
}
if (e.type == SDL_MOUSEBUTTONUP)
{
mouseArray[e.button.button - 1] = false;
}
}
In my Game class I have setup the mouseinput as well as a variable to store the coordinates of the tile that was clicked. I also Have a variable pressed to check to see if the mouse was pressed. In my updating function I check if the mouse was not pressed. If the mouse was not pressed, then I check if the mouse is pressed that way the game can get the coordinates of the mouse position. I know that sounds crazy so here is some of my code:
std::pair<int, int> coords; // Will store the coordinates of the tile position that the mouse rests in
bool pressed; // If the mouse was pressed (i should call it toggle because it's true if the mouse was pressed once, then false if the mouse was pressed again, then true...ect.)
Updating function
Mouse Class
bool clicked; // In my custom Mouse.h file and set to false in constructor
MouseInput input; // In my custom Mouse.h file
Mouse class Update()
input.Update(); // Calls the MouseInput updating
if (input.GetPressed() && !clicked)
{
clicked = !clicked;
printf("OK");
}
//else if (clicked && input.GetReleased())
else if (clicked && input.GetPressed()) // Somewhat works. It works almost like before. If I click the mouse it shows up then sometimes when I click again it works, and other times I have to click a couple times
{
clicked = !clicked;
}
In my game class I just check if mouse clicked is true, then draw what i need
if (mouse.GetClicked())
{
// Draw what I need here.
// Currently is only drawn when I hold down the mouse
// And not drawn when I release the mouse.
}
Then when I'm drawing the square around the tile, I only draw it if pressed = true. Otherwise it is not drawn. This code works sometimes, and other times it doesn't. In order to get the effect I want, I have to real quickly click the mouse. If I don't, sometimes it appears as if I'm holding the mouse button down and the square just flashes.

The issue is that the "mouse-down" event is exactly that - an event. It should happen once, be handled, and then not occur until the button is physically pressed again. With your code, once pressed it'll set the internal state to "pressed" and then not change it until it's released. Which is okay, except your code polling this internal state treats it as if it's an event, not as state, so every time the update function runs while the mouse button is pressed, it'll see a "new button press".
This (simplified version of your code that uses one button and an idea of showing an image only when a button's pressed) might help you visualise it:
bool buttonPressed = false;
bool imageShown = false;
while (true)
{
SDL_Event e;
SDL_PollEvent(&e);
if (e.type == SDL_MOUSEBUTTONDOWN)
buttonPressed = true;
else if (e.type == SDL_MOUSEBUTTONUP)
buttonPressed = false;
if (!imageShown && buttonPressed)
imageShown = true;
else if (buttonPressed)
imageShown = false;
}
Do you see how each time the loop runs, even if no events have been fired this iteration, the state remains? This would cause the imageShown variable to keep toggling on and off each iteration, until a "button up" event is fired to reset the state.
You should probably rework a bit of your input controller - you need to expose a way to differentiate from "button being pressed right now" and "button that's been pressed for a while".
An idea would be to provide eg. ButtonsPressed and ButtonsJustPressed members (although I'm sure you can think of more appropriate names). The ButtonsJustPressed members should be reset to false each time you call GetButtonStates, so they're only true if on that specific iteration of the loop, they've been pressed, while the ButtonsPressed members are exactly what you're doing right now.
A tweaked version of the above to take this into account:
bool buttonJustPressed = false;
bool buttonJustReleased = false;
bool imageShown = false;
while (true)
{
// As far as we know, this iteration no events have been fired
buttonJustPressed = false;
buttonJustReleased = false
SDL_Event e;
SDL_PollEvent(&e);
if (e.type == SDL_MOUSEBUTTONDOWN)
buttonJustPressed = true;
else if (e.type == SDL_MOUSEBUTTONUP)
buttonJustReleased = true;
if (!imageShown && buttonJustPressed)
imageShown = true;
else if (imageShown && buttonJustReleased)
imageShown = false;
}
See how this version will only change the visibility of the image if a button has just been pressed/released - if the button's being held down, the events aren't fired, so the state remains false, and the image's state remains the same.

Related

Multiple key presses SFML

I'm trying to detect keyPress only once, but it goes for random amount of presses. I'm using keyrelease, and it work normally if I have breakpoint.
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
} else {
characterScreen.handleEvent(event, window);
}
}
bool keyReleased = event.type == sf::Event::KeyPressed;
bool rightArrowKey = event.key.code == sf::Keyboard::Right;
bool leftArrowKey = event.key.code == sf::Keyboard::Left;
if (keyReleased&&leftArrowKey)
{
if (selectedCharacter == 0)
{
selectedCharacter = characterList.size() - 1;
}
else
{
selectedCharacter--;
}
changeCharacters(characterList[selectedCharacter], font, characterScreen);
std::cout << "left pressed" << std::endl;
keyReleased = false;
std::cout << keyReleased << std::endl;
}
else if (keyReleased && rightArrowKey)
{
if (selectedCharacter == characterList.size()-1)
{
selectedCharacter = 0;
}
else
{
selectedCharacter++;
}
changeCharacters(characterList[selectedCharacter], font, characterScreen);
}
window.clear(sf::Color::White);
characterScreen.update();
characterScreen.render(window);
window.display();
}
I've tryed window.setKeyRepeatEnabled(false); and keypressed, still nothing. Tryed different keyboard aswell.
When using (C)SMFL, we generally have a main loop condition rendering the main frame (the windows) and checking events, it can be an infinite loop (such as while (1)) or a more recomanded one like your while (window.isOpen()).
It means that the windows and all events listener are refreshed / updated each loop's turn, so if you press a key on your keyboard, you will trigger your keyboard's event listener, fine, and you will continue your code until re-enter in your main loop, and re-refreshing the windows and re-updating your event listener.
What the point of explaining that ? it just means by only pressing once a key, your computer will process maybe 10, 100 or maybe 1000 iteration of your main loop (depending on it's performances and more). So it's "normal" to works fine when you use convenient features like breakpoints, but in reality you can't.
You have a lot of possibilities, bad ones (like "use delay / pause" who's not recommended since it will halt your window's refresh and can be interpreted by a crash by your OS) or good ones like tampon variables, who'll cancel the entrance of some conditions by their state:
Example:
bool tmp_var = false;
while (window.isOpen()) {
// Warn, you called your variable keyReleased but it check keyPressed
if (event.type == sf::Event::KeyPressed && tmp_var == false) {
// Check wich key is pressed and perform required actions
tmp_var = true;
} else if (event.type == sf::Event::KeyReleased) {
tmp_var = false;
}
}
Note that the code can be not correct, I wanted to point out the reasoning

OpenGL GLUT left mouse click overrides holding right mouse

I'm adapting some skeleton code to learn how OpenGL works and have in SphericalCameraManipulator.cpp which allows me to pan and tilt the camera while I hold down right mouse:
void SphericalCameraManipulator::handleMouseMotion(int x, int y)
{
//Motion
float xMotion = (float)x - previousMousePosition[0];
float yMotion = (float)y - previousMousePosition[1];
if(reset)
{
xMotion = yMotion = 0.0;
reset = false;
}
this->previousMousePosition[0] = (float)x;
this->previousMousePosition[1] = (float)y;
//Right Button Action
if(this->currentButton == GLUT_RIGHT_BUTTON && this->currentState == GLUT_DOWN)
{
this->pan -= xMotion*this->panTiltMouseStep ;
this->tilt += yMotion*this->panTiltMouseStep ;
}
//Enforce Ranges
this->enforceRanges();
}
I deleted the left mouse action as I don't want it to control the camera, and it now actions a command in the main code body.
//Left Button Action
if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
... //game action
}
My problem is that when I click the left mouse, the press of the right mouse is cancelled, and I must release and repress the right mouse to continue controlling the camera.
Is there a way to prevent this from happening? It interrupts the flow of the game. I'm using GLUT
Rabbid76's comment saying there is no mouse hold event set me on the right path.
I wrote a small function that simply recorded the last state of mouse button 2 (the 'camera look around' button):
void SphericalCameraManipulator::handleMouse(int button, int state)
{
this->currentButton = button;
this->currentState = state;
//to enable mouse look around
if (this->currentButton == 2) //right mouse
this->lastLookState = this->currentState;
if(this->currentState == GLUT_UP)
{
reset = true;
}
}
This mean even when the current state was regarding the left mouse button (game action) it was still possiblew to look around since a 'mouse two button up' event had not occured:
this->currentButton = 2;
this->currentState = 1;

Using GLFW to capture mouse dragging? [C++]

So i'm trying to capture mouse dragging in my OpenGL application. I've done the following so far:
glfwSetMouseButtonCallback(window, mouse_callback);
static void mouse_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button == GLFW_MOUSE_BUTTON_LEFT) {
double x;
double y;
glfwGetCursorPos(window, &x, &y);
if (previous_y_position - y > 0)
{
camera_translation.y -= 1.0f;
previous_y_position = y;
}
else
{
camera_translation.y += 1.0f;
previous_y_position = y;
}
}
}
The problem with this though is if I would like to zoom in I need to move my mouse upwards and then click repeatedly. For some reason if I press down on the left mouse button and drag upwards it does nothing.
In cursor_pos_callback, just confirm if the button is pressed, and that works.
void mouse_cursor_callback( GLFWwindow * window, double xpos, double ypos)
{
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_RELEASE)
{
return;
}
// `write your drag code here`
}
mouse_callback is stateless. It receives events, momentary "actions".
You need to make your program to "remember" that mouse button is pressed. So that when button is pressed in a frame 1, you can refer to this information in all the frames after that and before mouse button is released.
The simple way is to flip a boolean flag on press/release:
static void mouse_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button == GLFW_MOUSE_BUTTON_LEFT) {
if(GLFW_PRESS == action)
lbutton_down = true;
else if(GLFW_RELEASE == action)
lbutton_down = false;
}
if(lbutton_down) {
// do your drag here
}
}
Schematically:
state released pressed released
timeline -------------|------------------------------|---------------
^ ^
mouse_callback calls GLFW_PRESS GLFW_RELEASE
The hard way is to use a state machine (especially if you need more complicated combinations of states of input controllers).

SDL: how to get the mouse motion while the left mouse button is down using case statement

I am currently working with SDL2 and am fairly new to it. I am trying to use case statments to get the mouse motion coordinates only while the left mouse button is pressed down.
In the end, I need to be able to click on a object and find how much the mouse is dragged from that selected object.
So far I have been able to get the mouse press and the mouse motion working separately, but not at the same time.
Here is my code for the mouse press event:
void SDL::OnEvent(SDL_Event *_event)
{
Mallet mallet;
switch (_event->type)
{
case SDL_QUIT:
m_running = false;
break;
default:
break;
case SDL_KEYUP:
switch (_event->key.keysym.sym)
{
case SDLK_SPACE:
if(m_playerTurn == 1)
m_playerTurn = 2;
else
m_playerTurn = 1;
std::cout<<"player turn = "<<m_playerTurn<<std::endl;
break;
}
case SDL_MOUSEBUTTONDOWN:
switch(_event->button.button)
{
case SDL_BUTTON_LEFT:
int x = _event->button.x;
int y = _event->button.y;
if(m_playerTurn == 1)
{
bool collision = checkCollision(x, y, m_player1->getTeamMallets(), mallet);
if(collision)
std::cout<<"collision with P1"<<std::endl;
}
if(m_playerTurn == 2)
{
bool collision = checkCollision(x, y, m_player2->getTeamMallets(), mallet);
if(collision)
std::cout<<"collision with P2"<<std::endl;
}
break;
}
}
}
Can anyone help.
Many thanks in advance.
Will
on SDL_MOUSEBUTTONDOWN set variable click = true ans save x,y coordinates,
on SDL_MOUSEMOTION check if click == true and update x,y coordinates,
on SDL_MOUSEBUTTONUP set click = false and calculate distance.
http://lazyfoo.net/tutorials/SDL/17_mouse_events/index.php

SFML2 Mouse::isButtonPressed filling up event queue

First of all I'd like to say that I'm not sure if the title is appropriate but it's the only logical explanation I could find.
What I'm trying to do is move my window by clicking a sprite (sort of like simulating a titlebar).
All working fine until I let go of the mouse button the rate new mouse events are emitted is way lower and with huge 1.5~2s pauses between them.
Is it possible that sf::Mouse::isBUttonPressed is filling the queue or is it another issue?
Edit: The window class has a sf::Event object and passes it to each object's event handler.
The sprite class has an event handler of this form:
bool object::handleEvents(sf::Event& event)
{
switch(event.type)
{
case sf::Event::MouseMoved:
case sf::Event::MouseButtonPressed:
case sf::Event::MouseButtonReleased:
{
auto mouse_pos = sf::Mouse::getPosition(*this->parent);
if(this->isPointInside(mouse_pos))
{
if(event.type == sf::Event::MouseMoved)
{
this->hovering = true;
if(this->callback["onHover"])
this->callback["onHover"](this, nullptr);
return true;
}
else if(event.type == sf::Event::MouseButtonPressed)
{
this->clicked = true;
this->focused = true;
if(event.mouseButton.button == sf::Mouse::Left)
if(this->callback["onLClick"])
this->callback["onLClick"](this, ref(mouse_pos));
if(event.mouseButton.button == sf::Mouse::Right)
if(this->callback["onRClick"])
this->callback["onRClick"](this, ref(mouse_pos));
return true;
}
else if(event.type == sf::Event::MouseButtonReleased && this->clicked)
{
this->clicked = false;
if(event.mouseButton.button == sf::Mouse::Left)
if(this->callback["onLClickReleased"])
this->callback["onLClickReleased"](this, ref(mouse_pos));
if(event.mouseButton.button == sf::Mouse::Right)
if(this->callback["onRClickReleased"])
this->callback["onRClickReleased"](this, ref(mouse_pos));
return true;
}
}
else
{
if(this->hovering)
{
if(this->callback["onHoverLost"])
this->callback["onHoverLost"](this, nullptr);
this->hovering = false;
}
}
}break;
default: ;
}
return false;
}
and the code responsible for moving the window:
titlebar->callback["onLClick"] = [&](object* obj, void* data)
{
sf::Vector2i* relpos = (sf::Vector2i*)(data);
while(sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
sf::Vector2i abspos = sf::Mouse::getPosition();
window.setPosition(sf::Vector2i((abspos.x - relpos->x),(abspos.y - relpos->y)));
}
titlebar->clicked = false;
};
The sf::Mouse class as well as sf::Keyboard and sf::Joystick aren't connected to the event system, but are completely independent, thus it's impossible that isButtonPressed would've any influence on the events.
The real problem in your code is the 'infinite' loop when the left mouse button is pressed. If the left mouse button is pressed, everything that happens in your application is, that the window gets moved around. There won't be any event dispatches (= processing) and every event that happens within that time, will get pilled in the event queue. Thus when you return to process the events, you'll have a longer queue than usual and will start dispatching with the oldest event.
So if you now move your window around for 2 seconds, you'll get a filled queue worth 2 seconds which can delay the further processing.
To solve this problem, you'll most probably have to dispatch all the events while you're moving the window.