Last active
March 8, 2021 12:02
-
-
Save etzl/17612eb71654a7198e7a4be6c7d73f50 to your computer and use it in GitHub Desktop.
A cool looking window with extra features (using ncurses)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You can compile with
gcc cool_window.c -lmenu -lncurses -o program
. Needless to say but you also need to install ncurses.