/** * @file dialogue.c. * * @brief Is a hight level api around toolkit.c for easy window creation. * * Only one dialogue may be open at once or behaviour is unspecified. * * All these dialogues use what I'm calling the secondary main loop hack. * Basically they spawn another main lopp identical to the primary whose only * difference is that it breaks on loop_done. Therefore this loop hijacks * the main lopp until it's over, making these functions seem to be blocking * without really being blocking. * * @todo Make dialogue system more flexible. * * @sa toolkit.c */ #include #include "lephisto.h" #include "log.h" #include "toolkit.h" #include "pause.h" #include "opengl.h" #include "input.h" #include "dialogue.h" int dialogue_open; /**< Number of dialogues open. */ /* Extern. */ extern void main_loop(void); /* From lephisto.c */ /* Dialogues. */ static glFont* dialogue_getSize(char* msg, int* w, int* h); static void dialogue_alertClose(unsigned int wid, char* str); static void dialogue_msgClose(unsigned int wid, char* str); static void dialogue_YesNoClose(unsigned int wid, char* str); static void dialogue_inputClose(unsigned int wid, char* str); static void dialogue_inputCancel(unsigned int wid, char* str); /* Secondary loop hack. */ static int loop_done; /**< Used to indicate the secondary loop is finished. */ static int toolkit_loop(void); /** * @brief Check to see if a dialogue is open. */ int dialogue_isOpen(void) { return !!dialogue_open; } /** * @brief Display an alert popup with only an ok button and a message. * @param fmt Printf stype message to display. */ void dialogue_alert(const char* fmt, ...) { char msg[512]; va_list ap; unsigned int wdw; int h; if(fmt == NULL) return; else { /* Get the message. */ va_start(ap, fmt); vsnprintf(msg, 512, fmt, ap); va_end(ap); } h = gl_printHeight(&gl_smallFont, 260, msg); wdw = window_create("Warning", -1, -1, 300, 90+h); window_addText(wdw, 20, -30, 260, h, 0, "txtAlert", &gl_smallFont, &cBlack, msg); window_addButton(wdw, 135, 20, 50, 30, "btnOK", "OK", dialogue_alertClose); dialogue_open++; } /** * @brief Closes the alert dialogue. * @param wid Window being closed. * @param str Unused. */ static void dialogue_alertClose(unsigned int wid, char* str) { (void)str; window_destroy(wid); dialogue_open--; } /** * @brief Get the size needed for the dialogue. * @param msg Message of the dialogue. * @param[out] width Get the width needed. * @param[out] height Get the height needed. * @return The font that matches the size. */ static glFont* dialogue_getSize(char* msg, int* width, int* height) { glFont* font; double w, h, d; int len; w = 300; /* Default width to try. */ len = strlen(msg); /* First we split by text length. */ if(len < 50) { font = &gl_defFont; h = gl_printHeight(font, w-40, msg); } else { /* Now we look at proportion. */ font = &gl_smallFont; h = gl_printHeight(font, w-40, msg); d = ((double)w/(double)h) * (2./4.); /* Deformation factor. */ if(fabs(d) > 0.3) { if(h > w) w = h; h = gl_printHeight(font, w-40, msg); } } /* Set values. */ (*width) = w; (*height) = h; return font; } /** * @brief Open a dialogue window with an OK button and a message. * @param caption Window title. * @param fmt Printf syle message to display. */ void dialogue_msg(char* caption, const char* fmt, ...) { char msg[4096]; va_list ap; int w, h; glFont* font; unsigned int msg_wid; if(fmt == NULL) return; else { va_start(ap, fmt); vsnprintf(msg, 4096, fmt, ap); va_end(ap); } font = dialogue_getSize(msg, &w, &h); /* Create the window. */ msg_wid = window_create(caption, -1, -1, w, 110 + h); window_addText(msg_wid, 20, -40, w-40, h, 0, "txtMsg", font, &cBlack, msg); window_addButton(msg_wid, (w-50)/2, 20, 50, 30, "btnOK", "OK", dialogue_msgClose); dialogue_open++; toolkit_loop(); } /** * @brief Closes a message dialogue. * @param wid Window being closed. * @param str Unused. */ static void dialogue_msgClose(unsigned int wid, char* str) { (void)str; window_destroy(wid); loop_done = 1; dialogue_open--; } /* Run a dialogue with a Yes and No button, return 1 if yes, 0 for no. */ static int yesno_result; static unsigned int yesno_wid = 0; int dialogue_YesNo(char* caption, const char* fmt, ...) { char msg[4096]; va_list ap; int w, h; glFont* font; if(yesno_wid) return -1; if(fmt == NULL) return -1; else { /* Get the message. */ va_start(ap, fmt); vsnprintf(msg, 4096, fmt, ap); va_end(ap); } font = dialogue_getSize(msg, &w, &h); /* Create the window. */ yesno_wid = window_create(caption, -1, -1, w, h+110); /* Text. */ window_addText(yesno_wid, 20, -40, w-40, h, 0, "txtYesNo", font, &cBlack, msg); /* Buttons. */ window_addButton(yesno_wid, w/2-50-10, 20, 50, 30, "btnYes", "Yes", dialogue_YesNoClose); window_addButton(yesno_wid, w/2+10, 20, 50, 30, "btnNo", "No", dialogue_YesNoClose); /* Tricky secondary loop. */ dialogue_open++; toolkit_loop(); /* Return the result. */ return yesno_result; } /** * @brief Closes a yesno dialogue. * @param wid Window being closed. * @param str Unused. */ static void dialogue_YesNoClose(unsigned int wid, char* str) { /* Store the result. */ if(strcmp(str, "btnYes")==0) yesno_result = 1; else if(strcmp(str, "btnNo")==0) yesno_result = 0; /* Destroy the window. */ window_destroy(wid); yesno_wid = 0; loop_done = 1; dialogue_open--; } static unsigned int input_wid = 0; static int input_cancelled = 0; /** * @brief Create a dialogue that allows the player to write a message. * * You must free the result if it's not null. * @param title Title of the dialogue window. * @param min Minimum length of the message (must be non-zero). * @param max Maximum length of the message (must be non-zero). * @param fmt Printf style message to display on the dialogue. * @return The message the player typed or NULL if it was cancelled. */ char* dialogue_input(char* title, int min, int max, const char* fmt, ...) { char msg[512], *input; va_list ap; int h; if(input_wid) return NULL; if(fmt == NULL) return NULL; else { /* Get the message. */ va_start(ap, fmt); vsnprintf(msg, 512, fmt, ap); va_end(ap); } /* Start out not cancelled. */ input_cancelled = 0; /* Get text height. */ h = gl_printHeight(&gl_smallFont, 200, msg); /* Create Window. */ input_wid = window_create(title, -1, -1, 240, h+140); window_setAccept(input_wid, dialogue_inputClose); window_setCancel(input_wid, dialogue_inputCancel); /* Text. */ window_addText(input_wid, 30, -30, 200, h, 0, "txtInput", &gl_smallFont, &cDConsole, msg); /* Input. */ window_addInput(input_wid, 20, -50-h, 200, 20, "inpInput", max, 1); /* Button. */ window_addButton(input_wid, -20, 20, 80, 30, "btnClose", "Done", dialogue_inputClose); /* Tricky secondary. */ dialogue_open++; input = NULL; while(!input_cancelled && (!input || ((int) strlen(input) < min))) { /* Must be longer than min. */ if(input) { dialogue_alert("Input must be at least %d characters long!", min); free(input); input = NULL; } if(toolkit_loop() != 0) /* Error in loop -> quit. */ return NULL; if(input_cancelled != 0) input = NULL; else input = strdup(window_getInput(input_wid, "inpInput")); } /* Cleanup plox. */ window_destroy(input_wid); input_wid = 0; dialogue_open--; /* Return the result. */ return input; } /** * @brief Closes an input dialogue. * @param wid Unused. * @param str Unused. */ static void dialogue_inputClose(unsigned int wid, char* str) { (void)str; (void)wid; /* Break the loop. */ loop_done = 1; } /** * @brief Cancels an input dialogue. * @param wid Window being closed. * @param str Unused. */ static void dialogue_inputCancel(unsigned int wid, char* str) { input_cancelled = 1; dialogue_inputClose(wid, str); } /** * @brief Spawns a secondary loop that only works until the toolkit dies, * alot like the main while loop in lephisto.c * @return 0 on success. */ static int toolkit_loop(void) { SDL_Event event; loop_done = 0; while(!loop_done && toolkit) { /* Loop first so exit condition is checked before next iteration. */ main_loop(); while(SDL_PollEvent(&event)) { /* Event loop. */ if(event.type == SDL_QUIT) { /* Pass quit event to main engine. */ loop_done = 1; SDL_PushEvent(&event); return -1; } input_handle(&event); /* Handles all the events and player keybinds. */ } } return 0; }