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.
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();
}
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With