Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Write text input on the screen in SFML

Tags:

c++

sfml

So I'm creating a graphing calculator. I have an input string s. From the string, I can graph it using SFML. I start from the a MIN x-coordinate to a MAX x-coordinate, get the corresponding y from a EvaluateString() method, and all the coordinates to a VertexArray v. I wrote my method and the graphing method already and it all worked well.

However, I have a small issue. I want to input my string on the screen, such as "sin(cos(tan(x)))" like this. I'm struggling to find a way to do it. I kinda figured out it has to do with the event TextEntered, but still I can't find anything completely.

Please suggest me a way.

Sample graph

class Calculator{
public:
    void main();
private:
    WindowSize DefaultWindow;
    sf::RenderWindow window;
    Cartesian vertexX[2],vertexY[2];
    sf::Vertex axis[4];
    const double MAX = 10;
    const double MIN = -10;
    const double INCREMENT = 0.001;

};

int main(){ 
    DefaultWindow.Max = Cartesian(10,10);
    DefaultWindow.Min = Cartesian(-10,-10);
    DefaultWindow.plane.width=1500;
    DefaultWindow.plane.height=1500;

    // Set up x and y-axis
    vertexX[0] = Cartesian(-100,0);
    vertexX[1] = Cartesian(100, 0);
    vertexY[0] = Cartesian(0,-100);
    vertexY[1] = Cartesian(0,100);

    axis[0] = sf::Vertex(convertCartesiantoWindow(vertexX[0],DefaultWindow));
    axis[1] = sf::Vertex(convertCartesiantoWindow(vertexX[1],DefaultWindow));
    axis[2] = sf::Vertex(convertCartesiantoWindow(vertexY[0],DefaultWindow));
    axis[3] = sf::Vertex(convertCartesiantoWindow(vertexY[1],DefaultWindow));

    // Set up the window
    window.create(sf::VideoMode(1500, 1500), "Graphing calculator");

    // Input string
    string s = "sin(cos(tan(x)))";

    // Stack c contains all the Cartesian coordinate vertices
    // Cartesian is a struct which contains x and y coordinates
    Stack<Cartesian> c;

    sf::VertexArray v;

    // For a certain function in string s, I evaluate it 
    // and return the y_coordinate from the function EvaluateString (s, i)
    // Push each (x,y) evaluated in the Stack c
    for (double i = MIN; i <= MAX; i+= INCREMENT)
        c.Push(Cartesian(i,EvaluateString(s,i)));

    // v is VertexArray which contains all the vertices (x,y)
    v = plot(DefaultWindow, c);


    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {

            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
            }
        }
    }

    // Draw the graph
    window.clear(sf::Color::Black);
    window.draw(axis,4,sf::Lines);
    window.draw(v);
    window.display();
}
like image 803
Brandon Williams Avatar asked Dec 23 '22 02:12

Brandon Williams


1 Answers

As @super suggest, use a library would be a nice solution, and surely better than mine, but just in case this satisfies your needs, I implemented a super basic TextField class.

It may be plenty of errors, but it can gives you an idea on how to achieve that functionality.

A TextField is nothing more than a rectangle which contains a text. Since it will have a sf::Text, it must have a sf::Font. Additionally, I limit the number of characters that it will contain. In order for us to write inside the TextField, we have to know if it's selected, i.e. if it has the focus. So, a first approach could be:

class TextField : public sf::Transformable, public sf::Drawable{
    private:
        unsigned int m_size;
        sf::Font m_font;
        std::string m_text;
        sf::RectangleShape m_rect;
        bool m_hasfocus;
};

We need a constructor for this class:

class TextField : public sf::Transformable, public sf::Drawable{
    public:
        TextField(unsigned int maxChars) :
            m_size(maxChars),
            m_rect(sf::Vector2f(15 * m_size, 20)), // 15 pixels per char, 20 pixels height, you can tweak
            m_hasfocus(false)
        {
            m_font.loadFromFile("C:/Windows/Fonts/Arial.ttf"); // I'm working on Windows, you can put your own font instead
            m_rect.setOutlineThickness(2);
            m_rect.setFillColor(sf::Color::White);
            m_rect.setOutlineColor(sf::Color(127,127,127));
            m_rect.setPosition(this->getPosition());
        }

    private:
        unsigned int m_size;
        sf::Font m_font;
        std::string m_text;
        sf::RectangleShape m_rect;
        bool m_hasfocus;
};

We also need some basic methods, we want to get the text inside:

const std::string sf::TextField::getText() const{
    return m_text;
}

and move it, placing it somewhere inside our window:

void sf::TextField::setPosition(float x, float y){
    sf::Transformable::setPosition(x, y);
    m_rect.setPosition(x, y);
}

this is a tricky one. We are overwritting setPosition method of sf::Transformable because we need to update our own m_rect.

Also, we need to know if a point is inside of the box:

bool sf::TextField::contains(sf::Vector2f point) const{
    return m_rect.getGlobalBounds().contains(point);
}

pretty simple, we use cointains method of sf::RectangleShape, already in sfml.

Set (or unset) focus on the TextField:

void sf::TextField::setFocus(bool focus){
    m_hasfocus = focus;
    if (focus){
        m_rect.setOutlineColor(sf::Color::Blue);
    }
    else{
        m_rect.setOutlineColor(sf::Color(127, 127, 127)); // Gray color
    }
}

easy one. For aesthetics, we also change the outline color of the box when focused.

And last, but not least, our TextField has to behave some way when input (aka an sf::Event) is received:

void sf::TextField::handleInput(sf::Event e){
    if (!m_hasfocus || e.type != sf::Event::TextEntered)
        return;

    if (e.text.unicode == 8){   // Delete key
        m_text = m_text.substr(0, m_text.size() - 1);
    }
    else if (m_text.size() < m_size){
        m_text += e.text.unicode;
    }
}

That delete key check is little dirty, I know. Maybe you can find better solution.

That's all! Now main looks like:

int main()
{
    RenderWindow window({ 500, 500 }, "SFML", Style::Close);

    sf::TextField tf(20);
    tf.setPosition(30, 30);

    while (window.isOpen())
    {
        for (Event event; window.pollEvent(event);)
            if (event.type == Event::Closed)
                window.close();
            else if (event.type == Event::MouseButtonReleased){
                auto pos = sf::Mouse::getPosition(window);
                tf.setFocus(false);
                if (tf.contains(sf::Vector2f(pos))){
                    tf.setFocus(true);
                }
            }
            else{
                tf.handleInput(event);
            }

            window.clear();
            window.draw(tf);
            window.display();
    }
    return 0;
}

Proof of concept:

enter image description here

like image 172
alseether Avatar answered Dec 25 '22 15:12

alseether