Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Dragging a grid, *without* clicking on it  (Read 384 times)

0 Members and 1 Guest are viewing this topic.

abcd1234567

  • Newbie
  • *
  • Posts: 23
    • View Profile
Dragging a grid, *without* clicking on it
« on: November 11, 2023, 05:57:51 pm »
I'm trying to simulate Game of Life. In my program, the user starts with an empty grid, they click on some squares, and input the pattern by pressing Enter.

I want to implement the ability to drag the screen like in this site: https://playgameoflife.com.
Example: https://gifyu.com/image/SR2kj

The conflict I'm facing is: when clicking to drag, that counts as a click, thus everytime I drag I end up clicking on a square. This is not like the in the example I provided.

I thought about using old_view_pos and new_view_pos, basically saying "register the input only if the distance between them is smaller than some number". Problem is - they change very quickly. So if I drag and than pause for a bit (but still clicking), it will still register the input.

Would appreciate hearing from you how would you tackle this. Also, would appreciate getting feedback about using 'isButtonPressed' against putting this logic in an event loop.

Thank you very much in advanced. This is my code:

#include <iostream>
#include <SFML/Graphics.hpp>
#include <unordered_set>
#include <cmath>
#include "game_logic.h"

inline float distance(sf::Vector2f vec1, sf::Vector2f vec2){
    return sqrt(pow(vec2.x - vec1.x, 2) + pow(vec2.y - vec1.y, 2));
}

int main(){

    // We implement the grid using a sparse matrix - which is just a set that stores only the *live cells*.
    std::unordered_set<sf::Vector2i, pair_hash, pair_equal> grid;

    sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Game of life", sf::Style::Default);
    bool focus = true; // True iff focus is on window.
    sf::View view(sf::FloatRect(WINDOW_WIDTH * (MULTIPLE / 2), WINDOW_HEIGHT * (MULTIPLE / 2), WINDOW_WIDTH, WINDOW_HEIGHT));
    window.setView(view);

    bool getting_input = false;
    getting_input = true;

    sf::Clock timestep_clock;
    sf::Vector2f old_view_pos = window.mapPixelToCoords(sf::Mouse::getPosition(window)), new_view_pos;
    while (window.isOpen()){
        sf::Event evnt;
        while (window.pollEvent(evnt)){
            switch(evnt.type){
                // The program exits when user closes the window by pressing &#39;X&#39;.
                case sf::Event::Closed:
                    window.close();
                    return 0;
                    break;

                    // Submitting input.
                    if (evnt.key.code == sf::Keyboard::Enter){
                        if (getting_input){
                            getting_input = false;
                        }
                    }
                    break;

                // Because &#39;isKeyPressed&#39; is "connected" to the actual device, it&#39;s getting input even when window is out of focus.
                // We want to accept keyboard input only when the window has focus, so we have to keep track of it using a boolean.
                case sf::Event::GainedFocus:
                    focus = true;
                    break;
                case sf::Event::LostFocus:
                    focus = false;
                    break;

                case sf::Event::MouseButtonReleased:
                    if (getting_input && evnt.mouseButton.button == sf::Mouse::Left){
                        handleLeftClick(window, grid);
                    }
                    break;
            }
        }

        new_view_pos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
        if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)){

            sf::Vector2f deltaPos = old_view_pos - new_view_pos;
            view.move(round(deltaPos.x), round(deltaPos.y));
            window.setView(view);
        }
        old_view_pos = window.mapPixelToCoords(sf::Mouse::getPosition(window));

        if (TIMESTEP <= timestep_clock.getElapsedTime() && !getting_input){
            timestep_clock.restart();
            updateGrid(grid)
        }

        window.clear();
        drawGrid(window, grid);
        window.display();
    }

    return 0;
}



« Last Edit: November 11, 2023, 11:29:21 pm by abcd1234567 »

G.

  • Hero Member
  • *****
  • Posts: 1592
    • View Profile
Re: Dragging a grid, *without* clicking on it
« Reply #1 on: November 11, 2023, 11:04:47 pm »
I'd register mouse position on MouseButtonPressed and set a bool "dragging" to true, check mouse position in MouseButtonReleased and set the "dragging" bool to false then if the 2 mouse positions are the same I activate the square.
On MouseMoved if dragging is true then I move the view. (I don't remember if the delta is inside the event data or if you have to compute it yourself)

That's pretty much how it works in your link. (if you manage to start and end you drag on the same position it activates the square)

abcd1234567

  • Newbie
  • *
  • Posts: 23
    • View Profile
Re: Dragging a grid, *without* clicking on it
« Reply #2 on: November 11, 2023, 11:24:37 pm »
I'd register mouse position on MouseButtonPressed and set a bool "dragging" to true, check mouse position in MouseButtonReleased and set the "dragging" bool to false then if the 2 mouse positions are the same I activate the square.
On MouseMoved if dragging is true then I move the view. (I don't remember if the delta is inside the event data or if you have to compute it yourself)

That's pretty much how it works in your link. (if you manage to start and end you drag on the same position it activates the square)

I thought about a similar thing, and updated my code as well:
#include <iostream>
#include <SFML/Graphics.hpp>
#include <unordered_set>
#include <cmath>
#include "game_logic.h"
#include "patterns.h"

inline float distance(sf::Vector2i vec1, sf::Vector2i vec2){
    return sqrt(pow(vec2.x - vec1.x, 2) + pow(vec2.y - vec1.y, 2));
}

int main(){
    // We implement the grid using a sparse matrix - which is just a set that stores only the *live cells*.
    std::unordered_set<sf::Vector2i, pair_hash, pair_equal> grid;

    sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Game of life", sf::Style::Default);
    bool focus = true; // True iff focus is on window.
    bool getting_input = false, dragging = false;
    sf::View view(sf::FloatRect(WINDOW_WIDTH * (MULTIPLE / 2), WINDOW_HEIGHT * (MULTIPLE / 2), WINDOW_WIDTH, WINDOW_HEIGHT));
    // Since our initial view is different from the default view, we have to set it before we continue.
    window.setView(view);

    getting_input = true;

    sf::Clock timestep_clock;
    sf::Vector2i old_view_pos = sf::Mouse::getPosition(window), new_view_pos, drag_start;
    while (window.isOpen()){
        sf::Event evnt;
        while (window.pollEvent(evnt)){
            switch(evnt.type){
                // The program exits when user closes the window by pressing &#39;X&#39;.
                case sf::Event::Closed:
                    window.close();
                    return 0;
                    break;

                    if (evnt.key.code == sf::Keyboard::Enter){
                        if (getting_input){ // Submitting input (finished getting input)
                            getting_input = false;
                        }
                    }
                    break;

                // Because &#39;isKeyPressed&#39; is "connected" to the actual device, it&#39;s getting input even when window is out of focus.
                // We want to accept keyboard input only when the window has focus, so we have to keep track of it using a boolean.
                case sf::Event::GainedFocus:
                    focus = true;
                    break;
                case sf::Event::LostFocus:
                    focus = false;
                    break;

                // Getting input from mouse.
                case sf::Event::MouseButtonReleased:{
                    sf::Vector2i pixel_pos = sf::Mouse::getPosition(window);
                    sf::Vector2i view_pos = static_cast<sf::Vector2i>((window.mapPixelToCoords(pixel_pos)));
                    if (getting_input && evnt.mouseButton.button == sf::Mouse::Left && distance(drag_start, pixel_pos) <= 2 * CELL_SIZE){
                        handleLeftClick(window, grid, sf::Vector2i(view_pos.x, view_pos.y));
                    }
                    dragging = false;
                    break;
                }
            }
        }

        new_view_pos = sf::Mouse::getPosition(window);
        if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)){
            if (!dragging){
                dragging = true;
                drag_start = new_view_pos;
            }

            sf::Vector2i deltaPos = old_view_pos - new_view_pos;
            view.move(deltaPos.x, deltaPos.y);
            window.setView(view);
        }
        old_view_pos = sf::Mouse::getPosition(window);

        if (TIMESTEP <= timestep_clock.getElapsedTime() && !getting_input){
            timestep_clock.restart();

            updateGrid(grid)
        }

        window.clear();
        drawGrid(window, grid);
        window.display();
    }

    return 0;
}

But it's still not fluid. Mainly, if you click, release and move the mouse, it doesn't register the click sometimes.
I have a gif to show it: https://gifyu.com/image/SR2k8
Every time I move the mouse I also click it, and note how it sometimes doesn't register.

You mentioned using 'mouseMoved' event. I intentioanlly avoided it, and using events at all for this, because I know that dragging is a continous motion, and for that we prefer using functions that are "connected" to the device itself (like isKeyPressed). Basically, to mimic mouseMoved I simply save old_view_pos and new_view_pos.

Do you think I should switch to events?

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10821
    • View Profile
    • development blog
    • Email
Re: Dragging a grid, *without* clicking on it
« Reply #3 on: November 14, 2023, 12:30:33 pm »
You can just track mouseButtonPressed and mouseButtonReleased events, so anything in between doesn't need a "real-time" connection.

To fix your issue, you might simply want to extend the check and allow for a release of the mouse within like 5x5px or something like that. Clicking and not moving the mouse at all isn't actually that easy. You're most of the time moving it a few pixels here and there.
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Dragging a grid, *without* clicking on it
« Reply #4 on: November 15, 2023, 06:09:10 pm »
I'd like to also mention that it may be better for usability to assign a separate mouse button for dragging the view. This is common in a lot of apps in various versions but almost always use the middle mouse button (Blender, GIMP etc.), if available. If not, some "flag" key that would control whether the dragging state is active or not (Photoshop, older Blender default etc.)

Definitely agree with eXpl0it3r with the range as well. I was going to mention that too before I'd read their post. I might use "distance moved" but it's an identical same concept with eXpl0it3r's being the easiest to implement probably without any real noticeable differences.

Tracking mouse-moved events and updating positions from those is useful for visual feedback but if that's not necessary (i.e. you don't need to see where it currently is, just that it is moving until it's been released), then ignore the mouse moved events for this (other that for checking that it's moved outside of its drag range as mentioned previously). However, if you do that visual feedback, I'd suggest using events because they will match with the pressed and released events.
> Technically, it's possible that you could release and that event is sent but not immediately received by the program. Then, you may update the mouse movement from realtime, which is now not where you wanted to drag to, followed by finally receiving the event and it thinks you actually moved it before releasing the button.
Using events for all of the parts in the same section like this allows you to avoid these (rare, but possible) issues as all of the events should be received in the order that they happened, even if it is not immediate.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*