miércoles, 5 de noviembre de 2014

HaxeUI: Reproductor Mp3 (3ra Parte)

En la entrada anterior vimos como agregar controles y darles forma. 

Como el reproductor hace uso de los archivos de música que estén alojados en diferentes sitios de Internet, sería bueno que tenga una lista de reproducción (direcciones web) para que no tengamos que volver a tipear la dirección si quisiésemos volver a escucharlo.

Agreguemos un control lista que nos permita una vez cargado seleccionar el archivo.



El control o componente (que a su vez es un contenedor) se llama ListView. Lo importamos a nuestro proyecto primero:

import haxe.ui.toolkit.containers.ListView;

Luego lo creamos y damos forma:


Como ya se habrán dado cuenta, muchas de las propiedades de los controles son similares para todos por igual. Sobretodo en lo que a propiedades estéticas se refiere.
No olviden agregar el método addchild(lista) al final.

Entonces; Dijimos que cada vez que el usuario ingresa una dirección de Internet y presione el botón de cargar, esa dirección se agregue a la lista de canciones.




Agreguemos algo de código para que ello suceda: 
Como el evento que deseamos controlar pertenece a un objeto generado por HaxeUI (un botón), es aconsejable que utilicemos aquellos que fueron proporcionados por el creador de la librería.

Nuevo import: import haxe.ui.toolkit.events.UIEvent;

Nos posicionamos por debajo del último control (objeto) creado y vamos tipear la llamada a  dos funciones:


El código, para los programadores con algo de experiencia, se verá muy familiar. 

En la línea 105, puede observarse como se agrega el método para interceptar el evento. En el caso particular, tenemos que:

1) Agregamos con el método addEventListener() a la "pila de eventos a monitorear".
2) Qué agregamos? el evento click del objeto carga (botón).
3) Que va a hacer el programa? Va a ejecutar el conjunto de instrucciones que se encuentre dentro de la función.

Que en este caso será:

1) Agregar al objeto lista, un nuevo ítem con el texto escrito en el cuadro (línea 106).
2) Vaciar el contenido de texto del cuadro (Línea 107).
3) desactivar el botón (hasta que algún otro evento lo reactive) (Línea 108).

En la línea 112 tenemos algo similar, pero en este caso el método no es un click del mouse, sino un cambio (CHANGE) en una de sus propiedades (el texto). O sea que, cuando el texto del control direccion cambie, se active el evento.
Que no hace otra cosa que volver a activar al botón para poder ingresar el nuevo valor a la lista (Línea 113).

Lo que estamos haciendo con esto es no permitirle al usuario crear una entrada "en blanco" o vacía dentro de la lista. Ello produciría un error en el programa más adelante.

Dejemos el manejo de eventos por un momento y sigamos agregando controles a la interfaz:

Pura estética, pero sirve de ejemplo. Vamos a agregar una línea divisoria, una barra de progreso y un par de etiquetas.

Dos nuevos import:

import haxe.ui.toolkit.controls.HProgress;

import haxe.ui.toolkit.controls.Text;

Y los creamos:


Las leyendas son simples etiquetas de texto que no pueden editarse en tiempo de ejecución por parte del usuario. Esa es la diferencia entre el objeto Texto y el objeto TextInput.

Luego tenemos a Hprogress que va a proporcionar una forma gráfica de ver como se completa la descarga del archivo.

Las propiedades min y max hacen referencia a los valores que va a tener el progreso como mínimo y máximo en este caso, que vamos a expresarlo en porcentual (de 0 a 100%) y a su vez, la etiqueta "leyenda_2" nos irá mostrando su valor numérico.

Por último, y para no complicar las cosas, en vez de dibujar una línea con algún método de OpenFL, aproveché que tenemos ya declarado el objeto Box, para crear una "falsa línea" (divisor_1), que no es otra cosa que un recuadro pintado de negro.

Al probar el programa, vemos que el cajón ya nos quedó chico o que la lista debería ser mas ancha..depende de ustedes darle forma a los objetos.



Además, sería bueno que el botón de carga al iniciar el programa ya se encuentre inactivo para evitar entradas en falso a la lista de canciones.

Por lo que hemos hecho hasta ahora, la lista va a mostrar direcciones de internet hacia donde apunta el archivo a reproducir. Quizás sea buena idea con un poco de código que muestre solo el nombre del archivo y no toda la ruta de acceso que se dificulta para leer.

El programa debería de ser capaz al menos de controlar lo que el usuario trata de ingresar para que no se produzcan errores.

Sería muy largo de explicar para un ejemplo tan sencillo como el del reproductor que estamos haciendo de como validar una dirección de internet (url), pero al menos podemos con un poco de código, manipular la cadena (direccion.text) y decidir si es valida o no.

Veamos: El programa va a necesitar una dirección directa que apunte a un archivo mp3. (Por ejemplo: cualquier archivo listado en este sitio http://www.stephaniequinn.com/samples.htm ).

Entonces, el usuario ingresa en nuestro cuadro de texto la dirección http://www.stephaniequinn.com/Music/Allegro%20from%20Duet%20in%20C%20Major.mp3

Podríamos, por ejemplo, revisar que el texto ingresado por el usuario debe terminar con la extensión correcta (en este caso .mp3).

Tenemos que de alguna forma verificar que eso sea verdad. Si no lo es, le avise al usuario del error y no ingresarlo a la lista.

Obviamente el programa verificará lo que está guardado en direccion.text cuando el usuario presione el botón cargar. Entonces volvemos al evento y escribimos:


Líneas 105-106, Declaro 2 variables y almaceno en una el largo total de la cadena introducida por el usuario. En la otra, haciendo uso de la función substr(), extraigo de la cadena original los últimos 4 caracteres que uso para comparar con una constante ".mp3"

Si son iguales (true), el programa agrega a la lista una versión corta de la cadena original, unos 20 caracteres del final (para que por lo menos el usuario tenga una referencia de que archivo se trata). Pueden alargar, acortar o dejar sin efecto esto dependiendo del largo del control lista..lo dejo a su criterio.

Si al comparar la constante obtenemos un Falso (los condicionales If en Haxe solamente retornan una variable lógica de comparación: Verdadero (True) o Falso (False), el programa va a mostrar una ventana emergente (Popup) señalando el error y reinicializando nuevamente los controles.





La ventana emergente va a salir siempre en el centro de la aplicación. como en el archivo application.xml hemos asignado al tamaño un valor mucho mas grande que el que por el momento usamos (800x480), se encuentra desplazada hacia abajo.



No olviden importar el objeto para su posterior uso en el código:

import haxe.ui.toolkit.core.PopupManager;

Más métodos y más controles nos esperan en la próxima. Les dejo el código de lo que vimos hasta ahora (difiere un poco ya que al escribir esta entrada estaba probando algunas cosas que veremos en el próximo capítulo, pero es perfectamente funcional.)





package ;

import flash.display.Sprite;
import flash.events.Event;
import flash.Lib;


import haxe.ui.toolkit.core.Toolkit;
import haxe.ui.toolkit.themes.GradientTheme;
import haxe.ui.toolkit.core.Root;
import haxe.ui.toolkit.containers.Box;
import haxe.ui.toolkit.controls.TextInput;
import haxe.ui.toolkit.controls.Button;
import haxe.ui.toolkit.containers.ListView;
import haxe.ui.toolkit.controls.HProgress;
import haxe.ui.toolkit.controls.Text;
import haxe.ui.toolkit.events.UIEvent;
import haxe.ui.toolkit.core.PopupManager;

/**
 * ...
 * @author gabriel
 */

class Main {
 static public function main() {
 
  var items = new Array();
  
  Toolkit.theme = new GradientTheme();  //Tema de colores usado por HaxeUI
  Toolkit.init();
  Toolkit.openFullscreen(function (root:Root)
  {

// ------------ Declaracion de Controles ---------------------
  
   var cajon:Box = new Box();
   cajon.x = 10;    //Punto de Origen x (abscisa o eje horizontal)
   cajon.y = 10;    //Punto de Origen y (origen o eje vertical)
   cajon.width = 420;   //Largo del cajon
   cajon.height = 170;      //Alto del cajón
   cajon.style.backgroundColor = 0xFFFFFF; //Color de Fondo
   cajon.style.borderSize = 3;    //Grosor en pixels del borde
   cajon.style.borderColor = 0x000000;  //Color del borde (negro)
   cajon.style.cornerRadius = 4;    //Redondeo de las esquinas
   
   var direccion:TextInput = new TextInput();
   direccion.x = 25;
   direccion.y = 25;
   direccion.width = 300;
   direccion.style.fontSize = 15;    //Tamaño de la tipografía
   direccion.text = "Agregar";    //Texto a mostrar inicialmente
   
   var carga:Button = new Button();
   carga.x = 350;
   carga.y = 25;
   carga.text = "Cargar";     //Etiqueta del boton
   
   var leyenda_1: Text = new Text();   //Simple etiqueta 1
   leyenda_1.x = 25;
   leyenda_1.y = 65;
   leyenda_1.style.fontSize = 15;
   leyenda_1.text = "Descargando:";
   
   var barra_1: HProgress = new HProgress(); //progreso de la descarga
   barra_1.x = 125;
   barra_1.y = 65;
   barra_1.width = 225;
   barra_1.min = 0;
   barra_1.max = 100;
   
   var leyenda_2: Text = new Text();   //Esta se va a modificar con codigo
   leyenda_2.x = 360;
   leyenda_2.y = 65;
   leyenda_2.style.fontSize = 15;
   leyenda_2.text = "100%";
   
   var divisor_1:Box = new Box();
   divisor_1.x = 10;
   divisor_1.y = 105;
   divisor_1.width = 420;
   divisor_1.height = 4;
   divisor_1.style.backgroundColor = 0x000000;
   
   var lista:ListView = new ListView();   //Lista de canciones
   lista.x = 25;
   lista.y = 115;
   lista.width = 150;
   lista.height = 100;
   lista.style.borderSize = 2;
   lista.style.borderColor = 0x000000;
   
   var leyenda_3: Text = new Text();
   leyenda_3.style.fontSize = 15;
   leyenda_3.x = 100;
   leyenda_3.y = 200;
   
   
   
   
   
// ----------------- Eventos -------------------------

   carga.addEventListener(UIEvent.CLICK, function(e:UIEvent) {
    var largo:Int = direccion.text.length; //Almacena el largo de la cadena
    var corte:String = direccion.text.substr(largo-4, largo - 1); //almacena los ultimos 4 caracteres
    if (corte == ".mp3") {
      items.push(direccion.text);
      lista.dataSource.add ( { text: direccion.text.substr(largo-20, largo - 1) } ); //Agrega a la lista una cadena mas corta
     direccion.text = ""; //prepara para nueva entrada
     carga.disabled = true; //desactiva el boton
    }else
     {
      PopupManager.instance.showSimple("Dirección Incorrecta", "Error", PopupButton.CANCEL);  //Popup declarando el error
      direccion.text = "";  //bora la cadena erronea
      carga.disabled = true; //desactiva el boton
     }
    
   });
   
   direccion.addEventListener(UIEvent.CHANGE, function (e:UIEvent){
   carga.disabled = false;
   });
   
   
   lista.addEventListener(UIEvent.CLICK, function (e:UIEvent) {
    leyenda_3.text = items[lista.selectedIndex];
    
   });
   
   
   
   root.addChild(cajon);
   root.addChild(direccion);
   root.addChild(carga);
   root.addChild(leyenda_1);
   root.addChild(barra_1);
   root.addChild(leyenda_2);
   root.addChild(divisor_1);
   root.addChild(lista);
//   root.addChild(leyenda_3);
   
  });
  
   
 }
 
}


Pueden descargarlo de GitHub, aunque va a ir sufriendo cambios con el tiempo.