MVC Extra – TFT Button

Este ejercicio es un modelo especial que nos servirá para poner a prueba nuestras habilidades de programación con pantallas TFT.

En nuestras librerías existen a menudo clases de las que subestimamos su capacidad. Nos fijamos en una necesidad inmediata como es el uso de una librería para pintar sobre una pantalla TFT, pero quizás podamos extender más su uso para crear una interfaz de control.

En este caso, vamos a desarrollar el concepto de botón.

Un botón puede ser algo demasiado simple y sencillo, pero hemos de reconocer que los botones gobiernan nuestras vidas. Para encender las luces, cuando queremos bajar del bus, cada vez que escribimos una letra en nuestro teclado hasta el famoso botón rojo de autodestrucción.

Historia

Una de las librerías más conocidas para Arduino que se usan para programar pantallas TFT, es la famosa Adafruit_GFX en la que podremos aprender los comandos básicos para crear gráficos y está muy bien documentada.

Esta librería es la clase base para ejecutar los comandos básicos independientemente de la pantalla TFT que nos hayamos comprado, pero necesitaremos además una librería adicional que contenga las especificaciones del modelo de pantalla que estamos utilizando.

Para aprender más, podéis acceder a nuestro curso en el que disponéis de un conjunto de ejemplos para aprender cómo utilizar nuestras pantallas TFT.

En esta documentación nos hemos dado cuenta de que no disponemos de ese concepto de botón y navegando a través de infinitas lineas de código hemos descubierto que existe una clase denominada Adafruit_GFX_button y no solo contiene el modelo para pintar botones sobre nuestra pantalla; sino que además contiene la capacidad de detectar eventos que nos advierten de si hemos presionado el botón o dejar de pulsarlo.

  • isPressed()
  • justPressed()
  • justReleased()

Y justo encima, también podemos observar una función contains en la que si introducimos dos coordenadas nos devuelve si el botón ha sido presionado. ¿ Sabéis que quiere decirnos esto ?

Pues que si nos compramos una pantalla TFT con la capacidad táctil, podemos crear un programa que ejecute acciones en el momento de apretar nuestra pantalla con nuestros dedos. Eso sería una pasada, pero vayamos por partes…

SimpleButton

Vamos a comenzar con una serie de cuestiones que vamos a revisar para realizar nuestro ejercicio con botones. En este primer código vamos a aprender a crear 3 botones y fijándonos en los pequeños errores que nos van a aparecer, vamos a intentar definir un modelo que se ajuste a los tamaños que necesitamos.

#include <SPI.h>

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_DC 8
#define TFT_CS 9

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

Adafruit_GFX_Button topBtn;
Adafruit_GFX_Button leftBtn;
Adafruit_GFX_Button rightBtn;

uint16_t w,h;

void setup() {
  Serial.begin(9600);
  tft.begin();  // Begin ILI9341
  tft.setRotation(3);
  tft.fillScreen ( WHITE );
  
  w = tft.width() ;
  h = tft.height() ;

  leftBtn.initButton( &tft, w/2,h/4, w ,50,BLUE,  YELLOW, GREEN,"ZGZMakerSpace", 3);
  leftBtn.drawButton();
  
  leftBtn.initButton( &tft, w/3,h/2,100,50,GREEN,  YELLOW, BLACK,"ZMS", 2);
  leftBtn.drawButton();

  rightBtn.initButton( &tft, 2*w/3,h/2,50,50,RED, WHITE, BLUE,"100", 4);
  rightBtn.drawButton();
}

void loop() {
  // put your main code here, to run repeatedly:

}

Por una parte, podemos observar que nuestro título tiene un máximo de 9 letras. Por defecto no podemos poner botones con un contenido de más de 9 letras.

Por otro lado, debemos prestar atención que el texto se centra automáticamente dentro del botón y eso está muy bien, pero puesto que tenemos que definir en ancho y el alto del botón, éste tendría que ajustarse al tamaño del texto que contiene.

Es por ello, que vamos a crear una nueva clase llamada TXTButton.

IMPORTANTE: Esta nueva clase, va a ser heredada de la anterior, así que aprenderemos cómo funciona este proceso de la herencia, en la que vamos a generar un mismo modelo de botón pero con unas opciones extendidas.

TXTButton

Este código aunque pueda parecer más complicado, deberemos prestar atención en los conceptos que vamos a aprender, así que los explicaremos paso a paso para desarrollar el apartado de creación de botones de una manera dinámica.

Para empezar vamos a crear una nueva clase. Esta clase va a ser una herencia de la que hemos explicado anteriormente. Es por ello, que vamos a ir escribiendo el codigo paso a paso y desarrollando las funcionalidades que ésta contiene.

Objetivos de la nueva clase.

  • Calculo del ancho y alto del botón  (El ancho del botón viene dado en función de la longitud de su mensaje y el alto que dependerá del tamaño del texto)
  • Además de las dimensiones, podemos añadir unos márgenes laterales medidos en pixeles.
  • Debe ser capaz de leer un puntero a un array de caracteres o un String.
  • Si modificamos el botón durante la ejecución del código es necesario borrar el botón anterior.
  • Finalmente, hay que crear funciones adicionales para modificar los atributos de nuestra clase.

Para comenzar, empezaremos construyendo la nueva clase heredada con algunos constructores.

class TXTButton: public Adafruit_GFX_Button{
  public:
    uint16_t _x, _y, _w, _h;
    uint8_t _marginx, _marginy, _textsize;
    uint16_t _c, _c_txt, _c_border;
    String _str;

    TXTButton::TXTButton(void):Adafruit_GFX_Button(),_marginx(10), _marginy(5), _c( 0xFFFF ), _c_txt( 0x0000 ), _c_border( 0xFFFF ), _txtsize( 4 ) {};
    
    TXTButton( char* msg, uint16_t x, uint16_t y, uint16_t c = 0xFFFF , uint16_t txtc = 0x0000,uint16_t borderc = 0xFFFF, uint8_t textsize = 4,  uint8_t mgx = 10, uint8_t mgy = 10  )
      :Adafruit_GFX_Button( ),_str( msg ), _x( x ), _y( y ),_marginx(mgx), _marginy(mgy), _c( c ), _c_txt( txtc ), _c_border( borderc ), _txtsize( textsize )  {
      _msg = msg;
    }

    TXTButton( String msg, uint16_t x, uint16_t y,  uint16_t c = 0xFFFF , uint16_t txtc = 0x0000, uint16_t borderc = 0xFFFF, uint8_t textsize = 4,  uint8_t mgx = 10, uint8_t mgy = 5  )
      :Adafruit_GFX_Button( ),_str( msg ), _x( x ), _y( y ),_marginx(mgx), _marginy(mgy), _c( c ), _c_txt( txtc ), _c_border( borderc ), _txtsize( textsize )  {
      
    }
};

Los atributos serán la anchura, la altura del botón, los márgenes laterales en X e Y, un valor para definir el tamaño del texto, 3 atributos para la definición del color del texto, del borde y del relleno y para contener el mensaje un atributo String.

Cuando realizamos una herencia, siempre tendremos que crear el nuevo nombre de la clase seguida de la herencia separado por dos puntos.

class TXTButton: public Adafruit_GFX_Button { };

Y para ahorrar tiempo introduciendo algunos parámetros por defecto, estableceremos los atributos de la clase en el constructor de la siguiente manera.

TXTButton::TXTButton (void) : Adafruit_GFX_Button(), _marginx(10), _marginy(5),  _c( 0xFFFF ), _c_txt( 0x0000 ), _c_border( 0xFFFF ), _txtsize( 4 ) {};

Una vez hecho esto, la parte complicada hasta acertar con la sintaxis correcta para que compile, será la de añadir constructores asociados a su vez a esta clase heredada.

La parte más importante va a ser la ejecución del código cuando queramos pintar el botón en la pantalla. Para ello reaprovecharemos el código del programa anterior para definir el cálculo de ancho y alto del botón de la siguiente manera.

   void init (Adafruit_GFX& _tft ){
      
      uint16_t msg_w = _str.length()*_txtsize*5+ (_str.length()-1)*_txtsize;
      uint16_t msg_h = _txtsize*8;
      
      //Tamanio real del boton
      uint16_t btn_w = msg_w+2*_marginx;
      uint16_t btn_h = msg_h+2*_marginy;
      // Center of Button
      
      uint16_t btn_x = _x ;
      uint16_t btn_y = _y ;
      
      char buf[ 10 ];
      _str.toCharArray( buf, _str.length()+1 );
      
      initButton( &_tft ,btn_x, btn_y, btn_w, btn_h, _c_border , _c, _c_txt, buf, _txtsize);
    }

    void draw(bool inv = true){
      drawButton(inv);
    }

Una de las cuestiones más interesantes es que cada letra esta compuesto por el producto del tamaño del texto por cada 5 pixeles de ancho . Por lo que consideramos que el tamaño de la letra, habrá que multiplicarlo por este valor del atributo _textsize. De la misma manera cada letra consta de 7 pixeles de alto, pero vamos a añadir un espacio de 8 píxeles de alto para compensar aquellos símbolos que particularmente están desplazados hacia abajo.


 

 void clear(Adafruit_GFX& _tft, uint16_t  c ){
      setColor( c, c, c );
      //initButton( &_tft ,btn_x, btn_y, btn_w, btn_h, _c_border , _c, _c_txt, buf, _txtsize);
      init ( _tft );
      drawButton(true);
    }

La función clear es la manera en la borraremos el botón anterior para modificarlo nuevamente por otro. Solamente consiste en cambiar los 3 colores de relleno, texto y borde por un color que debería ser del mismo color que el fondo de la pantalla.
De esta forma podemos desarrollar el siguiente programa para desarrollar un evento en el que este botón cambia con otro mensaje y otro tamaño.

#include <SPI.h>

#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define ROSE  0xF81F
#define WHITE   0xFFFF

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_DC 8
#define TFT_CS 9

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

class TXTButton: public Adafruit_GFX_Button{
  public:
    uint16_t _x, _y, _w, _h;
    uint8_t _marginx, _marginy, _txtsize;
    uint16_t _c, _c_txt, _c_border;
    char* _msg;
    String _str;

    TXTButton::TXTButton(void):Adafruit_GFX_Button(),_marginx(10), _marginy(5), _c( 0xFFFF ), _c_txt( 0x0000 ), _c_border( 0xFFFF ), _txtsize( 4 ) {};
    
    TXTButton( char* msg, uint16_t x, uint16_t y, uint16_t c = 0xFFFF , uint16_t txtc = 0x0000,uint16_t borderc = 0xFFFF, uint8_t textsize = 4,  uint8_t mgx = 10, uint8_t mgy = 10  )
      :Adafruit_GFX_Button( ),_str( msg ), _x( x ), _y( y ),_marginx(mgx), _marginy(mgy), _c( c ), _c_txt( txtc ), _c_border( borderc ), _txtsize( textsize )  {
      _msg = msg;
    }

    TXTButton( String msg, uint16_t x, uint16_t y,  uint16_t c = 0xFFFF , uint16_t txtc = 0x0000, uint16_t borderc = 0xFFFF, uint8_t textsize = 4,  uint8_t mgx = 10, uint8_t mgy = 5  )
      :Adafruit_GFX_Button( ),_str( msg ), _x( x ), _y( y ),_marginx(mgx), _marginy(mgy), _c( c ), _c_txt( txtc ), _c_border( borderc ), _txtsize( textsize )  {
      
    }

    void init (Adafruit_GFX& _tft ){
      
      uint16_t msg_w = _str.length()*_txtsize*5+ (_str.length()-1)*_txtsize;
      uint16_t msg_h = _txtsize*8;
      
      //Tamanio real del boton
      uint16_t btn_w = msg_w+2*_marginx;
      uint16_t btn_h = msg_h+2*_marginy;
      // Center of Button
      
      uint16_t btn_x = _x ;
      uint16_t btn_y = _y ;
      
      char buf[ 10 ];
      _str.toCharArray( buf, _str.length()+1 );
      
      initButton( &_tft ,btn_x, btn_y, btn_w, btn_h, _c_border , _c, _c_txt, buf, _txtsize);
    }

    void setStr( char* msg ){
      _str = msg;
    };
    
    void setStr( String  str ){
      _str = str;
    };
    
    void setTextSize( uint16_t  size ){
      _txtsize = size;
    };
    
    void setMarginSize(  uint16_t  mx,uint16_t my ){
      _marginx =mx;
      _marginy =my;
    };

    void setXY( uint16_t  x,uint16_t y ){
      setX(x);
      setY(y);
    };

    void setX( uint16_t  x ){
      _x =x;
    };

    void setY( uint16_t  y ){
      _y =y;
    };
    
    void setColor( uint16_t  c, uint16_t ctxt, uint16_t cBorder ){
      setFillColor(c);
      setTextColor( ctxt );
      setBorderColor (cBorder);
    };
    
    void setTextColor( uint16_t  c ){
      _c_txt = c;
    };
    
    void setBorderColor( uint16_t  c ){
      _c_border = c;
    };
    void setFillColor( uint16_t  c ){
      _c = c;
    };
    
    void draw(bool inv = true){
      drawButton(inv);
    }
    
    void clear(Adafruit_GFX& _tft, uint16_t  c ){
      setColor( c, c, c );
      init ( _tft );
      drawButton(true);
    }
};

bool evt_change = true;

uint16_t w=320;
uint16_t h=240;

String title ="ZGZMakerSpace";
TXTButton topBtn( title, w/2,h/4 ,BLUE,  YELLOW, GREEN, 3);
TXTButton leftBtn ( "ZMS", w/3,h/2 ,GREEN,  YELLOW, BLACK, 2);
TXTButton rightBtn  ( "100", 2*w/3, h/2 ,BLACK, WHITE, BLUE, 4);

void setup() {
  Serial.begin(9600);
  tft.begin();  // Begin ILI9341
  tft.setRotation(3);
  tft.fillScreen( WHITE );
  w = tft.width() ;
  h = tft.height() ;

  topBtn.init( tft );
  topBtn.draw();
  
  leftBtn.init( tft );
  leftBtn.draw();

  rightBtn.init( tft );
  rightBtn.draw();
}

void loop() {
  
  if( millis() > 5000 && evt_change){
    evt_change = false;
    //Cambiar el boton derecho a su inverso
    rightBtn.draw( false );
    //Borrar el boton anterior
    topBtn.clear( tft, WHITE );

    //Modificar las propiedades del boton y dibujar el nuevo
    title ="Hi Maker!"; 
    topBtn.setStr( title );
    topBtn.setTextSize( 2 );
    topBtn.setColor( BLACK, CYAN, BLUE );
    topBtn.setMarginSize(20,20);
    topBtn.init( tft );
    topBtn.draw();
  }
  
}

Aprovechando este código crearemos un modelo de programa para crear un sistema de botones en el siguiente tutorial y dejamos un ejercicio para elaborar que ejecutaremos en un juego.

Ejercicio para programar

El ejercicio consiste en introducir una palabra a través del puerto serie, que facilmente podremos desarrollar con el modelo Serial.

A través de este puerto vamos a introducir un texto y que el programa detecte el evento para modificar este texto dentro del botón.

 

Podéis acceder a nuestro repositorio Github, con cada una de los programas que mostramos en estos tutoriales 


Para aprender más, podéis acceder a nuestro curso en el que disponéis de un conjunto de ejemplos para aprender cómo utilizar nuestras pantallas TFT.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

3 × 4 =

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.