在SFML中在屏幕上编写文本输入

4
我正在创建一个绘图计算器。 我有一个输入字符串s。 从该字符串,我可以使用SFML进行绘图。 我从MIN x坐标到MAX x坐标开始,使用EvaluateString()方法获取相应的y,并将所有坐标添加到VertexArray v中。 我已经编写了自己的方法和绘图方法,并且它们都很好用。
但是,我有一个小问题。 我想在屏幕上输入我的字符串,如“sin(cos(tan(x)))”之类的内容。 我还在苦苦寻找方法。 我想到与事件TextEntered有关,但仍然找不到任何完全的解决方法。
请为我提供建议。

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();
}

@fang:是的,只是关于绘制文本。我想在屏幕上输入文本 :( - Brandon Williams
1
ImGui可以在SFML中使用(https://github.com/eliasdaler/imgui-sfml)。虽然这可能有点过头了,但如果您的小项目需要更多GUI元素,那么看一下它可能非常值得。 - super
@super:你能告诉我应该看哪一部分吗?我刚开始接触SFML,所以阅读关于另一个库的内容会让我有些不知所措 :) - Brandon Williams
@super:非常感谢。我也听说过TGUI,它们能用来做同样的事情吗? - Brandon Williams
@super:所以我将ImGUI + SFML与ocornut的ImGUI文件结合在一起,但是ImGUI + SFML出现了一些错误。我仍在努力寻找一种方法,在SFML中放置一个简单的文本输入字段。如果您有任何想法,我将非常感激。 - Brandon Williams
显示剩余4条评论
2个回答

12

@super suggest,使用一个库会是一个不错的解决方案,肯定比我的好,但为了保险起见,我实现了一个超级基础的TextField类。

可能会有很多错误,但它可以给你一个实现该功能的思路。

TextField只是包含文本矩形。由于它将拥有一个sf::Text,因此必须具有sf::Font。此外,我限制它所包含的字符数。为了在TextField中写入文本,我们必须知道它是否被选中,即它是否具有焦点。因此,第一种方法可能是:

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;
};

我们需要为这个类创建一个构造函数:
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;
};

我们还需要一些基本的方法,我们想要获取其中的文本:
const std::string sf::TextField::getText() const{
    return m_text;
}

将其移动并将其放置在窗口内的某个位置:

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

这是一个棘手的问题。我们正在覆盖sf::TransformablesetPositionmethod,因为我们需要更新自己的m_rect
此外,我们需要知道一个点是否在框内:
bool sf::TextField::contains(sf::Vector2f point) const{
    return m_rect.getGlobalBounds().contains(point);
}

很简单,我们使用已经在中的sf::RectangleShapecontains方法。

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
    }
}

易懂的内容。为了美观,当焦点在框上时,我们还会改变框的轮廓颜色。
最后,但同样重要的是,我们的TextField在接收到输入(即sf :: Event)时必须以某种方式进行操作:
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;
    }
}

那个删除键的检查有点不太好,我知道。也许你可以找到更好的解决方案。
现在,main 看起来像这样:
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;
}

概念证明:

enter image description here


1
在 Meta 上关注了你的帖子后,随机代码审查评论:通过反转条件并尽早返回来减少 void sf::TextField::handleInput(sf::Event e){} 中的嵌套:if (!m_hasfocus || e.type != sf::Event::TextEntered) return;然后将以下 else 合并为 else ifelse if (m_text.size() < m_size)最后,您可以删除花括号,使整个函数更清洁、易读。如果您需要一个指标,认知复杂度得分会有所提高。 - AndyG
2
我没有看到你的文本输入类中的 draw() 方法,但我仍然不明白为什么你要同时实现 setPosition() 并继承 sf::Transformable。既然你已经继承了它,就让它处理位置,只需在 draw() 方法中使用 states.transform *= this->getTransform(); target.draw(m_rect, states); 即可。 - renardesque

-2
std::string str;
sf::String text;

// In event loop...
if (event.Type == sf::Event::TextEntered)
{
    // Handle ASCII characters only
    if (event.Text.Unicode < 128)
    {
        str += static_cast<char>(event.Text.Unicode);
        text.SetText(str);
    }
}

// In main loop...
window.Draw(text);

这将创建一个用于输入的sf::Event::TextEntered,以及用于输出的sf::String


你能帮我理解这个吗?我如何在其中输入到特定的教科书中? - Brandon Williams
2
sf::String 可以像这样 drawable 吗?我认为你可能需要 sf::Text - Galik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接