How to color text in c++ when I use arrow keys in my keyboard moving to that text

92 Views Asked by At

I'm coding a small project about finding a day of the week from the given date. I have created a menu which is has 3 options (Press the number to choose): 1. Feature 1, 2. Feature 2, 0. Quit the program. The menu will look like this:

enter image description here

#include <iomanip>
#include <iostream>
#include <string.h>
#include <Windows.h>
#include <conio.h> //dung de sdung getch(), text color
#include <bits/stdc++.h>
#include <vector>
#include <fstream> //doc file


using namespace std;

vector<pair<int, string> > days{{0,"SATURDAY"}, {1, "SUNDAY"}, {2,"MONDAY"}, {3,"TUESDAY"},
                            {4,"WEDNESDAY"}, {5,"THURSDAY"}, {6,"FRIDAY"}};


bool validDate(int d, int m, int y, char c) //Check input 
{
    if(c == '/')
    {
    if(y >= 1 && y <= 2999)
    {
        if(m >= 1 && m <= 12)
        {
           if(d >= 1 && d <= 31)
            {
                if( (d >= 1 && d <= 30) && (m == 4|| m == 6|| m == 9|| m == 11))
                    return true;
                else if((d >= 1 && d <= 30) && (m == 1 || m == 3 || m == 5 || m == 7 || m == 8|| m == 10 ||m == 12))
                    return true;
                else if((d >= 1 && d <= 28) && (m == 2))
                    return true;
                else if(d == 29 && m == 2 && ((y % 400 == 0)||(y % 4 == 0 && y % 100 != 0))) //leapyear
                    return true;
                else
                    return false;
            }
            else return false;
        }
        else return false;
    }
    else return false;
    }
    else return false;
}


/*
The following code is Zeller congruence method to calculate the day of given date
*/
int ZellerCongruence(int givenday, int givenmonth, int givenyear)
{
    if(givenmonth == 1) //In Zeller congruence, Jan & Feb is 13th and 14th month in the previous year
    {
        givenmonth = 13;
        givenyear--;
    }
    if(givenmonth == 2)
    {
        givenmonth = 14;
        givenyear--;
    }
    int q = givenday;
    int m = givenmonth;
    int k = givenyear % 100;
    int j = givenyear / 100;
    int h = q + (13 * (m + 1) / 5) + k + (k / 4) + (j / 4) + (5 * j);
    h = h % 7;
    return h;
}


void SubMenu1()
{
    cout << endl << "FIND THE DAY OF THE WEEK FROM THE GIVEN DATE" << endl;
    cout << endl <<  "Notice: Program can only calculate the year from 1 to 2999" << endl;
    cout << endl << "Enter date in dd/mm/yyyy format and press enter: ";
    int givenday, givenmonth, givenyear;
    char c;
    cin >> givenday;
    cin >> c;
    cin >> givenmonth;
    cin >> c;
    cin >> givenyear;
    if(validDate(givenday, givenmonth, givenyear, c) == false)
    {
        cout << "Invalid Date \n";
        Sleep(200);
        return;
    }
    int h = ZellerCongruence(givenday, givenmonth, givenyear);
    cout << endl << "The given day of date is: " << days[h].second << endl;
}


void SubMenu2()
{
    cout << "FIND THE DAY OF THE WEEK AFTER N DAYS FROM THE GIVEN DATE" << endl;
    cout << endl <<  "Notice: Program can only calculate the year from 1 to 2999" << endl;
    cout << endl << "Enter date in dd/mm/yyyy format and press enter: ";
    int givenday, givenmonth, givenyear;
    char c;
    cin >> givenday;
    cin >> c;
    cin >> givenmonth;
    cin >> c;
    cin >> givenyear;
    if(validDate(givenday, givenmonth, givenyear, c) == false)
    {
        cout << "Invalid Date \n";
        Sleep(200);
        return;
    }
    int h = ZellerCongruence(givenday, givenmonth, givenyear);
    int n;
    cout << "Enter the value of n: ";
    cin >> n;
    if(n <= 0)
    {
        cout << "Invalid value, n must be a positive interger." << endl;
        Sleep(200);
        return;
    }
    h = h + n;
    if(h < 7)
    {
        cout << "The day of the week after " << n << " days from the given date is: " << days[h].second << endl;
    }
    else
    {
        h = h % 7;
        cout << "The day of the week after " << n << " days from the given date is: " << days[h].second << endl;
    }
}


void Menu()
{
    cout << endl << "MAIN MENU" << endl;
    cout << "1. Find the day of the week from the given date" << endl;
    cout << "2. Find the day of the week after n days from the given date" << endl;
    cout << "0. Quit the program" << endl;
}

int main()
{
    int choice;

    start:
    system("CLS");
    Menu();
    cout<<"Enter Your Choice: ";

    s:
    char c = getch(); // getch() method pauses the Output Console until a key is pressed
    if(c >= '0' && c <= '2')
    {
        choice = c - 48; // translate the char values to the int values (cause '0' == 48 in ASCII). We can also write: choice = c - '0'
        cout << c;
        goto s2;
    }
    else
    {
        choice = getch();
    }
    goto s;

    s2:
    Sleep(200);
    system("CLS");
    switch(choice)
        {
            case 0:
                cout << "See you again !" << endl;
                return 0;
                break;
            case 1:
                SubMenu1();
                system("pause");
                system("CLS");
                goto start;
                break;
            case 2:
                SubMenu2();
                system("pause");
                system("CLS");
                goto start;
                break;
            default:
                cout << "Invalid Number..."<<endl;
                system("pause");
                system("CLS");
                goto start;
                break;
        }
        return 0;
}

I want to ask if we can improve the selection style from pressing the number to using the arrow keys. Moreover, I want every time the arrow keys move, the text line at that change the color. Can you help me?

1

There are 1 best solutions below

0
Joel On

As the comments already say, changing the terminal output color is platform dependent and can vary with each terminal you use. Since you use Windows I think my solution should work on your machine as well.

Set terminal textcolor

You can change the terminal text color in windows in multiple ways with e.g. system("color 0A") being one of the most common ways. However this is not recommended and you have only a small set of possible colors (16 for background and foreground each).

This is why I prefer using an escape sequence (see this article) to change the text color to some RGB value like the following function would do:

void setColor(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
    // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

    // print an escape sequence which lets us control the text color in the windows terminal with RGB values
    std::string output{ "\x1b[38;2;" };
    output += std::to_string(r) + ";";
    output += std::to_string(g) + ";";
    output += std::to_string(b) + "m";

    std::cout << output;
}

Get arrow key

To register if some arrow keys are pressed or not you could use e.g. GetKeyState() from the Windows API, but I preferred using some normal text input function for this. Since you already use the non standard <conio.h> header, I did too because I think it is quite easy to implement it this way. From here I got the values for both the arrowKeyIndicator and the values for the keys which get returned from _getch() (getch() is deprecated) when you press an arrow key.

My function waits for user input (is blocking) and returns the arrow key which the user pressed. When the user presses the ENTER key (or '\r' to be exact) my function returns an invalid key by design but you could change that to fit your needs.

enum Key : unsigned char {
    None = 0, ArrowUp = 72, ArrowDown = 80, ArrowLeft = 75, ArrowRight = 77
};

Key getArrowKey() {
    // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797

    constexpr unsigned char arrowKeyIndicator{ 224 };

    unsigned char ch{};
    // loop until the user presses either an arrow key (which will get us two chars)
    // or the user presses enter
    while ((ch = static_cast<char>(_getch())) != arrowKeyIndicator) {
        // return when the user presses enter (so there is a way to exit the loop without having to click an arrow key)
        if (ch == '\r') {
            return Key::None;
        }
    }

    // the first char was some character indicating arrow keys which is the same for all of them
    // now we get the second char
    ch = static_cast<char>(_getch());
    // and we return the corresponding arrow key
    for (const Key k : { Key::ArrowUp, Key::ArrowDown, Key::ArrowLeft, Key::ArrowRight }) {
        if (ch == k) {
            return k;
        }
    }

    // if it was not a arrow key, return `Key::None` (or could be another value to mark failure to get an arrow key)
    return Key::None;
}

Example usage

If you want to test those two functions you could do so with the following snippet. Note the '\r' at the end of the print statement (which you could of course move into the string literal), which lets you write to the same line, opposed to '\n' which would go to the next line. Therefore you can change the color and just print the text again. However note that rewriting a shorter output than before will leave some characters from the old line still visible, so you either need to use a different approach or just make sure not to print a smaller line when using '\r'.

int main() {
    Key k{};

    do {
        std::cout << "When you press an arrow key, the color changes. If you press ENTER, the program terminates." << '\r';
        k = getArrowKey();

        switch (k)
        {
        case Key::ArrowUp:
            setColor(0xFF, 0x00, 0x00);
            break;
        case Key::ArrowDown:
            setColor(0x00, 0xFF, 0x00);
            break;
        case Key::ArrowLeft:
            setColor(0x00, 0x00, 0xFF);
            break;
        case Key::ArrowRight:
            setColor(0xFF, 0xA5, 0x00);
            break;
        default:
            break;
        }

    } while (k != Key::None);

    return 0;
}