Skip to content

Instantly share code, notes, and snippets.

@etzl
Last active March 8, 2021 12:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etzl/17612eb71654a7198e7a4be6c7d73f50 to your computer and use it in GitHub Desktop.
Save etzl/17612eb71654a7198e7a4be6c7d73f50 to your computer and use it in GitHub Desktop.
A cool looking window with extra features (using ncurses)
#include <curses.h>
#include <panel.h>
#include <menu.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char *itemsstr[][2] = {
{"Resume", "Continue where you left"},
{"New Game", NULL},
{"Settings", "Tweak the game"},
{"Item1", "A beautiful sword made with 'power', 'dignity' and 'beauty' in mind. enjoy the most valuable sword in the game"},
{"Item2", "Yet another item"},
{"Item3", "Change your item size"},
{"Item4", NULL},
{"Item5", NULL},
{"Item6", NULL},
{"Item7", NULL},
{"Item8", NULL},
{"Item9", NULL},
{"Exit", "I mean, it's exit. What do you want me to say?"},
NULL
};
#define SETTINGS_TXT "First things first, you can **always** use <q> to *quit*, \b<Up Arrow>\b to go *up* in the text, and \b<Down Arrow>\b to go *down*. Also <k> and <j> respectively. So by now you should have seen that none of the buttons work in the menu, *except exit* of course. Becasue this is just a demonstration of what this menu is capable of. You might be wondering why the name is \"**settings**\"? because of the historical reason. If I wanted to change it, I had to change some of the source code. But this was it you can press <q> to quit."
int showdesc(const char*, WINDOW*);
#define SET_RANGE(_arr, _init, _size, _val) for (int _index=_init; _index<_size; _arr[_index++] = _val)
#define SET_ALL(_arr, _size, _val) SET_RANGE(_arr, 0, _size, _val)
/* function associated with settings item in the main menu */
void settingsf(MENU* mainmenu)
{
unpost_menu(mainmenu);
WINDOW *mmwin = menu_win(mainmenu);
wbkgd(mmwin, COLOR_PAIR(2));
showdesc(SETTINGS_TXT, mmwin);
werase(mmwin);
wbkgd(mmwin, 0);
wattron(mmwin, COLOR_PAIR(4));
box(mmwin, 0, 0);
mvwaddstr(mmwin, 0, 2, "Pause Menu!");
post_menu(mainmenu);
wrefresh(mmwin);
}
/* temporary leave curses and show the error message */
void showerr(const char* msg)
{
def_prog_mode();
endwin();
printf("%s\n", msg);
printf("press any key...");
getchar();
reset_prog_mode();
doupdate();
}
#define ATTRIB "*\n\b"
#define SEP " "
/* Count the actual word (without attribute characters) in range: [beg, end) */
int countword(const char* beg, const char* end)
{
int s = 0;
for (const char* p = beg; p != end; ++p) {
if (strspn(p, ATTRIB))
continue;
++s;
}
return s;
}
#define DESCTXT "Description"
/* Call only by showdesc to paint desc win */
void des_descwin(WINDOW* win)
{
int maxx = getmaxx(win);
werase(win);
box(win, 0, 0);
mvwaddstr(win, 0, (maxx-strlen(DESCTXT))/2, DESCTXT);
}
int showdesc(const char* desc, WINDOW* place)
{
keypad(place, TRUE);
des_descwin(place);
int maxx, maxy;
getmaxyx(place, maxy, maxx);
if (!desc)
mvwprintw(place, 1, 1, "None");
else {
const char *beg, *end = desc;
const int maxc = maxx - 2, maxl = maxy - 2;
int m_line = maxl, prev = 0, winattrs = 0;
int n_line = 0, lsize = 0, bufcol = 0;
chtype *buffer = malloc(sizeof(chtype[maxc*m_line]));
if (!buffer)
return 1;
SET_ALL(buffer, m_line*maxc, 32);
bool bflag = false;
while (*end) {
beg = end;
end += strspn(end, SEP); // skip separator
end = strpbrk(end, SEP); // point to one passed the last character
if (!end) // last word
end = strchr(beg, '\0');
int wsize = countword(beg, end);
lsize += wsize;
if (lsize > maxc && wsize < maxc && *(beg+1)!='\n') {
n_line++;
bufcol = 0;
int spn = strspn(beg, SEP);
beg += spn; // do not include separator if we are going in the new line
wsize -= spn;
lsize = wsize;
}
for (; beg!=end; ++beg) {
if (*beg == '<')
winattrs |= COLOR_PAIR(5);
else if (*beg=='*') {
if (winattrs & A_BOLD) {
if (prev == '*')
winattrs ^= A_ITALIC;
winattrs ^= A_BOLD;
}
else
winattrs |= A_BOLD;
prev = *beg;
continue;
}
else if (*beg == '\n') {
n_line++;
bufcol = 0;
if (beg+1 == end) { // occurred at the end of the word
lsize = 0;
end += strspn(end, SEP); // next word is the beginning of the line so remove white space
break;
}
else if (*(beg-1) == ' ') // beginning of the word
lsize = wsize - 1; // we already printed white space in the previous line
else { // middle of two separators
/* first condition removes white-space anyway so there's
* no point in stick two words with \n in betwen
*/
// wsize = countword(beg+1, end);
// lsize = wsize;
free(buffer);
return 1;
}
continue;
}
else if (*beg == '\b') {
if (bflag) { // this is just the second pair we don't need to do anything
bflag = false;
continue;
}
end = strchr(end, '\b'); // what user told is the end of word
end = strpbrk(end, SEP); // what actually is the end of word
// count new word size
lsize -= wsize;
wsize = countword(beg, end);
lsize += wsize;
if (lsize > maxc) {
if (wsize < maxc) {
n_line++;
bufcol = 0;
int spn = strspn(beg, SEP);
beg += spn; // do not include separator when we are going in the new line
wsize -= spn;
lsize = wsize;
}
else {
// error: text width between \b flag shouldn't be larger than a line
free(buffer);
return 2;
}
}
bflag = true;
continue;
}
if (n_line == m_line) {
bufcol = 0;
int tmp = m_line + maxl; // allocate new screen
chtype *newptr = realloc(buffer, tmp*maxc * sizeof(chtype));
if (!newptr) {
free(buffer);
return 1;
}
m_line = tmp;
buffer = newptr;
SET_RANGE(buffer, n_line*maxc, m_line*maxc, 32);
}
if (bufcol == maxc) {
++n_line;
bufcol = 0;
}
buffer[n_line * maxc + bufcol] = *beg | winattrs;
bufcol++;
prev = *beg;
if (*beg=='>')
winattrs ^= COLOR_PAIR(5);
}
}
// print buffer
n_line = 0; // mark beginning position of the buffer
int ch = 0, winline = 0;
bool editor = false;
for (int i = n_line; ; ++i) {
if (i == m_line) {
if (editor)
winline = m_line; // winline will eventually get reset but gives us the chance stay in the editor
else
break;
}
wmove(place, ++winline, 1);
if (winline > maxl) {
get_input:
ch = wgetch(place);
switch (ch) {
case 'k':
case KEY_UP:
if (n_line == 0)
goto get_input;
i = --n_line;
break;
case 'j':
case KEY_DOWN:
/* first character of the line is never a white-space
* if it is we hit the end of buffer */
if (buffer[i*maxc] == 32 || i == m_line)
goto get_input;
i = ++n_line;
break;
case 'q':
goto end;
default:
goto get_input;
}
werase(place);
des_descwin(place);
winline = 1;
wmove(place, winline, 1);
editor = true;
}
for (bufcol = 0; bufcol < maxc; ++bufcol)
waddch(place, buffer[i*maxc + bufcol]);
}
end:
free(buffer);
}
wrefresh(place);
}
void showscroll(MENU* mainmenu, WINDOW* showin)
{
int yinit = 3, x = 2, slength = 5;
char sc[slength];
// calculate relative scroller
int index = item_index(current_item(mainmenu));
int csc = (index * (slength-1))/(item_count(mainmenu)-1);
for (size_t t = 0; t < slength; ++t)
sc[t] = ' ';
sc[csc] = '|';
for (size_t t = 0; t < slength; ++t)
mvwaddch(showin, yinit+t, x, sc[t]);
wrefresh(showin);
}
int main()
{
initscr();
cbreak();
noecho();
intrflush(stdscr, TRUE);
keypad(stdscr, TRUE);
start_color();
init_pair(1, COLOR_GREEN, COLOR_BLACK);
init_pair(2, COLOR_RED, COLOR_CYAN);
init_pair(3, COLOR_BLUE, COLOR_WHITE);
init_pair(4, COLOR_YELLOW, COLOR_MAGENTA);
init_pair(5, COLOR_BLACK, COLOR_CYAN);
int size = sizeof(itemsstr) / sizeof(char*);
ITEM *items[size];
for (int i=0; i!=size; ++i)
items[i] = new_item(itemsstr[i][0], itemsstr[i][1]);
set_item_userptr(items[2], settingsf);
MENU* menu = new_menu(items);
int nlines = 12, ncols = 50, ybeg = 6, xbeg = 15;
WINDOW* mwin = newwin(nlines, ncols, ybeg, xbeg);
refresh();
keypad(mwin, TRUE);
int derlines = nlines-3, dercols = 20, derybeg = 2, derxbeg = 4;
int desccols = 30;
WINDOW* descwin =
derwin(mwin, derlines+1, desccols, 1, ncols-desccols-1);
set_menu_win(menu, mwin);
set_menu_sub(menu, derwin(mwin, derlines, dercols, derybeg, derxbeg));
curs_set(0);
wattron(descwin, COLOR_PAIR(2));
wbkgdset(descwin, COLOR_PAIR(2));
wattron(mwin, COLOR_PAIR(4));
set_menu_fore(menu, COLOR_PAIR(4));
set_menu_mark(menu, "> ");
set_menu_format(menu, derlines-2, 1);
menu_opts_off(menu, O_SHOWDESC);
box(mwin, 0, 0);
char msg[] = "Pause Menu!";
mvwaddstr(mwin, 0, (ncols-strlen("Pause Menu!"))/2, msg);
post_menu(menu);
wrefresh(mwin);
const char* dsc = item_description(current_item(menu));
showdesc(dsc, descwin);
showscroll(menu, mwin);
pos_menu_cursor(menu);
bool pause = true;
while (true) {
int ch = getch();
if (ch == 'q')
break;
switch (ch) {
case ' ':
addch('~');
refresh();
break;
case 27:
if (!pause) {
box(mwin, 0, 0);
mvwaddstr(mwin, 0, 2, "Pause Menu!");
post_menu(menu);
wrefresh(mwin);
}
else {
unpost_menu(menu);
wrefresh(mwin);
}
pause = !pause;
break;
case KEY_UP:
menu_driver(menu, REQ_UP_ITEM);
dsc = item_description(current_item(menu));
showdesc(dsc, descwin);
showscroll(menu, mwin);
break;
case KEY_DOWN:
menu_driver(menu, REQ_DOWN_ITEM);
dsc = item_description(current_item(menu));
showdesc(dsc, descwin);
showscroll(menu, mwin);
break;
case '\n':;
const char* name = item_name(current_item(menu));
if (strcmp(name, "Exit") == 0)
goto end;
else if (name == "Settings") {
void (*func)(MENU*) = item_userptr(current_item(menu));
(*func)(menu);
dsc = item_description(current_item(menu));
showdesc(dsc, descwin);
showscroll(menu, mwin);
}
break;
}
}
end:
unpost_menu(menu);
free_menu(menu);
for (int i=0; i<4; ++i)
free_item(items[i]);
delwin(mwin);
endwin();
return 0;
}
@etzl
Copy link
Author

etzl commented Feb 20, 2021

You can compile with gcc cool_window.c -lmenu -lncurses -o program. Needless to say but you also need to install ncurses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment