-[ 0x0C ]-------------------------------------------------------------------- -[ Interfaces con SDL ]------------------------------------------------------ -[ by blackngel ]----------------------------------------------------SET-35-- ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel || * * * * (C) Copyleft 2008 everybody _* *_ 1 - Prologo 2 - Introduccion 3 - GUI's con SDL 3.1 - Eventos 3.1.1 - Raton 3.1.2 - Teclado 3.2 - Imagenes 3.2.1 - SDL_image 3.2.2 - Ventanas 3.2.3 - Botones 3.3 - Audio 3.3.1 - SDL_mixer 3.3.2 - CD-ROM 3.4 - Texto 3.5 - Varios 3.5.1 - Envoltorios 3.5.2 - SDL_strlcpy & SDL_strlcat 3.5.3 - Threads (hilos) 3.5.4 - Graficos Primitivos 3.6 - Efectos 3.6.1 - Zoom 3.6.2 - Rotaciones 4 - Consola Personal 4.1 - Representacion 4.2 - Escritura y Borrado 4.3 - Scroll 4.4 - Ejecutar comandos 4.5 - Utiles 5 - Conclusion 6 - Referencias ---[ 1 - Prologo Aqui comienza el camino, estas dispuesto a recorrerlo? Si estas aburrido de escribir programas en modo consola... si has experimentado con "ncurses" y su potencia o gracia no te acaba de persuadir; pero todavia no quieres introducirte en el mundo de Glade, GTK, Borland C++ Builder o cosas por el estilo, entonces, y solo entonces puede que SDL sea lo que estas buscando. SDL es una libreria pensada inicialmente para la creacion de videojuegos en 2D (puede ayudar en 3D junto con OpenGL). Pero este articulo no se centra en tal habilidad. Nosotros aprovecharemos esta libreria para crear interfaces graficas de usuario, mas conocidas como GUI. Esta libreria nos dara una sensacion de Programacion Orientada a Eventos (POE, oh Edgar... };-D) ---[ 2 - Introduccion SDL [1], o Simple DirectMedia Layer es compatible con la mayoria de Sistemas Operativos, incluyendo Linux, Windows, MacOS, las variantes BSD, y muchos otros. Desarrollaremos el codigo bajo Linux y mostraremos como compilar los programas correctamente, no obstante, portar todo lo aqui descrito a cualquier otro sistema acaba resultando en algo trivial. Una de las caracteristicas mas importantes de SDL es su division en subsistemas tales como video, audio, eventos, cdrom, red, hilos (threads), manejo de texto y mas... varios de estos subsistemas forman parte de extensiones que han venido al rescate de la, a veces, arcaica base de SDL. Se explicaran en su momento y se indicaran las opciones de compilacion correspondientes. Gracias a esta forma de trabajar, nosotros podemos elegir los que nos interesen y empezar a desarrollar nuestras aplicaciones inmediatamente. Si necesitas un peque~o adelanto para ir cogiendo ideas, esto es para ti [2]. ************** NO SUPRIMIRE EL MANEJO DE ERRORES EN EL CODIGO. MUCHOS LO * IMPORTANTE * HACEN Y SOLO CONSIGUEN CREAR APTITUDES DE PROGRAMACION ************** PEREZOSAS, INCORRECTAS Y LA MAYOR DE LAS VECES PELIGROSAS. ---[ 3 - GUI's con SDL Bien, empezaremos por a~adir a nuestro programa la cabecera principal: #include "SDL/SDL.h" Definiremos una superficie principal que representara la pantalla principal de la aplicacion durante la ejecucion del programa: SDL_Surface *pantalla; Ahora, lo principal es iniciar el sistema SDL en si. La siguiente funcion nos proporciona esta facilidad: atexit(SDL_Quit); if (SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0) { fprintf(stderr, "Error al iniciar SDL: %s\n", SDL_GetError()); exit(-1); } La primera llamada puede haber llamado tu atencion, pero es muy sencillo. La funcion 'atexit()' registra el nombre de las funciones que seran llamadas antes de la finalizacion del programa. Como puedes observar, al iniciar el sistema basico tambien podemos arrancar otros subsistemas mediante el uso de constantes combinadas con un OR logico. Podemos utilizar otros como: SDL_INIT_CDROM SDL_INIT_TIMER SDL_INIT_JOYSTICK O iniciarlos todos con: SDL_INIT_EVERYTHING Si has olvidado iniciar un subsistema y no lo has indicado en esta funcion, todavia puedes iniciarlo de esta forma: if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) { fprintf(stderr, "No se puede iniciar el joystick %s\n", SDL_GetError()); exit(1); } Podras detenerlos analogamente con la funcion 'SDL_QuitSubSystem()'; Establezcamos seguidamente el modo de video, que creara nuestra ventana principal con la resolucion y profundidad de color que indiquemos y ciertos atributos que explicaremos a continuacion: pantalla = SDL_SetVideoMode(1024, 768, 24, SDL_ANYFORMAT | SDL_DOUBLEBUF); if (pantalla == NULL){ fprintf(stderr, "No se pudo iniciar el modo de pantalla: %s\n", SDL_GetError()); SDL_Quit(); exit(1); } OPCIONES (flags): SDL_SWSURFACE -> Superficie en memoria del sistema. SDL_HWSURFACE -> Superficie en memoria de video. SDL_ASYNCBLIT -> Actualizacion asincrona. SDL_ANYFORMAT -> Fuerza el uso de los bpp de la surface actual. Hay que usarlo cuando queramos crear la superficie en una ventana. SDL_HWPALETTE -> Da a SDL acceso exclusivo a la paleta de color. SDL_DOUBLEBUF -> Solo valido con SDL_HWSURFACE. Tecnica del "doble buffer". SDL_FULLSCREEN -> Visualizacion a pantalla completa. SDL_OPENGL -> Crea un contexto OpenGL. SDL_OPENGLBLIT -> Igual a la anterior, pero SDL hace el renderizado 2D. SDL_RESIZABLE -> La ventana puede cambiar de tama~o. SDL_NOFRAME -> Crea una ventana sin borde. Las constantes anteriores puedes encontrarlas directamente en la cabecera correspondiente, no obstante, yo he cogido las descripciones utilizadas en este documento [3]. Existen otras 6 constantes, pero son de uso interno y no las podras establecer con la funcion anterior. Puedes estudiar mas en . Por ultimo, si quieres definir el titulo de la ventana de tu aplicacion, puedes hacerlo mediante: SDL_WM_SetCaption("Hack The World", NULL); El segundo parametro es el nombre de un icono opcional, puedes obtener estos valores con la analoga 'SDL_WM_GetCaption()' y establecer por separado un icono con 'SDL_WM_SetIcon(SDL_LoadBMP("/home/usuario/icono.bmp"), NULL)'. Puedes minimizar la pantalla durante la ejecucion del programa mediante la funcion 'SDL_WM_IconifyWindow()'. Si quieres ver como funciona tu primera ventana, puedes utilizar un codigo general de compilacion como este: $ gcc prog.c -lSDL -o prog o $ gcc proc.c `sdl-config --cflags` `sdl-config --libs` -o prog Vale, hasta aqui tenemos lo basico para iniciar el sistema y comenzar a cargar graficos (imagenes). ---[ 3.1 - Eventos Esta es, sin duda alguna, la parte mas importante de una aplicacion creada con SDL. Se inicia siempre junto al sistema de video y es en resumen la parte del sistema que nos permite interactuar con la aplicacion, ya sea mediante el teclado, el raton, un joystick u otros relaciones mas directamente con el sistema operativo. Como aperitivo mostrare el bucle principal (main loop) que yo suelo utilizar para controlar los eventos en mis aplicaciones. Luego dare una peque~a aclaracion sobre su estructura y funciones, dando paso, ya por ultimo, a las siguientes secciones que explicaran particularmente el caso de cada dispositivo de entrada. **-------------------------------------------------------------------------** SDL_Event evento; int terminar = 0; while (!terminar) { SDL_WaitEvent(&evento); switch (evento.type) { case SDL_QUIT: terminar = 1; break; case SDL_MOUSEBUTTONDOWN: sonidos(1); /* printf("\nX=%d - Y=%d\n", evento.button.x, evento.button.y); */ terminar = accion(evento.button.x, evento.button.y); break; case SDL_MOUSEMOTION: motion_cursor(evento.motion.x, evento.motion.y); break; case SDL_KEYDOWN: sonidos(2); mkeys(evento); break; default: break; } } **-------------------------------------------------------------------------** Empecemos por el principio. Lo mas basico es el bucle while que estara siempre iterando hasta que la variable 'terminar' tenga un valor positivo. Seguidamente viene la funcion mas importante. No me queda mas remedio que explicar en este momento las funciones de captura de eventos que SDL nos facilita. Son 3: - SDL_WaitEvent() -> Esta funcion espera por la llegada de un evento, ya sea un click, una pulsacion de teclado, la peticion de cierre de la ventana u otro cualquiera. Se puede decir que esta funcion es 'bloqueante' pues nada ocurrira mientras un evento no se produzca y demos una respuesta al mismo. - SDL_PollEvent() -> Muy parecida a la anterior, pero esta es mas usual en los videojuegos. En estos entornos, se precisa que la aplicacion siga realizando tareas en segundo plano aun a pesar de que no existan eventos a los que responder. Esta llamada no es bloqueante y ese es el motivo. - SDL_PumpEvents() -> Esta funcion nos permitira acceder directamente al estado de un dispositivo para conocer si algun evento se ha producido en ese mismo instante. Su uso es menos habitual y puede que solo la veas ocasionalmente. Lo que vemos a continuacion es una sentencia 'switch' que maneja el valor almacenado en el campo 'type' de la estructura 'SDL_Event'. De este modo sabremos exactamente que evento se ha producido. Puedes entonces introducir tantas sentencias 'case' como constantes hay definidas en la enumeracion 'SDL_EventType' declarada en el archivo de cabecera En este caso hacemos lo siguiente: 1 - Para 'SDL_QUIT', que resulta de hacer click en la [X] de la ventana, activamos la variable 'terminar' que provoca la salida inmediata del bucle 'while'. 2 - Para 'SDL_MOUSEBUTTONDOWN', que resulta de hacer click en cualquier region de la ventana, reproducimos un sonido (se vera en una seccion posterior), y ejecutamos una accion segun donde se haya clickado (se vera en la seccion 3.1.1). 3 - Para 'SDL_MOUSEMOTION', que resulta de haber movido el raton dentro de la ventana, nos comportamos practicamente como en el evento anterior pero sin reproducir sonido alguno. 4 - Para 'SDL_KEYDOWN', que resulta de presionar cualquier tecla, reproducimos un sonido y ejecutamos una funcion que se encarga de controlar que pulsacion hemos realizado exactamente y realizar un cometido segun corresponda. Habras observado una sentencia 'printf()' convenientemente comentada. Pues bien; esta instruccion te sera practicamente imprescindible en la etapa de desarrollo de tus programas. Imprime las coordenadas donde has hecho click, y esto sera mas que necesario cuando quieras conocer la posicion de un objeto situado en pantalla y definir "regiones de clickado" de un modo veloz. Pasemos ahora a describir las acciones de comportamiento que utilizaremos para cada dispositivo. ---[ 3.1.1 - Raton Como acabas de ver hace tan solo unos instantes, cuando un evento 'click' se produce, las coordenadas de la pulsacion son almacenadas en el elemento 'button' de la union 'SDL_Event'. He dicho que 'button' es un elemento y no una variable porque en realidad es otra estructura. Lo que es mas, menos uno, todos los elementos de 'SDL_Event' son estructuras que controlan todas las propiedades de cada evento. Pero no nos vayamos del tema. Si sigues echando un vistazo a la estructura 'SDL_MouseButtonEvent', podras ver otros elementos, como cual de los dos botones del raton se ha pulsado, o si el boton pulsado esta bajando o subiendo, aparte de otras mas. Una funcion tipica que ejecute acciones segun las coordenadas donde hayas pulsado suele tener la siguiente estructura: **-------------------------------------------------------------------------** int accion(Uint16 X, Uint16 Y) { /* BOTON 1 */ if ((X >= 915 && Y >= 685) && (X <= 1024 && Y <= 718)) { funcion01(arg1, arg2, ...); } /* BOTON 2 */ else if ((X >= 915 && Y >= 648) && (X <= 1024 && Y <= 682)) { funcion02(arg1, arg2, ...); } /* BOTON 3 */ else if ((X >= 915 && Y >= 614) && (X <= 1024 && Y <= 646)) { funcion03(arg1, arg2, ...); } /* BOTON SALIR */ else if ((X >= 915 && Y >= 578) && (X <= 1024 && Y <= 610)) { return 1; } return 0; } **-------------------------------------------------------------------------** La funcion puede llegar a complicarse tanto como desees, pero al fin y al cabo siempre acaba teniendo una estructura similar. Puedes pensar ahora para que necesitas una funcion que controle el movimiento del raton sobre la ventana. Pues es bastante facil, imaginate que tienes unas imagenes representando botones (se vera mas adelante), podrias desear que los botones se iluminen cada vez que pasas por encima de ellos. Pero OJO: Debes prestar especial atencion a la hora de crear esta funcion. Puede parecer invisible, pero esta funcion se ejecutara cada vez que el cursor del raton se mueva un solo pixel en cualquier direccion. Esto quiere decir que la funcion puede ejecutarse cientos de veces por segundo. Si los condicionales no estan bien definidos y tiene que recorrer mas de los que deberia, el rendimiento podria verse seriamente afectado. No tendras problema alguno en desarrollar la tuya propia, pero quizas veamos algo mas adelante cuando tratemos con la representacion de botones o menus. ---[ 3.1.2 - Teclado Para controlar las pulsaciones del teclado accederemos a un miembro de 'SDL_Event' llamado 'key' que es una estructura 'button' y que a su vez contiene otra estructura mas con el nombre 'SDL_keysym' cuyo elemento mas importante es el miembro 'sym' que, a pesar de que os entren unas ganas enormes de asesinarme, es una ultima estructura que define la totalidad de las constantes referentes a las posibles teclas pulsadas. Segun la tecla pulsada tu podras hacer lo que creas conveniente. Yo mostrare aqui un ejemplo que llama a una funcion 'escribir()' para cada tecla pulsada cuyo objetivo es escribir el caracter correspondiente en la representacion de una consola (o shell) que estudiaras en un capitulo posterior. Recortare el codigo: **-------------------------------------------------------------------------** void mkeys(SDL_Event ev) { int shift = 0; int altgr = 0; if (ev.key.keysym.mod & (KMOD_LSHIFT|KMOD_RSHIFT)) shift = 1; else if (ev.key.keysym.mod & (KMOD_LALT|KMOD_RALT)) altgr = 1; switch (ev.key.keysym.sym){ case SDLK_ESCAPE: terminar = 1; break; case SDLK_BACKSPACE: borrar(); break; case SDLK_RETURN: ejecutar(); break; case SDLK_TAB: escribir('\t', 1); break; case SDLK_SPACE: escribir(' ', 1); break; case SDLK_COMMA: if (shift) escribir(':', 1); else escribir('.', 1); break; case SDLK_a: if (shift) escribir('A', 1); else escribir('a', 1); break; case SDLK_b: if (shift) escribir('B', 1); else escribir('b', 1); break; ... ... ... case SDLK_1: if (shift) escribir('!', 1); else if (altgr) escribir('|', 1); else escribir('1', 1); break; case SDLK_2: if (shift) escribir('"', 1); else if (altgr) escribir('@', 1); else escribir('2', 1); break; ... ... ... default: break; } } **-------------------------------------------------------------------------** Como ya he dicho, lo importante es comprender el procedimiento que seguimos para llevar a cabo nuestros objetivos. Posteriormente echaremos un vistazo a las funciones que en este caso he utilizado. Es muy bueno observar el uso que hemos hecho del elemento 'mod' de la estructura 'SDL_keysym'. Siempre comprobamos si cada vez que pulsamos una tecla lo hacemos simultaneamente con SHIFT o ALT(GR). ---[ 3.2 - Imagenes Lo principal es que conozcamos que es una SUPERFICIE. En realidad es una estructura que controla todos los pixeles de una imagen o region de la pantalla asi como su profundidad de color y otro tipo de propiedades. Nos sobrara con saber que se definen siempre igual que la superficie principal: SDL_Surface *imagen; Otra estructura basica que debemos estudiar es 'SDL_Rect', sirve para definir una region en la pantalla indicando sus coordenadas iniciales y el ancho/alto de la misma. Se acostumbra a utilizar asi: SDL_Rect rect = (SDL_Rect) {300, 300, 100, 100}; Esto define una region que comienza en las posiciones x=300, y=300, teniendo una medida de 100 pixeles tanto para el ancho como para el alto. Si los dos ultimos valores son iguales a 0, cuando dibujemos la superficie se igualaran automaticamente al ancho y alto de la misma. Podemos tambien acceder a sus elementos individualmente de esta forma: rect.x = 300; rect.y = 300; rect.w = 100; rect.h = 100; SDL nos proporciona una funcion basica para la carga de imagenes, que es conocida como: imagen = SDL_LoadBMP("/home/usuario/file.bmp"); Devuelve siempre el valor NULL cuando no ha podido cargar el archivo. Pero esta funcion es pobre y no soporta otros formatos. Es aqui donde entran en juego las librerias auxiliares. En este caso SDL_image. Antes de pasar a estudiar el uso de esta libreria debemos conocer algunas funciones mas para el manejo basico de las superficies. Por ejemplo, ahora que ya tenemos cargada una superficie en la variable 'imagen', la pregunta es: como dibujarla en pantalla? 'SDL_BlitSurface()' al rescate. Mostraremos como se usa y luego explicaremos sus argumentos: SDL_BlitSurface(imagen, NULL, pantalla, &rect); 1 - La superficie que queremos dibujar. 2 - La porcion de la superficie que queremos dibujar (SDL_Rect). Un valor NULL dibuja la superficie completa. 3 - La superficie sobre la que dibujaremos. 4 - Las coordenadas donde imprimiremos la superficie a pintar (SDL_Rect). No siempre desearemos dibujar nuestras superficies o imagenes directamente sobre la pantalla principal; pero quedas advertido, si realizas un 'blit' sobre cualquier otra superficie, luego estaras obligado a realizar el 'blit' de esta ultima sobre la pantalla principal. Muy bien, si has llegado hasta aqui y has intentado dibujar una imagen propia sobre la ventana principal, te preguntaras porque esta no es visible y parece que la ejecucion de tu aplicacion ha fallado. No te precipites, aqui tienes la solucion: SDL_Flip(pantalla); Esta funcion provoca que la representacion grafica que has creado en memoria hasta el momento, sea volcada en pantalla. Podrias decir vulgarmente que estas "actualizando la pantalla" si te sientes mas comodo. ---[ 3.2.1 - SDL_image La funcion mas importante que proporciona esta libreria es: - SDL_Surface * IMG_Load(const char *file) Puedes usarla tal y como lo has hecho con 'SDL_LoadBMP()'. Pero entonces, cual es la diferencia? Una y muy grande. Soporta los siguiente formatos: - BMP, PNM, XPM, LBM, PCX, GIF, JPG, PNG y TGA. Puedes incluir esta libreria muy facilmente a~adiendo "-lSDL_image" a tus opciones de compilacion normales. No hay mas que decir, creo que esta mas que claro que esta es la funcion que empezaras a utilizar a partir de ahora. ---[ 3.2.2 - Ventanas La cruda realidad es que SDL no comprende entidades como ventanas o botones. No tiene estructuras para manejar este tipo de objetos y eso supone un paso hacia atras para nosotros. En SDL todo es apariencia, y como tal, nuestro objetivo es utilizar una imagen para darle la apariencia de una ventana y cierto comportamiento que se asemeje lo suficiente. Lo normal es utilizar siempre una imagen en formato "PNG", dado que soporta las transparencias y ese es un aspecto muy util a la hora de crear una interfaz atractiva y eficiente. Las imagenes en este formato tiene una alta compresion y ocupan relativamente poco espacio. Un editor como "GIMP" puede ayudarte mucho en tareas como esta. Encontrar una imagen con el aspecto de una ventana es tan facil como navegar durante unos minutos por la web o simplemente sacar una captura de pantalla de una ventana de tu PC y editarla posteriormente para borrar todo su contenido, dejando unicamente el marco de la misma. Como aqui no vamos a ver la posibilidad de arrastrar una ventana por la pantalla, que podria tornarse en una tarea infernal (pues requiere el repintado continuo de todas las superficies por las que esta pasa), definiremos unas regiones en las que interactuaran nuestras ventanas individualmente. Lo logico seria situar las ventanas en cada una de las esquinas de la pantalla. Algo asi: ______________________ |------------- --------| || V1 | | V2 || || | | || |------------- --------| |------------- | || V3 | === | || | === | |------------- === | |______________________| A mi me gusta utilizar dos peque~as funciones para cargar y mostrar en pantalla una ventana. Las expongo aqui y las explico a continuacion: **-------------------------------------------------------------------------** SDL_Surface *v1; void carga_v1(int put) { v1 = IMG_Load(IMAGES"v1.png"); if (!v1) { fprintf(stderr, "No se pudo cargar v1.png\n"); SDL_Quit(); exit(-1); } if (put) put_v1(); } void put_v1(void) { SDL_Rect r1; r1 = (SDL_Rect) {0, 0, 705, 375}; SDL_FillRect(pantalla, &r1, SDL_MapRGB(pantalla->format,0,0,0)); r1 = (SDL_Rect) {0, 0, 0, 0}; SDL_BlitSurface(v1, NULL, pantalla, &r1); SDL_Flip(pantalla); } **-------------------------------------------------------------------------** Te preguntaras por que no poner las dos funciones en una. El motivo es el siguiente. La primera vez que entras en el programa llamaras a la funcion 'carga_v1()' con un argumento positivo; esto cargara la imagen (ventana) y la mostrara en pantalla. Durante el resto de la ejecucucion del programa, cuando realices un 'blit' sobre esta ventana, llamaras simplemente a 'put_v1()', que borrara la region y repintara la ventana nuevamente con los cambios realizados. Solo llamaremos a 'carga_v1()' cuando querramos obtener una ventana limpia, sin modificaciones. Bien, imaginate ahora que ya tenemos la imagen de la ventana cargada en la esquina superior izquierda de la ventana. Imaginate tambien que esa imagen tiene dibujados dos cuadrados o circunferencias (o lo que sea) representando los botones de cierre, minimizado, etc... Tal como se ha explicado en una seccion anterior, podemos definir una "region de clickado" que realice una operacion al clickar en esta zona. Podriamos a~adir a la funcion 'accion()' algo como: /* BOTON "CERRAR" DE V1 */ if ((X >= 642 && Y >= 6) && (X <= 662 && Y <= 26)) { if (is_v1) quitar_v1(); } Lo mas facil es que cuando hagamos click en este "boton", la ventana deje de ser visible ya sea dibujando un rectangulo negro encima, haciendo que vaya desapareciendo desplazandose hacia la izquierda, hacia arriba o incluso en diagonal. Nosotros, que somos asi de atrevidos vamos a optar por esta ultima eleccion: **-------------------------------------------------------------------------** void quitar_v1(void) { SDL_Rect pos; int x=0, y=0; while(x < 400 && y < 400){ pos = (SDL_Rect) {0, 0, 705, 375}; SDL_FillRect(pantalla, &pos, SDL_MapRGB(pantalla->format,0,0,0)); pos = (SDL_Rect) {0-x, 0-y, v1->w, v1->h}; SDL_BlitSurface(v1, NULL, pantalla, &pos); SDL_Flip(pantalla); x += 20; y += 20; SDL_Delay(20); } is_v1 = 0; } **-------------------------------------------------------------------------** No te ciegues, la operacion no es nada complicada. Simplemente vamos restando 20 a las coordenadas iniciales (0,0) y redibujamos la ventana en cada vuelta. Entremedias dibujamos siempre un rectangulo negro que cubra toda la zona. Es esta secuencia de "quitar y poner" la que nos ofrece una sensacion real de movimiento. La ventana ira desapareciendo diagonalmente hasta perderse fuera de la region visible de la pantalla. 'SDL_Delay()' hace una funcion similar a la 'usleep()' que es detener el programa la cantidad de milisegundos especificados como unico argumento. Puedes incrementar esta cifra si deseas un movimiento mas lento o bajarla si deseas el efecto contrario. Como puedes ver, utilizamos una ultima variable global 'is_v1' que usamos como si de un dato booleano se tratase y que nos permitira saber en que estado se encuentra la ventana (abierta/cerrada o visible/oculta). Deberias tener una lista de botones en la parte inferior de la ventana, o en aquel sitio que tu elijas, que te permita traer de nuevo las ventanas que has cerrado (como crear botones simulados sera el tema del siguiente apartado). La funcion de regreso de una ventana puede parecerse a esto: **-------------------------------------------------------------------------** void traer_v1(void) { SDL_Rect pos; int x=0, y=0; while(x < 700 && y < 700){ pos = (SDL_Rect) {0, 0, 705, 375}; SDL_FillRect(pantalla, &pos, SDL_MapRGB(pantalla->format,0,0,0)); if (x > -50 && y > 365) pos = (SDL_Rect) {0, 0, v1->w, v1->h}; else pos = (SDL_Rect) {-710+x, -768+x, v1->w, v1->h}; SDL_BlitSurface(v1, NULL, pantalla, &pos); SDL_Flip(pantalla); x += 20; y += 20; SDL_Delay(20); } is_v1 = 1; } **-------------------------------------------------------------------------** La operacion es muy parecida. Recuerda tambien que las cifras van a depender siempre de tu caso en particular y que deberas ajustarlas en su momento. La unica diferencia en esta ocasion es que es muy improbable que la ultima iteracion del bucle termine colocando la ventana en sus coordenadas exactas. Es por ello que controlamos cuando la ventana se acerca a esta posicion y acabamos por colocarla nosotros mismos en el lugar correspondiente. ---[ 3.2.3 - Botones Para practicar con la representacion de botones seguiremos el penoso ASCII ART que utilizamos en el apartado anterior, es decir, que situaremos un boton en la esquina inferior derecha que abrira un menu cuando hagamos click en el. Tambien veremos como crear un efecto de iluminado cuando pasemos por encima de cada uno de los botones. Esto les dara una sensacion de volumen y aumentara notablemente su credibilidad. Los botones deben ser sin duda, junto con las fotografias xxx, las imagenes mas abundantes en la telara~a de la World Wide Web. Incluso si te molestas en buscar un poco en Google, encontraras unos peque~os programas que te permitiran crear, on-line, tus propios botones personalizados. Recuerda utilizar formato "PNG" preferentemente, si utilizas botones con esquinas redondas este requisito se vuelve cuasi imprescindible. La primera funcion que debemos definir, se encarga de cargar las imagenes en las superficies correspondientes. Segun el argumento, se cargaran los botones normales o los que hayamos creado con efecto iluminado. Piensa que podrias utilizar otro 'case' para cargar botones, por ejemplo, con efecto presionado. **-------------------------------------------------------------------------** void carga_botones(int op) { switch (op) { case 0: { boton0 = IMG_Load(IMAGES"boton0.png"); boton1 = IMG_Load(IMAGES"boton1.png"); boton2 = IMG_Load(IMAGES"boton2.png"); boton3 = IMG_Load(IMAGES"boton3.png"); break; } case 1: { boton0 = IMG_Load(IMAGES"boton0_ilu.png"); boton1 = IMG_Load(IMAGES"boton1_ilu.png"); boton2 = IMG_Load(IMAGES"boton2_ilu.png"); boton3 = IMG_Load(IMAGES"boton3_ilu.png"); break; } } if (!boton0 || !boton1 || !boton2 || !boton3) { fprintf(stderr, "Don't load some button image\n"); SDL_Quit(); exit(-1); } } **-------------------------------------------------------------------------** Comprobamos al final que todas las imagenes han podido ser cargadas correctamente, evitamos asi caidas inesperadas. La siguiente funcion se encarga de poner los botones que seran estaticos, es decir, los que no forman parte del menu interno y seran visibles durante toda la ejecucion. En nuestro caso solo pondremos uno en la esquina inferior derecha, pero podriamos poner tres en horizontal y hacer que cada uno abriera un menu diferente siguiendo los pasos que aqui explicaremos. **-------------------------------------------------------------------------** void poner_botones(void) { SDL_Rect pos; int x=0, y=0; carga_botones(0); pos = (SDL_Rect) {910, 710, 0, 0}; SDL_BlitSurface(boton0, NULL, pantalla, &pos); SDL_Flip(pantalla); } **-------------------------------------------------------------------------** Puedes ver como llamamos antes de nada a 'carga_botones()' con un valor de 0. Mientras no interactuemos con ellos, deben de estar en estado normal. Para lograr recrear la apertura de un menu, primero tenemos que definir una "region de clikado" en torno a 'boton0'. Como dije antes, la instruccion 'printf()' que imprime las coordenadas puede servirte de mucho. Haz click cerca de la esquina superior izquierda de 'boton0' anota la posicion en un papel y repite el proceso para su esquina inferior derecha. Hecho esto, vete a la funcion 'accion()' y a~ade algo como: **-------------------------------------------------------------------------** /* BOTON 0 */ else if ((X >= 920 && Y >= 720) && (X <= 1024 && Y <= 768)) { if (is_menu) cerrar_menu(); else abrir_menu(); } **-------------------------------------------------------------------------** Bien hecho, y ahora vamos directamente a definir las dos funciones tan esperadas. **-------------------------------------------------------------------------** void abrir_menu(void) { SDL_Rect rmenu; int x = 0; carga_botones(0); while(x < 250){ if((1030 - x) >= 910){ rmenu = (SDL_Rect) {745, 675, 300, 40}; SDL_FillRect(pantalla, &rmenu, SDL_MapRGB(pantalla->format,0,0,0)); rmenu = (SDL_Rect) {1030-x, 675, 0, 0}; SDL_BlitSurface(boton1, NULL, pantalla, &rmenu); } if((1070 - x) >= 910){ rmenu = (SDL_Rect) {745, 640, 300, 40}; SDL_FillRect(pantalla, &rmenu, SDL_MapRGB(pantalla->format,0,0,0)); rmenu = (SDL_Rect) {1070-x, 640, 0, 0}; SDL_BlitSurface(boton2, NULL, pantalla, &rmenu); } if((1110 - x) >= 910){ rmenu = (SDL_Rect) {745, 605, 300, 40}; SDL_FillRect(pantalla, &rmenu, SDL_MapRGB(pantalla->format,0,0,0)); rmenu = (SDL_Rect) {1110-x, 605, 0, 0}; SDL_BlitSurface(boton3, NULL, pantalla, &rmenu); } SDL_Flip(pantalla); x += 10; SDL_Delay(20); } is_menu = 1; } **-------------------------------------------------------------------------** El efecto que crea la funcion que acabamos de ver es realmente bonito. Los botones (1, 2 y 3) van apareciendo escalonados por la parte derecha de la ventana y se detienen finalmente para quedar situados en una vertical perfecta. En realidad lo que ocurre es que empezamos dibujando estos botones fuera de la zona visible de la pantalla (lo hacemos ya de forma escalonada). Despues vamos incrementando 'x', cifra que se resta a las coordenadas inciales de cada boton. De esta forma los botones se van dibujando mas hacia la izquierda en cada pasada del bucle. 'SDL_Delay()' controla la velocidad y cada boton se detiene cuando su coordenada horizontal(rmenu.x) llega a 910. No nos olvidamos de dibujar un rectangulo negro que borra cada boton de forma individual en cada iteracion del bucle. Con esto evitamos el rastro que irian dejando los botones en cada operacion de repintado. Activamos por ultimo una variable global llamada 'is_menu' que indicara a la funcion 'accion()' que el menu ya ha sido abierto y el proximo click en 'boton0' conllevara el cierre del mismo. Aqui la funcion analoga: **-------------------------------------------------------------------------** void cerrar_menu(void) { SDL_Rect rmenu; int x = 0; carga_botones(0); while(x < 150){ rmenu = (SDL_Rect) {745, 570, 300, 150}; SDL_FillRect(pantalla, &rmenu, SDL_MapRGB(pantalla->format,0,0,0)); rmenu = (SDL_Rect) {940+x, 675, 0, 0}; SDL_BlitSurface(boton1, NULL, pantalla, &rmenu); rmenu = (SDL_Rect) {920+x, 640, 0, 0}; SDL_BlitSurface(boton2, NULL, pantalla, &rmenu); rmenu = (SDL_Rect) {900+x, 605, 0, 0}; SDL_BlitSurface(boton3, NULL, pantalla, &rmenu); SDL_Flip(pantalla); x += 10; SDL_Delay(20); } is_menu = 0; } **-------------------------------------------------------------------------** La diferencia principal es que aqui la variable 'x' se suma a las coordenadas en cada pasada para ir desplazando los botones hacia la derecha hasta que los mismos desaparezcan de la pantalla. Desactivamos is_menu para dejar las cosas limpias. Muy bien, muy bien. Ya queda poco. Lo prometido es deuda, ahora veremos como crear el efecto de iluminado de los botones. Veamos el primero de los metodos para comentarlo posteriormente. **-------------------------------------------------------------------------** void motion_cursor(Uint16 X, Uint16 Y) { if (is_menu) { if ((X >= 915 && Y >= 685) && (X <= 1024 && Y <= 718)) cambiar_estado(1, 1); else if ((X >= 915 && Y >= 648) && (X <= 1024 && Y <= 682)) cambiar_estado(2, 1); else if ((X >= 915 && Y >= 614) && (X <= 1024 && Y <= 646)) cambiar_estado(3, 1); else if (st_ilu) { cambiar_estado(0, 0); cambiar_estado(1, 0); cambiar_estado(2, 0); cambiar_estado(3, 0); } } else if ((X >= 920 && Y >= 720) && (X <= 1024 && Y <= 768)) cambiar_estado(0, 1); else if (st_ilu) { cambiar_estado(0, 0); } } **-------------------------------------------------------------------------** Esta es la esperada funcion que reacciona ante el movimiento del raton. Pueden ocurrir varias cosas: (1) Si el menu esta abierto: (1.1) Si el puntero del raton se situa encima de alguno de los tres botones (1, 2 o 3), llama a la funcion cambiar_estado() que carga las imagenes iluminadas y redibuja el boton sobre el que esta el puntero. (1.2) En caso contrario, comprobamos si hay algun boton iluminado y los pasamos todos a modo normal. (2) Si el menu esta cerrado: (2.1) Si estamos encima de 'boton0', lo iluminamos (ya dije como). (2.2) En caso contrario lo apagamos :) Y ya por fin la funcion que cambia el estado de un boton especifico: **-------------------------------------------------------------------------** void cambiar_estado(int b, int s) { SDL_Rect pos; switch (s) { case 0:{ carga_botones(0); st_nor = 1; st_ilu = 0; break; } case 1:{ carga_botones(1); st_ilu = 1; st_nor = 0; break; } } switch (b) { case 0:{ pos = (SDL_Rect) {910, 710, 0, 0}; SDL_BlitSurface(boton0, NULL, pantalla, &pos); break; } case 1:{ pos = (SDL_Rect) {910, 675, 0, 0}; SDL_BlitSurface(boton1, NULL, pantalla, &pos); break; } case 2:{ pos = (SDL_Rect) {910, 640, 0, 0}; SDL_BlitSurface(boton2, NULL, pantalla, &pos); break; } case 3:{ pos = (SDL_Rect) {910, 605, 0, 0}; SDL_BlitSurface(boton3, NULL, pantalla, &pos); break; } } SDL_Flip(pantalla); } **-------------------------------------------------------------------------** Facil. El primer argumento indica a que boton se le quiere cambiar su estado. En el segundo que estado activaremos. Una sentencia 'switch()' para cada uno y todo arreglado. ---[ 3.3 - Audio Realizar tareas con el subsistema de audio que proporciona directamente la libreria SDL es posible, pero puede convertirse en toda una haza~a digna de comentar. Es por ello que la dejaremos a un lado centrandonos en la implementacion de la siguiente libreria auxiliar: SDL_mixer. ---[ 3.3.1 - SDL_mixer Compilar un programa que utilice esta libreria es tan facil como a~adir el archivo de cabecera y ayudarse de la opcion "-lSDL_mixer" a la hora de utilizar tu version de 'gcc' favorita. Lo primero es lo primero, que es iniciar el sistema de sonido. Suelo utilizar algo como lo que veras a continuacion: if (Mix_OpenAudio(44100, AUDIO_S16, 2, 4096)) { fprintf(stderr, "No se puede iniciar SDL_mixer %s\n", Mix_GetError()); SDL_Quit(); exit(1); } atexit(Mix_CloseAudio); Los argumentos son estos: 1 - Frecuencia (en Hertzios) para reproducir un 'sample' (*). (*) Yo utilizo la calidad CD, otros posibles son: 11025 -> Calidad telefono. 22050 -> Calidad radio. 2 - Formato del sample (revisa las constantes en ). 3 - Numero de canales (1 = mono, 2 = estereo) 4 - 'Chunksize', esto habitualmente siempre es 4096. Utiliza Mix_CloseAudio() para detener el sistema. Quizas te estes preguntando que significa eso de un "Chunk". Para que lo entiendas, podriamos decir escuetamente que es la estructura donde SDL_mixer almacena un sonido para poder trabajar con el. Ahora expondre una funcion que me gusta utilizar para la reproduccion de sonidos y luego la explicare en detalle para que se entienda: **-------------------------------------------------------------------------** void sonidos(int op){ u_int canal; Mix_Chunk *sonido; switch(op){ case 1:{ sonido = Mix_LoadWAV("/home/usuario/sonidos/sample1.wav"); if(sonido == NULL){ printf("No pude cargar sonido: %s\n", Mix_GetError()); return; } canal = Mix_PlayChannel(-1, sonido, 0); break; } case 2:{ sonido = Mix_LoadWAV("/home/usuario/sonidos/sample2.wav"); if(sonido == NULL){ printf("No pude cargar sonido: %s\n", Mix_GetError()); return; } canal = Mix_PlayChannel(-1, sonido, 0); break; } ... ... ... default: break; } Mix_FreeChunk(sonido); } **-------------------------------------------------------------------------** 'MixLoadWAV()' es analoga a la funcion de carga de imagenes, carga el fichero indicado en su inco parametro y lo alamace en el chunk que hemos creado. Comprobamos siempre que la funcion ha tenido exito y luego llamamos sin mas dilaciones a 'Mix_PlayChannel()' cuyos argumentos son los siguientes: 1 - Numero de canal para reproducir el sonido (*). (*) Un valor de '-1' para seleccion automatica del canal. 2 - Sonido (chunk) a reproducir. 3 - Numero de veces que se repetira el sonido (*). (*) Un valor de '-1' para reproducir indefinidamente. Liberamos finalmente el chunk para mejorar el rendimiento. Aqui tienes mas funciones, sacadas del archivo de cabecera de esta libreria. Dare una ligera explicacion de cada una: - Mix_PlayChannelTimed() -> Igual que la anterior pero lleva un ultimo argumento que indica cuantos milisegundos se reproducira. - Mix_FadeInChannel() y Mix_FadeInChannelTimed() -> -> Analogas a las 2 anteriores pero va subiendo el volumen del sonido de modo gradual. - Mix_Pause(channel), Mix_Resume(channel), Mix_HaltChannel(channel) -> -> Mas que obvios: Pausar, reanudar y detener. 'channel' es un int. - Mix_Playing(int channel) y Mix_Paused(int channel) -> -> Funciones de consulta, con ellas obtienes el estado actual de un canal. Y exiten muchisimas mas. Si te interesa crear un reproductor de sonidos o de musica queda a tu eleccion seguir estudiando este tema. Y hablando de "musica", decir que SDL_mixer diferencia realmente entre la estructura de "un sonido" y de "una musica". Tanto que reserva un canal especial para la reproduccion de esta ultima. Los formatos que reconoce son: - WAV, MP3, MOD, S3M, IT, XM, Ogg Vorbis, VOC y MIDI Todas las funciones para manejar una estructura del tipo 'Mix_Music *' son practicamente analogas a las que controlan "chunks". No te sera dificil investigar un poco por tu cuenta. ---[ 3.3.2 - CD-ROM Para ejecutar las funciones que se ense~aran a continuacion debes incluir en tus programas la cabecera . Existen dos estructuras para el control de un dispositivo CD-ROM. 'SDL_CD' controla el estado del dispositivo, el numero de pistas, la pista actual, el frame actual y un array de estructuras del tipo 'SDL_CDtrack'. Tambien tiene un identificador unico para cada uno de los dispositivos. Como no me voy a poner a mostrar ejemplos sobre el uso de estos metodos. Indicare como siempre las funciones tal y como se pueden ver en la cabecera principal y un escueto comentario. - int SDL_CDNumDrives(void) -> Devuelve el numero de dispositivos CD-ROM. - const char * SDL_CDName(int drive) -> Devuelve el nombre de un dispositivo. - SDL_CD * SDL_CDOpen(int drive) -> Abre un dispositivo de CD-ROM. - CDstatus SDL_CDStatus(SDL_CD *cdrom) -> Devuelve el estado de un CD-ROM. - int SDL_CDPlayTracks(SDL_CD *cdrom, int start_track, int start_frame, int ntracks, int nframes) -> -> Reproduce tantas pistas como se le indique en 'ntracks' empezando en 'star_track'. - int SDL_CDPlay(SDL_CD *cdrom, int start, int length) -> -> Reproduce un CD-ROM desde el frame 'start' hasta la cantidad indicada por 'lenght'. - int SDL_CDPause(SDL_CD *cdrom) -> Pausa la ejecucion de una pista. - int SDL_CDResume(SDL_CD *cdrom) -> Continua la ejecucion de una pista. - int SDL_CDStop(SDL_CD *cdrom) -> Detiene la ejecucion de una pista. - int SDL_CDEject(SDL_CD *cdrom) -> Abre la bandeja del CD-ROM. - SDL_CDClose(SDL_CD *cdrom) -> Cierra el dispositivo CD-ROM. ---[ 3.4 - Texto Para hacer uso del texto en nuestras aplicaciones SDL utilizaremos otra de las librerias auxiliares. Se llama 'SDL_ttf' y la estudiaremos inmediatamente. Deberas utilizar en tu programa el archivo de cabecera y a~adir la opcion de compilacion "-lSDL_ttf". Para iniciar el sistema de texto solemos utilizar algo asi: if (TTF_Init() < 0) { fprintf(stderr, "No se pudo iniciar SDL_ttf: %s\n", SDL_GetError()); SDL_Quit(); exit(1); } atexit(TTF_Quit); Con 'TTF_Quit()' logramos el resultado contrario. Para poder escribir algo en pantalla, primero debemos abrir una fuente que tengamos guardada en un directorio e indicar que tama~o de letra deseamos utilizar. La funcion que viene a continuacion empaqueta estas acciones: TTF_Font *fuente; if ((fuente = TTF_OpenFont("FreeMono.ttf", 20)) == NULL){ fprintf(stderr, "\nNo se puede abrir la fuente\n"); exit(-1); } Como puedes ver, primero declaramos una variable especial que podremos utilizar posteriormente en las funciones de escritura. Ahora toca definir el color. Seguimos el mismo procedimiento, declaramos una variable y le asignamos los atributos: SDL_Color color; color.r=0; /* Rojo */ color.g=255; /* Verde */ color.b=0; /* Azul */ Una de las funciones de escritura requiere otro segundo color que se utilizara como fondo del texto. No repetiremos el proceso pues se crea de la misma forma. Bien, las funciones principales para escribir en pantalla son 3: 1. TTF_RenderText_Solid(TTF_Font *font, const char *text, SDL_Color fg); 2. TTF_RenderText_Shaded(TTF_Font *font, const char *text, SDL_Color fg, SDL_color bg); 3.TTF_RenderText_Blended(TTF_Font *font, const char *text, SDL_Color fg); Te preguntaras que las diferencia. Pues ademas de los ultimos argumentos que son obvios, difieren tanto en rendimiento como en calidad. La primera es la que ofrece una calidad mas baja, no obstante precisa de un tiempo menor para ejecutar su cometido. La tercera es todo lo contrario. Nosotros siempre utilizaremos la ultima, pues la definicion es perfecta y en un PC normal no percibiras una merma de rendimiento visible. Estas funciones no escriben directamente en pantalla, sino que devuelven una superficie que sera la que posteriormente se imprimira sobre la pantalla en la region que nosotros deseemos. Un uso habitual seria este: **-------------------------------------------------------------------------** SDL_Surface *texto; SDL_Rect region = (SDL_Rect) {300, 300, 0, 0}; texto = TTF_RenderText_Blended(fuente, "HACK THE WORLD", color); SDL_BlitSurface(texto, NULL, pantalla, ®ion); SDL_FreeSurface(texto); **-------------------------------------------------------------------------** La ultima llamada, como habras adivinado, libera la superficie y por lo tanto la memoria que esta siendo utilizada. Es una forma de trabajar bastante eficiente. Con esto deberia ser suficiente para que puedas poner texto aleatorio sobre la pantalla. No obstante, aqui tienes otras funciones que pueden ser de utilidad: - TTF_OpenFontIndex() -> Igual a TTF_OpenFont(), pero ademas recibe un 3er parametro que es un indice para ficheros tff que contengan varios tipos de letra. - TTF_SetFontStyle(TTF_Font *, int) y TTf_GetFontStyle(TTF_Font *). La primera necesita una constante como segundo parametro que puede ser una de estas: TTF_STYLE_NORMAL -> Normal TTF_STYLE_BOLD -> Negrita TTF_STYLE_ITALIC -> Cursiva TTF_STYLE_UNDERLINE -> Subrayado Puedes investigar mas sobre esta libreria leyendo el contenido de su cabecera. ---[ 3.5 - Varios Quiero dejar claro que esta seccion "no es prescindible", aqui se explicaran algunos aspectos que normalmente no entran dentro de las funciones mas basicas de SDL, pero que pueden proporcionar ciertas habilidades a tu aplicacion que la convertira en mucho mas eficiente. ---[ 3.5.1 - Envoltorios El nombre de esta peque~a seccion puede parecer un poco raro; pero en realidad es la mejor forma de definir a las funciones que SDL ha creado para poder llamar a los metodos normalmente definidos en tu libreria habitual: . La cuestion es que en el archivo de cabecera podemos encontrar metodos como: SDL_calloc() SDL_free() SDL_putenv() SDL_memset() ... y asi con muchas otras. Pero en realidad SDL hace siempre una comprobacion de este tipo: #ifdef HAVE_PUTENV #define SDL_putenv putenv #else lo que viene a decir que siempre que la funcion exista ya en nuestro sistema operativo, sera utilizada aunque usemos el nombre proporcionado por SDL. ---[ 3.5.2 - SDL_strlcpy & SDL_strlcat Esto es interesante, muy interesante. Ya todos somos conocedores de los problemas que implica el uso de funciones tales como: 'strcpy()' y 'strcat()'. En multiples ocasiones pueden llevar a situaciones de desbordamiento de buffer indeseables y peligrosas. Cuando has leido algo acerca de "buffer overflows" y los autores de estos articulos te han concienciado de que el uso de 'strncpy()' y 'strncat()' es la panacea y todos tus problemas han desaparecido, entonces ya se te permite darte de cabeza contra un muro. El tema es el siguiente, y muchos deberian saberlo ya. Las funciones anteriores no comprueban que el ultimo caracter de la cadena a copiar sea un \0. Lo que es mas, aunque esta lo tuviera, podria llegar a no copiarse. Me explico: Cadena Orignal: [H][O][L][A][\0] -> Longitud = 5 Si el invocador de la funcion comete un error y llama a la funcion 'strncpy() pensando que la longitud de la cadena "hola" es 4. Entonces la cadena destino quedara cortada sin terminar nunca en un caracter \0. Cadena Destino: [H][O][L][A] Esto puede dar lugar a implicaciones posteriores que conlleven a errores dificiles de detectar e incluso fallos de segmentacion. Existen 2 funciones en los sistemas operativos de la familia BSD llamadas: 'strlcpy' y 'strlcat' que siempre terminan el buffer destino con un caracter nulo aunque ello conlleve la perdida de un caracter de la cadena original. Un sacrificio que merece la pena. Como en un sistema Linux normal no encontraremos estas funciones por defecto, podemos evitar compilarlas junto con nuestro programa haciendo uso de las proporcionadas por SDL, que, al no encontrarlas, hara uso de su propia implementacion. ---[ 3.5.3 Threads (hilos) Si llevas tiempo en esto de la programacion y las palabras 'thread' y 'proceso' te son conocidas de sobra. La interpretacion de las funciones que ahora vendran no te resultaran nada complicadas. No te lleves la idea equivocada de que una aplicacion que utilice SDL debe utilizar las siguientes funciones obligatoriamente. Es mas, yo suelo utilizar directamente las que proporciona . Pero si te gusta seguir la armonia con la nomenclatura de tu aplicacion, esto es para tu uso y disfrute. Debes crear siempre un puntero a una estructura 'SDL_Thread' tal que asi: SDL_Thread *hilo; En este puntero recibiras el resultado de la siguiente funcion: - SDL_Thread * SDL_CreateThread(int (*fn)(void *), void *data) - Primer argumento -> Una funcion que recibe un argumento 'void *' - Segundo argumento -> Un valor opcional que puedes pasar a la funcion. Y aqui tienes otras: - Uint32 SDL_ThreadID(void) -> Devuelve el identificador del hilo actual. - Uint32 SDL_GetThreadID(SDL_Thread *thread) -> Devuelve el identificador del hilo especificado. - SDL_WaitThread(SDL_Thread *thread, int *status) -> Espera la finalizacion de un hilo. - SDL_KillThread(SDL_Thread *th) -> Fuerza la finalizacion de un hilo. ---[ 3.5.4 - Graficos Primitivos Puede, quizas, que en algun momento necesites dibujar alguna que otra figura basica tal como un rectangulo, un circulo, una linea o tan siquiera un misero pixel. Las funciones incluidas en el archivo de cabecera cubren mas que de sobra esta necesidad. Dare una breve rese~a acerca de la mayoria de ellas pero no mostrare sus argumentos, pues varian muy poco de una funcion a otra e inflarian esta guia de manera desproporcionada. Cuando una de ellas te interese, te esforzaras en investigar como se ejecuta correctamente. - pixelColor() -> Dibuja un pixel en una superficie. - hlineColor() -> Dibuja una linea horizontal en una superficie. - vlineColor() -> Dibuja una linea vertical en una superficie. - rectangleColor() -> Dibuja un rectangulo una superficie. - boxColor() -> Dibuja un cuadrado en una superficie. - lineColor() -> Dibuja una linea en una superficie. - circleColor() -> Dibuja un circulo en una superficie. - ellipseColor() -> Dibuja una elipse en una superifice. - pieColor() -> Dibuja ... algo relacionado con graficos de quesitos??? - trigonColor() -> Dibuja un triangulo en una superficie. - polygonColor() -> Dibuja un poligono de 'n' lados en una superficie. - bezierColor() -> Dibuja una linea curva entre dos puntos de control. - characterColor() -> Dibuja un caracter en una superficie. - stringColor() -> Dibuja una cadena de caracteres en una superficie. - gfxPrimitivesSetFont() -> Establece una fuente para las funciones previas. Y existen otras "variantes": 1 - Las que cambian el sufijo 'Color' por 'RGBA' requieren un parametro 'Alfa' que indicara el nivel de transparencia del color. 2 - Las que llevan el prefijo 'filled' que rellenan la figura o poligono con el color que se le indique. 3 - Otras que utilizan un prefijo como 'aa'. ---[ 3.6 - Efectos A continuacion detallaremos el funcionamiento de dos funciones que nos proporciona la cabecera . La primera de ellas nos servira para ampliar superficies o regiones de superficies y la segunda, como indica tambien su nombre, para rotarlas el angulo que le especifiquemos. A~ade esto a las opciones de compilacion de tu programa: "-lSDL_gfx". ---[ 3.6.1 - Zoom Como ya se ha dicho, esta funcion que se define como: - SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth); 1 - Provoca el zoom de la superficie pasada com 1er argumento. 2 - Tanto como se indique en el 2do y 3er argumento (factores de escalado). 3 - El ultimo parametro suele ser 0. Si es '1' utiliza anti-aliasing. 4 - Devuelve una nueva superficie ya ampliada. Podriamos utilizar una funcion bastante general como la que mostrare a continuacion: **-------------------------------------------------------------------------** void do_zoom(SDL_Surface *sup, int sx, int sy, int x, int y) { SDL_Surface *szoom; SDL_Rect pos; szoom = zoomSurface(sup, sx, sy, 0); pos = (SDL_Rect){x, y, 0, 0}; SDL_BlitSurface(szoom, NULL, pantalla, &pos); SDL_Flip(pantalla); SDL_FreeSurface(szoom); } **-------------------------------------------------------------------------** A esta funcion le indicamos la superficie que queremos ampliar, los factores de escalado (un valor de 2 doblara el tama~o de la imagen) y las nuevas coordenadas donde situar esta nueva superficie ampliada. Imaginese ahora que usted tiene la imagen de un mapa y que quiere que se produzca la ampliacion de este. Si usted utiliza la funcion anterior, tendria que indicarle que la nueva superficie se dibuje sobre las mismas coordenadas en que esta la anterior, pero hay un error. Estas copiando completamente la nueva superficie y esta es el doble de ancho y de alto. Se saldra de la region esperada y podria solaparse encima de otros elementos que haya dibujado en su aplicacion. La solucion es definir un nuevo 'SDL_Rect' que defina una region igual al tama~o completo de la imagen original. De esta forma la superficie ampliada quedara recortada y ocupara el mismo espacio que la anterior. Pero aun no esta todo dicho. La solucion anterior no es muy practica, pues siempre estariamos ampliando la misma region de la imagen (esquina superior izquierda). Lo habitual es ampliar una zona circundante a aquel lugar donde nosotros hemos "clickado". Entonces ahora debemos comprender un nuevo aspecto. Si nosotros hemos hecho "click" en la posicion (x, y), el mismo punto en la superficie ampliada deberia ser mas o menos (x*2, y*2). Pero ese no debe ser el lugar donde empiece la imagen ampliada, pues hemos dicho que queremos una region circundante, por lo tanto tambien tenemos que mostrar una porcion del mapa anterior a esas coordenadas. Lo logico, entonces, seria utilizar unos puntos de inicio parecidos a estos ((x*2) - 100, (y*2) - 100). El ancho y el alto de esta nueva superficie dependera, como siempre, del tama~o de la imagen original. Un ejemplo practico, pero particular y personal, podria ser el siguiente: **-------------------------------------------------------------------------** void do_zoom(SDL_Surface *sup) { SDL_Surface *szoom; SDL_Rect pos, region; szoom = zoomSurface(sup, 2, 2, 0); region = (SDL_Rect){(x*2)-50, (y*2)-100, 100, 100}; pos = (SDL_Rect){350, 200, 0, 0}; SDL_BlitSurface(szoom, NULL, pantalla, &pos); SDL_Flip(pantalla); SDL_FreeSurface(szoom); } **-------------------------------------------------------------------------** La mejor forma de comprender como funciona esto, es que elabores tus propios ejemplos y los adaptes a tus necesidades y a las necesidades de tus imagenes. ---[ 3.6.2 - Rotaciones Bien. Para esta seccion tenemos dos funciones muy similares a la que se acaba de presentar hace un momento. Aqui las tenemos: - SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth); - SDL_Surface *rotozoomSurfaceXY(SDL_Surface * src, double angle, double zoomx, double zoomy, int smooth); La diferencia entre las dos es bastante evidente. En la primera, el parametro 'zoom' es comun y se aplica proporcionalmente tanto al ancho como al alto de la imagen a rotar. Un valor de 1 dejara la imagen en su tama~o actual. En la segunda, como puedes observar, los factores de escalado son individuales. En principio la imagen rotara en sentido contrario a las agujas del reloj. Lo que quiere decir que un valor de 90 para el argumento 'angle' rotara la imagen hacia la derecha. IMPORTANTE: No te olvides de poner un '1' o '1.0' al valor de 'zoom'. Un valor de 0 haria invisible tu nueva superficie rotada. Algo mas hay que tener en cuenta. Y es que si vuelves a dibujar siempre la imagen rotada en las mismas coordenadas que la original, la imagen estara girando continuamente sobre una esquina. Seguramente no sea este el efecto que deseamos. El siguiente codigo mostrar una funcion generica para rotar imagenes y como corregir la posicion en cada iteracion: **-------------------------------------------------------------------------** void do_rotozoom(SDL_Surface *sup, int x, int y, float zoomx, float zoomy, float angle) { SDL_Surface *szoom; SDL_Rect pos; pos = {x, y, 0, 0}; szoom = rotozoomSurfaceXY(src, angle, zoomx, zoomy, 0); /* Rotar siempre la imagen con respecto al centro */ pos.x -= (szoom->w - sup->w) / 2; pos.y -= (szoom->h - sup->h) / 2; SDL_BlitSurface(szoom, NULL, screen, &pos); SDL_Flip(pantalla); SDL_FreeSurface(szoom); } **-------------------------------------------------------------------------** Si la imagen a rotar no es un cuadrado perfecto, recuerda siempre borrar esta poniendo un rectangulo negro encima, asi no quedara solapada con la nueva pudiendose ver regiones de la anterior. ---[ 4 - Consola Personal Con lo aprendido hasta este momento, nos atreveremos a crear la representacion de una consola o shell, bastante arcaica pero funcional. ¿Que utilidad tiene una consola si lo que queremos es precisamente crear una GUI para hacer las cosas mas faciles? Muy facil, imagina que estas creando una interfaz para una suite de hacking; seria interesante que una ventana de la pantalla fuera una consola para poder ejecutar comandos directamente en el sistema y capturar su salida para imprimirla en nuestro programa sin tener que minimizarlo y abrir una nueva shell (menos aun cuando este se ejecuta a pantalla completa). Todavia mas. Crear la representacion de cuadros de texto (algo que seguramente ya te habras preguntado) es mas que una odisea en una aplicacion con SDL. Te invito a que lo intentes y me muestres tus resultados (si lo hicieras te recomendaria C++, donde puedes convertir realmente cada elemento de la GUI en un objeto). Es por ello y mucho mas que necesitas algun modo de proporcionar informacion a tu aplicacion, ya sean direcciones IP, MAC, registros de agenda, cuentas bancarias o aquello que tu programa quiera consumir. Si las razones no te convencen, entonces lee esto igual, aprenderas un poco mas de como se comportan los graficos e incluso quizas recibas algun que otro consejo. puede proporcionarte por si misma varias de las caracteristicas que se presentaran a continuacion; pero nosotros somos artesanos del codigo y vamos a hacer algo propio por una vez. ---[ 4.1 - Representacion Antes de nada necesitamos una estructura que contenga las coordenadas de nuestro supuesto cursor (de momento sera invisible). La estructura podria ser la siguiente: struct position{ int x; /* columnas */ int y; /* lineas */ } post; Definiremos tambien 2 variables "globales", una que contendra el comando que estamos escribiendo hasta un maximo de 64 caracteres y otra que llevara el contador de cuantos se han escrito hasta el momento: int ncar = 0; // Contador de caracteres char comando[64]; // Nos cuidaremos bien de los desbordamientos Ahora abriremos una fuente y definiremos su color: TTF_Font *fuente; SDL_Color fgcolor; if ((fuente = TTF_OpenFont("FreeMono.ttf", 20)) == NULL){ fprintf(stderr, "\nNo se puede abrir la fuente\n"); exit(-1); } fgcolor.r=0; fgcolor.g=255; // Verde fgcolor.b=0; Vale, ¿que es una consola visualmente? Pues diriamos de forma vulgar que un simple rectangulo negro. Exacto, eso es lo que vamos a crear, pero un rectangulo en medio de la pantalla quedaria bastante feo. Seria mas agradable cargar la imagen de una ventana como se ha mostrado en secciones anteriores y dibujar sobre esta la consola; lo que es mas, si el interior de esa ventana es totalmente negro, ni siquiera haria falta este ultimo paso. Imaginemos entonces que la ventana se situa en las coordenadas (0, 385) y que su tama~o en pixeles es de (z) por (y). Podemos crear un rectangulo negro dentro de esta ventana de modo que el marco quede bien ajustado: /* Asumire que "pantalla" es la 'SDL_Surface *' principal */ rect = (SDL_Rect) {11, 420, 688, 328}; SDL_FillRect(pantalla, &rect, SDL_MapRGB(pantalla->format,0,0,0)); Se observa que empezamos varios pixeles mas adentro para no "montarnos" encima de los marcos de la ventana. Que mas necesitamos? El Prompt, si, eso es importante, lo escribiremos con: escribir('#', 0); // Esta funcion se explica en el siguiente apartado // el 0 en el segundo argumento indica que no forma // parte de un comando, es un caracter de control. ---[ 4.2 - Escritura y borrado En esta primera funcion, controlaremos varias cosas. 1 - Si el caracter pertenece a un comando (*). 2 - Si el caracter es un tabulador (\t). 3 - Si el caracter es una nueva linea (\n). 4 - Si estamos al final de una linea, escribiremos en la siguiente. 5 - Si alcanzamos la ultima linea, realizar scroll(). (*) Los caracteres se escribiran en la consola de forma indefinida, pero solo seran almacenados como parte del comando hasta alcanzar la longtiud de 64. **-------------------------------------------------------------------------** void escribir(char car, int is_cmd) { int tab = 0; int nline = 0; char line[2]; snprintf(line, 2, "%c\0", car); // Si aun no se han superado los 64 caracteres y forma // parte de un comando, lo copiamos al buffer. if (ncar != 0 && ncar < 64 && is_cmd == 1) { SDL_strlcat(comando, line, 64); } // Si es una nueva linea bajamos el cursor aumentando // post.y, nos vamos al principio restaurando post.x if (strncmp(line, "\n", 1) == 0) { post.y += 20; post.x = 15; nline = 1; // Boolean } // Si es un tabulador escribimos 7 espacios else if (strncmp(line, "\t", 1) == 0) { texto = TTF_RenderText_Blended(fuente, " ", fgcolor); tab = 1; // Boolean } else texto = TTF_RenderText_Blended(fuente, line, fgcolor); if(nline != 1){ if (post.x >= 688) { // Si alcanzamos la ultima columna post.x = 15; // empezamos en una linea nueva. post.y += 20; } if (post.y >= 715) { // Si alcanzamos la ultima linea scroll(); // realizamos un scroll de la consola } // Imprimimos el texto y liberamos la superficie rconsola = (SDL_Rect) {post.x, post.y, 0, 0}; SDL_BlitSurface(texto, NULL, pantalla, &rconsola); SDL_FreeSurface(texto); if (tab == 1) post.x += 12 * 7; // 12 x tamanyo tabulacion else post.x += 12; // Adelantamos el cursor un caracter } ncar += 1; // Incrementamos el contador de caracteres SDL_Flip(pantalla); // Actualizamos la pantalla } **-------------------------------------------------------------------------** Como siempre, la pregunta. Que ocurre visualmente al borrar un caracter? Que desaparece (de Perogrullo). En nuestro caso, que un peque~o rectangulo negro lo tapa. Aqui controlamos lo siguiente: 1 - Si estamos al principio de la consola, no hacemos nada. 2 - Si no hay caracteres escritos, no hacemos nada. 3 - Si estamos al principio de una linea, siempre que esta sea continuacion de otra anterior, nos movemos al final de esta para borrar el ultimo caracter. Nunca debemos olvidar anular el ultimo caracter del array 'comando[]'. **-------------------------------------------------------------------------** void borrar(void) { if (post.x >= 27) { // Cuidado de no borrar el PROMPT // Si estamos al principio de todo o en una linea en // la que no se ha escrito ningun caracter, salimos. if ((post.x == 27 && post.y == 430) || (ncar == 1)) return; // Eliminamos el ultimo caracter del buffer comando[strlen(comando)-1] = '\0'; // Movemos el cursor un caracter atras, dibujamos un cuadradito // negro para tapar el caracter a borrar y disminuimos el contador. post.x -= 12; rconsola = (SDL_Rect) {post.x, post.y, 13, 20}; SDL_FillRect(pantalla, &rconsola, SDL_MapRGB(pantalla->format,0,0,0)); ncar -= 1; } else if (post.y > 430) { // Si estamos al comienzo de una linea que es post.x = 699; // continuacion de otra anterior, saltamos al post.y -= 20; // final de esta para borrar el caracter que borrar(); // alli se encuentra. } SDL_Flip(pantalla); // Actualizamos la pantalla } **-------------------------------------------------------------------------** ---[ 4.3 - Scroll El metodo es sencillo. Solo debemos tener un poco de imaginacion e intentar comprender como funcionan los graficos en este sentido. ¿Que ocurre visualmente cuando el scroll entra en accion? 1 - La primera fila de la consola desaparece. 2 - El resto de las lineas se mueven una fila hacia arriba. 3 - Aparece una nueva linea vacia al final de la consola. ¿Como realizar estos simples pasos? Los pasos 1 y 2 se ejecutan en una misma accion, pues al copiar todo el contenido de la consola excepto la primera linea, y copiarlo una fila mas arriba, esta se pierde automaticamente. El paso 3 es evidente. Creamos un rectangulo negro que se superponga a esta ultima fila, que tras realizar el paso anterior seria igual a la penultima. El codigo lo aclara todo: **-------------------------------------------------------------------------** static void scroll(void) { SDL_Rect orig; // Copiamos toda la consola excepto la primera linea (la que se pierde) // y lo pegamos desde el origen de las coordenadas. orig = (SDL_Rect) {15, 450, 685, 285}; rconsola = (SDL_Rect) {15, 430, 685, 285}; SDL_BlitSurface(pantalla, &orig, pantalla, &rconsola); // Tapamos la ultima linea con un rectangulo negro rconsola = (SDL_Rect) {11, 715, 688, 20}; SDL_FillRect(pantalla, &rconsola, SDL_MapRGB(pantalla->format,0,0,0)); post.y -= 20; // Restauramos post.y para no salirnos de la consola post.x = 15; // Nos vamos al principio de la linea } **-------------------------------------------------------------------------** ---[ 4.4 - Ejecutar comandos Esta funcion es muy simple, aunque puede llegar a ser peligrosa si ejecutamos el programa como ROOT (por ejemplo con sudo) y no somos conscientes de que estamos ejecutando comandos con este permiso. El comando almacenado en 'comando[]' es ejecutado mediante 'popen()' y su salida es capturada para escribirla caracter a caracter directamente en nuestra consola. Los comentarios del metodo son mas que suficientes. **-------------------------------------------------------------------------** void ejecutar(void) { char c; FILE *cmd; // Nueva linea y al principio. post.y += 20; post.x = 15; cmd = popen(comando, "r"); // Ejecuta comando while((c = fgetc(cmd)) != EOF){ // Lee la salida fflush(stdout); // Evitar comportamientos extranyos escribir(c, 0); // Escribimos la salida en la consola } pclose(cmd); // Cerramos el descriptor SDL_strlcpy(comando, "\0", 1); // Vaciamos el comando ncar = 0; // Contador a 0 escribir('#', 0); // Escribir nuevo PROMPT } **-------------------------------------------------------------------------** Te voy a mostrar un ejemplo real de la funcion que yo utilizo en uno de mis programas para dar entrada a un monton de informacion que este precisa para realizar sus operaciones. Tan solo echale un vistazo y quedate con la forma de operar: **-------------------------------------------------------------------------** void ejecutar(void){ char c; char *tmp; int n, fp; int spc = 0; u_int32_t p[6]; // FILE *cmd; post.y += 20; post.x = 15; if (comando[0] == '$') { tmp = comando + 1; free(thost); thost = (struct hosts *) calloc(1, sizeof(struct hosts)); SDL_strlcpy(thost->h_addr, "\0", 1); SDL_strlcpy(thost->h_addr, tmp, 15); spc = 1; } else if (comando[0] == '@') { tmp = comando + 1; n = sscanf(tmp, "%02x:%02x:%02x:%02x:%02x:%02x", &p[0],&p[1],&p[2],&p[3],&p[4],&p[5]); if (n != 6) { asprintf(&cadena, "MAC Address is not valid\n"); salida(1); } for (n = 0 ; n < 6 ; n++) thost->trg_ha[n] = (u_int8_t)p[n]; spc = 1; } else if(comando[0] == '%') { tmp = comando + 1; asprintf(&device, "%s", tmp); close_net(); init_net(2); spc = 1; } if (strncmp(comando, "help", 4) == 0) { show_help(); spc = 1; } else if (strncmp(comando, "clear",5) == 0) { clear_scr(); spc = 1; } else if (strncmp(comando, "trace", 5) == 0) { trace_lines(); put_mapa(); spc = 1; } else if (strncmp(comando, "cleanmap", 8) == 0) { clean_gh(); carga_mapa(1); spc = 1; } else if (strncmp(comando, "exit", 4) == 0) { terminar = 1; spc = 1; } else if (strncmp(comando, "smsn", 4) == 0) { sniff_msn(); spc = 1; } else if (strncmp(comando, "surl", 4) == 0) { sniff_url(); spc = 1; } else if (strncmp(comando, "fprint", 6) == 0) { fingerprint(thost->h_addr, 2); spc = 1; } /* THIS FUNCTION IS VERY INSECURE, IT * PLAY COMMANDS AS ROOT. UNCOMMENT * THIS UNDER YOUR OWN RESPONSABILITY. if (!spc) { cmd = popen(comando, "r"); // Play command while ((c = fgetc(cmd)) != EOF) { // Read output command fflush(stdout); // Flush the buffer escribir(c, 0); // Print output to console window } pclose(cmd); // Close descriptor } */ SDL_strlcpy(comando, "\0", 1); ncar = 0; escribir('#', 0); } **-------------------------------------------------------------------------** Deja volar a tu imaginacion. ---[ 4.5 - Utiles Te presentare aqui dos funciones muy sencillitas que puedes utilizar en aquellas aplicaciones en que utilices esta consola. La primera de ellas es el conocido "clear screen" que borra todo el contenido de la consola: **-------------------------------------------------------------------------** void clear_scr(void) { SDL_Rect rscr; rscr = (SDL_Rect) {11, 420, 688, 328}; SDL_FillRect(pantalla, &rscr, SDL_MapRGB(pantalla->format,0,0,0)); post.x = 15; post.y = 430; } **-------------------------------------------------------------------------** Menuda tonteria, diras. Y estas en lo cierto. Un rectangulo negro que tapa todo el contenido de la consola y un reinicio de coordenadas. Pensaras que esta funcion no te vuelve a proporcionar un 'PROMPT', cierto tambien; pero fijate, si la utilizas dentro de la funcion 'ejecutar()' activandola cuando 'strncmp(comando, "clear", 5) == 0' entonces ya tenemos cubierta nuestra necesidad. Imaginate ahora. Has programado un escaner de puertos que ejecutas haciendo click en uno de los botones del menu y te gustaria que su resultado se imprimiera por consola. Entonces puedes utilizar esta llamada: **-------------------------------------------------------------------------** char *cadena; void salida(int is_end) { while (*cadena) { escribir(*cadena++, 0); } SDL_strlcpy(cadena, "\0", 1); SDL_strlcpy(comando, "\0", 1); if (is_end) { ncar = 0; escribir('#', 0); } } **-------------------------------------------------------------------------** Cada vez que quieras imprimir una frase en la consola, puedes escribir en tu codigo esto: asprintf(&cadena, "Hack The World by %s\n", "blackngel"); salida(1); Queda a tu eleccion aqui volver a situar un prompt o dejar la linea preparada para imprimir a continuacion en otro momento. ---[ 5 - Conclusion El objetivo de este articulo, que bien pudiera haberse mencionado al principio, era que pudieses crear tus propias interfaces graficas sin necesidad de vertelas con un entorno de desarrollo visual cuya potencia puede a veces desbordar tus capacidades. Crear aplicaciones con el aspecto de un juego puede llegar a ser realmente estimulante. Es un mundo entero abierto a la imaginacion donde puedes elaborar desde interfaces con aspecto futurista y de ciencia-ficcion hasta lo mas exoterico que a ti se te pueda ocurrir. Para terminar, un lugar donde encontraras todo o casi todo lo que necesitas para programar con SDL. Podras leer una rese~a acerca de lo que el futuro le reserva a SDL. Lo encontraras aqui [4]. Podeis contactar conmigo en , los insultos seran bien recibidos siempre que sean lo bastante originales. Open your eyes, open your mind. ---[ 6 - Referencias [1] SDL http://www.libsdl.org [2] Programacion con SDL http://www.linux-magazine.es/issue/01/programacionSDL.pdf [3] Programacion de videojuegos con SDL http://www.agserrano.com/libros/sdl/%5Bebook%5DProgramacion%20de%20 videojuegos%20con%20SDL.pdf [4] Tutorial de libSDL para la programacion de videojuegos http://softwarelibre.uca.es/tutorialSDL/TutorialSDL-30012008.pdf *EOF*