Lephisto/src/dialogue.c

359 lines
8.7 KiB
C

/**
* @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 <stdarg.h>
#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;
}