I have a minor problem in my code and can't resolve it.
I created a Button class using SFML library. Its just code practice since I'm relatively new to programing.
But the problem is that I can't make it work on release, it only works when pressed. So instead of one click it registers multiple clicks while it is being pressed. It's best to try the given code to see what I mean.
Thanks in advance!
Here is code for Button class:
class Button
{
private:
//Button Text Params:
sf::Font font;
sf::Text text;
sf::Vector2f textPos;
sf::Vector2f textBounds;
//Button Shape Params:
sf::RectangleShape shape;
sf::Vector2f pos;
sf::Vector2f size;
//Button colors(Hover and Default)
sf::Color DefaultColor;
sf::Color hColor;
//Button Sounds:
sf::SoundBuffer bf1;
sf::SoundBuffer bf2;
sf::Sound HoverSound;
sf::Sound ClickSound;
bool wFocus = false; //Returns focus on main window
bool ButtonClick = false; //Controls the code on button click
bool CursorOverButton(sf::RenderWindow &window) //Detect cursor pos on window
{
sf::Vector2f CursorPos = (sf::Vector2f)sf::Mouse::getPosition(window);
if((CursorPos.x < (pos.x + size.x) && CursorPos.x > pos.x) && (CursorPos.y < (pos.y + size.y) && CursorPos.y > pos.y))
{
if(shape.getFillColor() != hColor){ //if cursor passes over window
shape.setFillColor(hColor); //all of this will execute
HoverSound.play();
}
return true;
}
else
{
shape.setFillColor(DefaultColor);
return false;
}
}
public:
Button()
{
font.loadFromFile("arial.ttf"); //Loads font for Button text default is ariel(change to what you want!)
}
//Button Creation:
void create(std::string ButtonText, int TextSize, sf::Color Color, float buttonWidth, float buttonHeight, float x, float y, sf::Color HoverColor)
{
//Create Button size and position:
pos.x = x;
pos.y = y;
size.x = buttonWidth;
size.y = buttonHeight;
DefaultColor = Color;
hColor = HoverColor;
//Load text for button:
text.setFont(font);
text.setCharacterSize(TextSize);
text.setString(ButtonText);
text.setOutlineColor(sf::Color::Black);
text.setOutlineThickness(2);
sf::FloatRect fr = text.getGlobalBounds();
textBounds.x = fr.width + fr.left;
textBounds.y = fr.height + fr.top;
//Apply parameters to shape:
shape.setSize(size);
shape.setFillColor(DefaultColor);
shape.setPosition(pos);
//Fit text to button:
text.setOrigin(textBounds.x/2, textBounds.y/2);
text.setPosition(pos.x + (size.x/2), pos.y + (size.y/2) - 1);
}
bool isButtonClicked(sf::RenderWindow &window) //When button is pressed down whis executes
{
if(!CursorOverButton(window)) //Tests focus on window
{
wFocus = (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))? true : false;
}
if(CursorOverButton(window) && !wFocus && sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))
{
if(!ButtonClick) ClickSound.play(); //Plays only on first click
ButtonClick = true;
return true;
}
else ButtonClick = false; //Return clicked state
return false;
}
void HoverClickSound(std::string HoverSoundPath1, std::string ClickSoundPath2) //add sounds to buffer and load into
{ //playable sound
bf1.loadFromFile(HoverSoundPath1);
HoverSound.setBuffer(bf1);
bf2.loadFromFile(ClickSoundPath2);
ClickSound.setBuffer(bf2);
}
void draw(sf::RenderWindow &window) //draws button on window(with text)
{
window.draw(shape);
if(textBounds.x < size.x || textBounds.y < size.y) window.draw(text);
}
};
Full Code with test is here on github
So I came across a solution(for the most part) it still registers on being pressed but registers only the first initial press down, once, and doesn't execute the wanted code more then once. Say you want to increment a value by one when button is clicked it used to run the code while button is pressed multiple times(because of the refresh rate), now this part of code wont let it continue after initial press down:
bool isButtonClicked(sf::RenderWindow &window) //When button is pressed down whis executes
{
if(!CursorOverButton(window)) //Tests focus on window
{
wFocus = (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))? true : false;
}
if(CursorOverButton(window) && !wFocus && sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))
{
if(!ButtonClick) //This wont let it execute more then once on click
{
ClickSound.play();
ButtonClick = true;
return true;
}
else return false;
}
else ButtonClick = false; //Return clicked state
return false;
}
Thoughts?
Related
So i'm trying to make a basic gui with sfml and need a scroll bar to scroll through a loop of drawables. After research I learned that view manipulation would be the way to do it. I have a rectangle for an outline that i'm trying to use as the view and have set up the bar itself i just need it to change the view. My issue is that I isn't drawing anything I put inside the view. If it won't draw anything in the view its hard to scroll through it. All help is appreciated thanks.
my deliration
projectsBox = sf::RectangleShape(sf::Vector2f((400 * scale) - (2 * (10 * scale)), 225 * scale));
projectsBox.setOrigin(sf::Vector2f(-10 * scale, -130 * scale));
projectsBox.setOutlineColor(sf::Color::Black);
projectsBox.setOutlineThickness(10 * scale);
projectsBox.setFillColor(sf::Color::Transparent);
projectsVeiw.setViewport(projectsBox.getGlobalBounds());
void sfmlWindow::drawProjects() {
sf::CircleShape base;
base.setRadius(30);
base.setScale(0, userProjects->projects[1].name.length());
base.setFillColor(sf::Color::Blue/*sf::Color(33, 33, 33, 270)*/);
base.setOrigin(0, 0);
sf::Text text;
textInit(&text, userProjects->projects[1].name, 96);
text.setOrigin(0, 0);
projectsDrawables.emplace_back(std::make_unique<sf::CircleShape>(base));
projectsDrawables.emplace_back(std::make_unique<sf::Text>(text));
}
My Loop
while (mainWindow.isOpen()) {
eventLoop();
mainWindow.clear(sf::Color::Transparent);
//Do project veiw
mainWindow.setView(projectsVeiw);
drawProjects();
for (std::unique_ptr<sf::Drawable>& i : projectsDrawables) {
mainWindow.draw(*i);
}
//draw drawbles
mainWindow.setView(mainWindow.getDefaultView());
for (std::unique_ptr<sf::Drawable>& i : allDrawables) {
mainWindow.draw(*i);
}
mainWindow.draw(projectsBox);
projectsSlider.setSize(sf::Vector2f(15, slidersize()));
mainWindow.draw(projectsSlider);
//render
mainWindow.display();
}
I've adapted your code to something that compiles and actually draws stuff on the screen. It's not how you want it to look but it show drawing through both views working. You should be able to experiment with this to progress.
projectDrawables are located around 1000,1000 in your world where as allDrawables are positioned around 0,0. To shrink you projectBox to just be in the corner you should increase the size of the projectView.
#include <iostream>
#include <SFML/Graphics.hpp>
const int BOARD_SIZE = 40;
const float TILE_SIZE = 20.0f;
std::vector<std::unique_ptr<sf::Drawable> > projectsDrawables;
std::vector<std::unique_ptr<sf::Drawable> > allDrawables;
sf::Font font;
void drawProjects() {
sf::CircleShape base;
base.setRadius(30);
base.setFillColor(sf::Color::Blue);
base.setPosition(1000, 1000);
sf::Text text;
text.setFont(font);
text.setFillColor(sf::Color::White);
text.setString("hello");
text.setCharacterSize(96);
text.setOrigin(1100, 1000);
projectsDrawables.emplace_back(std::make_unique<sf::Text>(text));
projectsDrawables.emplace_back(std::make_unique<sf::CircleShape>(base));
sf::CircleShape base2;
base2.setRadius(50);
base2.setFillColor(sf::Color::Red);
base2.setPosition(30, 80);
sf::Text text2;
text2.setFont(font);
text2.setFillColor(sf::Color::Green);
text2.setString("Cheese");
text2.setCharacterSize(48);
text2.setPosition(30, 30);
allDrawables.emplace_back(std::make_unique<sf::Text>(text2));
allDrawables.emplace_back(std::make_unique<sf::CircleShape>(base2));
}
int main()
{
sf::RenderWindow mainWindow(sf::VideoMode((2+BOARD_SIZE) * (int)TILE_SIZE, (2+BOARD_SIZE) * (int)TILE_SIZE), "Snake");
if (!font.loadFromFile("Instruction.ttf") ) {
std::cerr << "Font error." << std::endl;
exit( -1 );
}
sf::Clock clock;
auto scale = 1;
auto projectsBox = sf::RectangleShape(sf::Vector2f(400, 225));
projectsBox.setPosition(1000,1000);
projectsBox.setOutlineColor(sf::Color::Green);
projectsBox.setOutlineThickness(10 * scale);
projectsBox.setFillColor(sf::Color::Transparent);
sf::View projectsView( projectsBox.getGlobalBounds());
while (mainWindow.isOpen()) {
sf::Time elapsed = clock.getElapsedTime();
if (elapsed.asSeconds() > 0.2f) {
clock.restart();
}
sf::Event event;
while (mainWindow.pollEvent(event)) {
if (event.type == sf::Event::Closed)
mainWindow.close();
// Respond to key pressed events
if (event.type == sf::Event::KeyPressed){
if (event.key.code == sf::Keyboard::Escape){
return 0;
}
}
}
mainWindow.clear(sf::Color::Black);
mainWindow.setView(projectsView);
mainWindow.draw(projectsBox);
//Do project veiw
drawProjects();
for (std::unique_ptr<sf::Drawable>& i : projectsDrawables) {
mainWindow.draw(*i);
}
//draw drawbles
mainWindow.setView(mainWindow.getDefaultView());
for (std::unique_ptr<sf::Drawable>& i : allDrawables) {
mainWindow.draw(*i);
}
//projectsSlider.setSize(sf::Vector2f(15, slidersize()));
//mainWindow.draw(projectsSlider);
//render
mainWindow.display();
}
return 0;
}
This is code after includes:
Sprite player;
Texture playerTexture;
IntRect playerContainer(0, 0, 32, 32);
Vector2f playerPosition;
int playerDirection = 0; // 0 - fwd, 1 - back, 2 - stay
This is animation update method:
void updateAnims(Clock clock, float time) {
if(time > 0.3f) {
if(playerDirection == 0) playerContainer.top = 0;
else if(playerDirection == 1) playerContainer.top = 32;
else if(playerDirection == 2) playerContainer.top = 64;
if(playerContainer.left == 96) playerContainer.left = 0;
else playerContainer.left += 32;
player.setTextureRect(playerContainer);
clock.restart();
}
}
and this method is updated in "int main()" method.
int main() {
// Init window
RenderWindow window(VideoMode(800, 600), "RPG");
Clock gameClock;
Clock animClock;
float gameTime;
float animTime;
// Setting up the player
playerTexture.loadFromFile("player.png");
player.setTexture(playerTexture);
player.setTextureRect(playerContainer);
player.setScale(Vector2f(3.f, 3.f));
playerPosition.x = 30;
playerPosition.y = 120;
player.setPosition(playerPosition);
while(window.isOpen()) {
Event event;
while(window.pollEvent(event)) {
if(event.type == Event::Closed) {
window.close();
}
}
checkInputs(gameTime);
animTime = animClock.getElapsedTime().asSeconds();
updateAnims(animClock, animTime);
window.clear();
gameTime = gameClock.getElapsedTime().asMilliseconds();
gameClock.restart();
window.display();
}
return 0;
}
Turns out that sprite is created in main method, gets texture and texture shape, gets position, but not drawn. Why?
I think think the problem is in animation method but I tried different variations of solutions.
You should call window.draw(player) method between window.clear() and window.display() to actually draw player sprite on screen.
I must use hover event on Qt label, but I can't found no information about that.
I try use something like ui->label->setText("<a>ads</a>") and onLinkHovered but it's not work correct.
I must change text on hover.
The most flexible solution would be to create your own widget which inherits from QLabel. This way, you could override the enterEvent and leaveEvent #Jeremy and #Moe are writing about which are protected. As a part of these methods implementation you could change the text or decoration accordingly. For example:
class CustomLabel : public QLabel
{
Q_OBJECT
public:
CustomLabel(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : blue; }");
}
void leaveEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : green; }");
}
};
Another approach, but a lot less flexible would be to set the href attribute for the link tag you have specified in label text. This way text would be treated as actual link and you could use the linkHovered signal to connect to. For example:
ui->label->setText("<a href='www.google.com'>link</a>");
connect(ui->label, &QLabel::linkHovered, this, [this](const QString&)
{
// do smth with the widget/text
});
However, please denote that this way you could only make a modification on the hover event.
So if you need to bring the label back to its original state, the first option is the way to go.
Make use of enterEvent and leaveEvent of QLabel.
Create a subclass of QLabel like this for example:
class MyLabel : public QLabel
{
public:
MyLabel();
MyLabel(char* text, MainWindow* w) : QLabel(text, w) { }
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
};
and override enterEvent and leaveEvent like this:
void MyLabel::enterEvent(QEvent *event) {
qDebug() << "Entered!";
// Change your text here
}
void MyLabel::leaveEvent(QEvent *event) {
qDebug() << "Left!";
}
You can create instances of this class now like this:
MainWindow w;
MyLabel myLabel("A Test Label", &w);
If you want to determine whether mouse hovers over text inside the QLabel you can use
void mouseMoveEvent(QMouseEvent* event);
and check inside it whether mouse cursor is in bounding rectangle of text.
void PressLabel::mouseMoveEvent(QMouseEvent* event) {
QRect bRect = getTextComponentRectangle();
m_mouseCoord = event->pos();
if(bRect.contains(event->pos())) {
// Mouse pointer over text.
} else {
// Mouse pointer outside text.
}
QLabel::mouseMoveEvent(event);
}
Turn on mouse tracking for your widget to make mouseMoveEvent occur also when mouse button is not pressed.
setMouseTracking(true);
And finally the function that does the calculation of text bounding rectangle. Code below take into account some unexpected cases, too. I tried it with Qt 5.9.1.
For calculation of effective indent in negative case refer to http://doc.qt.io/archives/qt-4.8/qlabel.html#indent-prop .
QRect PressLabel::getTextComponentRectangle() const {
if(frameWidth() < 0) {
throw std::runtime_error("Negative frame width.");
}
int effectiveIndent = indent();
int trueMargin = margin();
if(effectiveIndent < 0) {
if(frameWidth() == 0 || margin() > 0) { // (1)
effectiveIndent = 0;
} else if(frameWidth() > 0) {
QFontMetrics fm(font());
effectiveIndent = fm.width(QChar('x')) / 2;
}
if(frameWidth() > 0 && margin() < 0) { // (2)
trueMargin = 0;
}
}
QFontMetrics fm(font());
QRect bRect = fm.boundingRect(text());
bRect.setWidth(fm.width(text()));
int indentOffset = effectiveIndent + trueMargin + frameWidth();
int offsetX = 0;
int offsetY = 0;
if(alignment() & Qt::AlignHCenter) {
offsetX = rect().width() / 2 - bRect.width() / 2;
} else if(alignment() & Qt::AlignRight) {
offsetX = rect().width() - bRect.width() - indentOffset;
} else if(alignment() & Qt::AlignJustify) {
offsetX = trueMargin + frameWidth();
} else if(alignment() & Qt::AlignLeft) {
offsetX = indentOffset;
}
if(alignment() & Qt::AlignVCenter) {
offsetY = rect().height() / 2 - bRect.height() / 2;
} else if(alignment() & Qt::AlignBottom) {
offsetY = rect().height() - bRect.height() - indentOffset;
} else if(alignment() & Qt::AlignTop) {
offsetY = indentOffset;
}
bRect.moveTopLeft(rect().topLeft());
bRect.setX(bRect.x() + offsetX);
bRect.setWidth(bRect.width() + offsetX);
bRect.setY(bRect.y() + offsetY);
bRect.setHeight(bRect.height() + offsetY);
return bRect;
}
Unexpected cases:
(1) For indent < 0 and margin > 0 effective indent is 0, not width('x')/2 as it is intended to be for negative indent.
(2) For indent < 0 and margin < 0 true margin is 0 and isn't summed to make offset.
I have found that you can also achieve this through the style sheet.
self.label = QtWidgets.QLabel("Toast")
self.label.setStyleSheet("QLabel{color: white;} QLabel:hover {color: blue;}")
I've got an application which handles zooming in/out using the mouse wheel with this event in Qt Creator.
cpp
void QNodeView::wheelEvent(QWheelEvent* event) {
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
// Scale the view / do the zoom
double scaleFactor = 1.15;
if(event->delta() > 0) {
// Zoom in
scale(scaleFactor, scaleFactor);
} else {
// Zooming out
scale(1.0 / scaleFactor, 1.0 / scaleFactor);
}
}
This is in the header file
h
protected:
//Take over the interaction
virtual void wheelEvent(QWheelEvent* event);
How can I add the ability to pan with the middle mouse button being pressed the user dragging the cursor?
I can post the project code if necessary just ask.
Thanks
Project files link (Qt Creator project)
https://www.dropbox.com/s/gbt4qqtdedltxek/QNodesEditor-master_01.zip?dl=0
At first, introduce some new member variables into your viewer class:
class QNodeView : public QGraphicsView
{
// ...
private:
int m_originalX = 0;
int m_originalY = 0;
bool m_moving = false;
};
Then reimplement mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent().
void QNodeView::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::MiddleButton)
{
// store original position
m_originalX = event->x();
m_originalY = event->y();
// set the "moving" state
m_moving = true;
}
}
void QNodeView::mouseMoveEvent(QMouseEvent* event)
{
if (m_moving)
{
// panning operates in the scene coordinates using x,y
QPointF oldp = mapToScene(m_originalX, m_originalY);
QPointF newp = mapToScene(event->pos());
QPointF translation = newp - oldp;
translate(translation.x(), translation.y());
m_originalX = event->x();
m_originalY = event->y();
}
}
void QNodeView::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::MiddleButton)
{
m_moving = false;
}
}
Currently I have this player.cpp class that I'm using for my sprite animations. I'm using a counter to update every frame. It animates, but it flies through the animations.
I want to slow this down. I found code that can be used to slow down sprite animations but I'm unsure how to implement it into my current program.
Below are my player.cpp file and following it is the code I found that can slow down sprite animations. When I've tried to add a clock to the counterWalking++ it didn't animate at all, and I've tried implementing this code to the same effect.
player::player()
{
rect.setSize(sf::Vector2f(32, 32));
rect.setFillColor(sf::Color::White);
rect.setPosition(300, 300);
sprite.setTextureRect(sf::IntRect(0, 0, 32, 32));
}
void player::update()
{
sprite.setPosition(rect.getPosition());
}
void player::updateMovement()
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
if (canMoveRight == true) {
rect.move(movementSpeed, 0);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 64, 32, 32));
direction = 4;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
if (canMoveLeft == true) {
rect.move(-movementSpeed, 0);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 32, 32, 32));
direction = 3;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
if (canMoveUp == true) {
rect.move(0, -movementSpeed);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 96, 32, 32));
direction = 1;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
if (canMoveDown == true) {
rect.move(0, movementSpeed);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 0, 32, 32));
direction = 2;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else {
//Player not moving
}
counterWalking++;
if (counterWalking == 3)
counterWalking = 0;
}
Here is the code I found that displays a slow animation:
int main()
{
sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "Demo Game");
sf::Event event;
sf::Texture texture;
texture.loadFromFile("images/player.png");
sf::IntRect rectSourceSprite(0, 0, 32, 32);
sf::Sprite sprite(texture, rectSourceSprite);
sf::Clock clock;
while (renderWindow.isOpen()) {
while (renderWindow.pollEvent(event)) {
if (event.type == sf::Event::EventType::Closed)
renderWindow.close();
}
if (clock.getElapsedTime().asSeconds() > 1.0f) {
if (rectSourceSprite.left == 96)
rectSourceSprite.left = 0;
else
rectSourceSprite.left += 32;
sprite.setTextureRect(rectSourceSprite);
clock.restart();
}
renderWindow.clear();
renderWindow.draw(sprite);
renderWindow.display();
}
}
Since a solution has been given in comment but not implemented in an answer, here's some kind of "code review"
Key Binding
This code is actually using the sf::Keyboard::isKeyPressed and sf::Keyboard::isKeyReleased wich needs to be called every loop while a simple boolean switched when the key is pressed/released could to the work using events.
while (renderWindow.pollEvent(event)) {
if (event.type == sf::Event::EventType::Closed)
renderWindow.close();
}
The Event used in the main loop does also contain information about KeyEvents but isn't used that way. I'll suggest sending the evet to the player eac time a key Event happens:
while (renderWindow.pollEvent(event)) {
switch(event.type){
case sf::Event::EventType::Closed:
renderWindow.close();
break;
case sf::Event::KeyPressed: case sf::Event::KeyReleased:
myPlayer.keyEvent(event);
break;
}
}
The keyEvent function in player should then look like this:
void player::keyEvent(sf::Event event){
//Keycode of your keyboard's arrows goes from 71 to 74
if (event.key.code >= 71 && event.key.code <= 74){
//Gets the array ID from the enumeration value
int ID = event.key.code - 71;
//Stores false if the key is release, and true if it's pressed
keys[ID] = (event.type == sf::Event::KeyPressed);
}
}
Wich changes each values of the array in Player.h:
private:
bool keys[4];
Movement
Now all you have to do is call the player update() function in your game loop each second:
if (clock.getElapsedTime().asSeconds() > 1.0f) {
if (rectSourceSprite.left == 96)
rectSourceSprite.left = 0;
else
rectSourceSprite.left += 32;
sprite.setTextureRect(rectSourceSprite);
myPlayer.update();
clock.restart();
}
In the update() function, ou will then build you movement vector based on wich key is pressed:
void player::update()
{
sf::Vector2f movementVector(0,0);
//Left
if (keys[0]){
movementVector.x -= movementSpeed;
//Sends the sprite Rectangle position based on the movement type
move(32);
}
//Right
if (keys[1]){
movementVector.x += movementSpeed;
move(64);
}
//Up
if (keys[2]){
movementVector.y -= movementSpeed;
move(96);
}
//Down
if (keys[3]){
movementVector.y += movementSpeed;
move(0);
}
}
The move function will then move your sprite Position depending on the movement vector and the counter, the sprite will then be set.
Your movement counter will be incremented up to 30 and your player may only move when this counter is equal to 0, 10 or 20:
void move(int spritePos){
//Here, the check() function should tell if the player isn't going outside of the screen / in a wall
if (check(sprite.getPosition() + movementVector)){
sprite.move(movementVector);
++counterWalking %= 30;
if(!counterWalking % 10)
sprite.setTextureRect(sf::IntRect(counterWalking / 10 * 32, spritePos, 32, 32));
}
}
There are multiple ways of doing this, that's only the way I would do it
Sounds like you might need to learn about controlling the time-step. Essentially you only execute certain code when a desired amount of time has passed.
You'll commonly see this site referenced so maybe check it out:
https://gafferongames.com/post/fix_your_timestep/