Building a Calculator App¶
Difficulty | Beginner | Time | 30 minutes~ |
1. Set the background color¶
- Use
Scene::SetBackground(color);
to set the background color.
Code
2. Prepare button information¶
- Create a
Button
class that holds aRect
representing the button area and aString
representing the text on the button. - Set the information for the "1" button as the initial value of the
Button
class variablebutton
.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
while (System::Update())
{
}
}
3. Draw the button background¶
- Access the
.rect
member variable of theButton
class and draw the button background.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
while (System::Update())
{
// Draw the button background
button.rect.draw();
}
}
4. Round the button corners¶
- Use the
Rect
member function.rounded(corner_radius)
to round the button corners.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
while (System::Update())
{
// Draw the button background
button.rect.rounded(8).draw();
}
}
5. Display text on the button¶
- Prepare a
Font
for displaying text. - Access the
.text
member variable of theButton
and draw the button text.
New Features
font(text).drawAt(font_size, position, color);
draws text centered at the specified position.- The
.center()
member function ofRect
returns the coordinates of the rectangle's center.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
while (System::Update())
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
}
}
6. Change the mouse cursor to a hand over buttons¶
- Use the
Rect
member function.mouseOver()
to check if the mouse cursor is over the button. - Use
Cursor::RequestStyle(CursorStyle::Hand);
to change the current mouse cursor style to a hand.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
while (System::Update())
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
}
}
7. Input formulas with buttons¶
- Prepare a
String
type variableexpression
to manage the input formula for the calculator. - When the button is clicked, add the button's text "U\"1\"" to
expression
. - Use
Print
to display the contents of the input formula briefly.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Button information
Button button{ Rect{ 100, 200, 90, 90 }, U"1" };
// Input expression
String expression;
while (System::Update())
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
// When the button is clicked
if (button.rect.leftClicked())
{
// Add the button text to the expression
expression += button.text;
}
ClearPrint();
Print << expression; // Display the expression briefly
}
}
8. Add more buttons¶
- Use the
Array<Button>
array to manage more buttons. - Write the processing for each button inside
for (const auto& button : buttons) { }
. - When a button is clicked, update the input expression
expression
according to that button's text.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Array of button information
Array<Button> buttons;
buttons << Button{ Rect{ 100, 200, 90, 90 }, U"1" };
buttons << Button{ Rect{ 200, 200, 90, 90 }, U"2" };
buttons << Button{ Rect{ 300, 200, 90, 90 }, U"3" };
buttons << Button{ Rect{ 400, 200, 90, 90 }, U"+" };
buttons << Button{ Rect{ 400, 300, 90, 90 }, U"=" };
buttons << Button{ Rect{ 400, 400, 90, 90 }, U"C" };
// Input expression
String expression;
while (System::Update())
{
// For each button
for (const auto& button : buttons)
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
// When the button is clicked
if (button.rect.leftClicked())
{
if (button.text == U"=")
{
// ToDo
}
else if (button.text == U"+")
{
expression += button.text;
}
else if (button.text == U"C")
{
// Clear the expression
expression.clear();
}
else
{
// Add the button text to the expression
expression += button.text;
}
}
}
ClearPrint();
Print << expression; // Display the expression briefly
}
}
9. Add an expression display area¶
- Add an area to the calculator for displaying expressions.
- Display the expression right-aligned, shifted 10 pixels to the left from the display area.
New Features
font(text).draw(font_size, Arg::rightCenter = position, color);
draws text right-aligned with the specified position as the center of the right edge of the text.- The
.rightCenter()
member function ofRect
returns the coordinates of the center of the rectangle's right edge.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Result display area color
const ColorF displayColor{ 0.8, 0.9, 0.8 };
// Result display area text color
const ColorF displayTextColor{ 0.1 };
// Result display area rectangle
const Rect displayRect{ 100, 100, 390, 90 };
// Array of button information
Array<Button> buttons;
buttons << Button{ Rect{ 100, 200, 90, 90 }, U"1" };
buttons << Button{ Rect{ 200, 200, 90, 90 }, U"2" };
buttons << Button{ Rect{ 300, 200, 90, 90 }, U"3" };
buttons << Button{ Rect{ 400, 200, 90, 90 }, U"+" };
buttons << Button{ Rect{ 400, 300, 90, 90 }, U"=" };
buttons << Button{ Rect{ 400, 400, 90, 90 }, U"C" };
// Input expression
String expression;
while (System::Update())
{
// For each button
for (const auto& button : buttons)
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
// When the button is clicked
if (button.rect.leftClicked())
{
if (button.text == U"=")
{
// ToDo
}
else if (button.text == U"+")
{
expression += button.text;
}
else if (button.text == U"C")
{
// Clear the expression
expression.clear();
}
else
{
// Add the button text to the expression
expression += button.text;
}
}
}
// Draw the result display area
displayRect.draw(displayColor);
// Draw the expression in the result display area
font(expression).draw(36, Arg::rightCenter = (displayRect.rightCenter() + Vec2{ -10, 0 }), displayTextColor);
}
}
10. Add calculation processing¶
- Implement the
PushEqual
function to calculate the input expression when the "=" button is pressed and make the result the new input expression. - Implement the
PushPlus
function to add "+" to the input expression when the "+" button is pressed.
New Features
- The
!
(not) operator inverts abool
type value. - The
.isEmpty()
member function of aString
type variable checks if the string is empty. - The
.back()
member function of aString
type variable returns the last character of the string. It causes an error if empty. - The
IsDigit(char32 ch)
function checks if the argument characterch
is a digit. - The
Eval(String expression)
function evaluates the expressionexpression
and returns the result as adouble
type. ReturnsNaN
if the expression is invalid.- You can check if it's
NaN
with theIsNaN(double x)
function.
- You can check if it's
- The
Format(double x)
function converts thedouble
type valuex
to aString
and returns it.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
};
/// @brief Returns whether the expression ends with a digit.
/// @param expression The expression
/// @return true if it ends with a digit, false otherwise
bool EndsWithDigit(String expression)
{
if (expression.isEmpty())
{
return false;
}
return IsDigit(expression.back());
}
/// @brief Processes when the = button is pressed.
/// @param expression The current expression
/// @return The new expression
String PushEqual(String expression)
{
// Do nothing if it doesn't end with a digit
if (!EndsWithDigit(expression))
{
return expression;
}
// Evaluate the expression with the expression parser
double result = Eval(expression);
// Convert the evaluation result to a string and return it
return Format(result);
}
/// @brief Processes when the + button is pressed.
/// @param expression The current expression
/// @return The new expression
String PushPlus(String expression)
{
// Do nothing if it doesn't end with a digit
if (!EndsWithDigit(expression))
{
return expression;
}
// Add + to the end of the expression and return it
return (expression + U"+");
}
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Result display area color
const ColorF displayColor{ 0.8, 0.9, 0.8 };
// Result display area text color
const ColorF displayTextColor{ 0.1 };
// Result display area rectangle
const Rect displayRect{ 100, 100, 390, 90 };
// Array of button information
Array<Button> buttons;
buttons << Button{ Rect{ 100, 200, 90, 90 }, U"1" };
buttons << Button{ Rect{ 200, 200, 90, 90 }, U"2" };
buttons << Button{ Rect{ 300, 200, 90, 90 }, U"3" };
buttons << Button{ Rect{ 400, 200, 90, 90 }, U"+" };
buttons << Button{ Rect{ 400, 300, 90, 90 }, U"=" };
buttons << Button{ Rect{ 400, 400, 90, 90 }, U"C" };
// Input expression
String expression;
while (System::Update())
{
// For each button
for (const auto& button : buttons)
{
// Draw the button background
button.rect.rounded(8).draw();
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), ColorF{ 0.1 });
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
// When the button is clicked
if (button.rect.leftClicked())
{
if (button.text == U"=")
{
expression = PushEqual(expression);
}
else if (button.text == U"+")
{
expression = PushPlus(expression);
}
else if (button.text == U"C")
{
// Clear the expression
expression.clear();
}
else
{
// Add the button text to the expression
expression += button.text;
}
}
}
// Draw the result display area
displayRect.draw(displayColor);
// Draw the expression in the result display area
font(expression).draw(36, Arg::rightCenter = (displayRect.rightCenter() + Vec2{ -10, 0 }), displayTextColor);
}
}
11. Customize button styles¶
- Add
ColorF
type member variables to theButton
class to represent the button's background color and text color.
Code
# include <Siv3D.hpp>
/// @brief Button class
struct Button
{
/// @brief Button rectangle
Rect rect;
/// @brief Button text
String text;
/// @brief Button color
ColorF backgroundColor;
/// @brief Text color
ColorF textColor;
};
/// @brief Returns whether the expression ends with a digit.
/// @param expression The expression
/// @return true if it ends with a digit, false otherwise
bool EndsWithDigit(String expression)
{
if (expression.isEmpty())
{
return false;
}
return IsDigit(expression.back());
}
/// @brief Processes when the = button is pressed.
/// @param expression The current expression
/// @return The new expression
String PushEqual(String expression)
{
// Do nothing if it doesn't end with a digit
if (!EndsWithDigit(expression))
{
return expression;
}
// Evaluate the expression with the expression parser
double result = Eval(expression);
// Convert the evaluation result to a string and return it
return Format(result);
}
/// @brief Processes when the + button is pressed.
/// @param expression The current expression
/// @return The new expression
String PushPlus(String expression)
{
// Do nothing if it doesn't end with a digit
if (!EndsWithDigit(expression))
{
return expression;
}
// Add + to the end of the expression and return it
return (expression + U"+");
}
void Main()
{
// Set the background color
Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
// Font for buttons
const Font font{ FontMethod::MSDF, 40, Typeface::Bold };
// Number button color
const ColorF numberButtonColor{ 0.9, 0.95, 1.0 };
// Clear button color
const ColorF clearButtonColor{ 1.0, 0.8, 0.8 };
// Button text color
const ColorF buttonTextColor{ 0.1 };
// Result display area color
const ColorF displayColor{ 0.8, 0.9, 0.8 };
// Result display area text color
const ColorF displayTextColor{ 0.1 };
// Result display area rectangle
const Rect displayRect{ 100, 100, 390, 90 };
// Array of button information
Array<Button> buttons;
buttons << Button{ Rect{ 100, 200, 90, 90 }, U"1", numberButtonColor, buttonTextColor };
buttons << Button{ Rect{ 200, 200, 90, 90 }, U"2", numberButtonColor, buttonTextColor };
buttons << Button{ Rect{ 300, 200, 90, 90 }, U"3", numberButtonColor, buttonTextColor };
buttons << Button{ Rect{ 400, 200, 90, 90 }, U"+", numberButtonColor, buttonTextColor };
buttons << Button{ Rect{ 400, 300, 90, 90 }, U"=", numberButtonColor, buttonTextColor };
buttons << Button{ Rect{ 400, 400, 90, 90 }, U"C", clearButtonColor, buttonTextColor };
// Input expression
String expression;
while (System::Update())
{
// For each button
for (const auto& button : buttons)
{
// Draw the button background
button.rect.rounded(8).draw(button.backgroundColor);
// Draw the button text
font(button.text).drawAt(32, button.rect.center(), button.textColor);
// When the mouse is over the button
if (button.rect.mouseOver())
{
// Change the mouse cursor to a hand
Cursor::RequestStyle(CursorStyle::Hand);
}
// When the button is clicked
if (button.rect.leftClicked())
{
if (button.text == U"=")
{
expression = PushEqual(expression);
}
else if (button.text == U"+")
{
expression = PushPlus(expression);
}
else if (button.text == U"C")
{
// Clear the expression
expression.clear();
}
else
{
// Add the button text to the expression
expression += button.text;
}
}
}
// Draw the result display area
displayRect.draw(displayColor);
// Draw the expression in the result display area
font(expression).draw(36, Arg::rightCenter = (displayRect.rightCenter() + Vec2{ -10, 0 }), displayTextColor);
}
}
Advanced Features¶
From here on, try improving the program by thinking for yourself.
Feature Ideas¶
- Add the remaining numbers
- Add subtraction, multiplication, and division
Eval()
also supports-
,*
, and/
.
- Enable input of parentheses
- Add a backspace (delete one character) button
- The
.pop_back()
member function ofString
deletes the last character of the string. It causes an error if the string is empty.
- The
- Enable input of decimal numbers
- Enable number input with the keyboard
- Enable calculation of square roots
Eval()
also supports expressions likesqrt(25)
.
- Record and reuse previous calculation results
- Copy the expression to the clipboard when clicking the display area
Clipboard::SetText(s);
copies the strings
to the clipboard.
Design Ideas¶
- Arrange the size and layout of buttons
- Change button colors on mouse hover