RadioZone

Beaucoup d'informations sont données pour le bouton PushZone qui ne sont pas toutes reprises ici. Une lecture de PushZone est une bonne idée.

RadioXXXX sont des classes de boutons radio comme on avait sur les vieux postes. Un bouton par gamme FM1, FM2, MW, SW, en appuyant sur un bouton, cela fait remonter les autres. Les commandes de volets roulants sont de ce type, ainsi que le choix d'un programme de lavage... Pour cette bibliothèque, lorsqu'on appuie dessus, le bouton s'active (ou reste actif), et un événement onSelect est généré. Lorsque l'on relâche l'appui, ou si l'appui sort de la zone d'action, il ne se passe rien. Si on appuie sur un bouton radio, cela va désactiver les autres boutons radio du même groupe.

On peut avoir plusieurs groupes indépendants. Avec cette bibliothèque, les limitations sont:
- le nombre maximum de boutons dans tous les groupes est le même et c'est une puissance de 2
- le nombre maximum de séries est une puissance de 2
- le produit des deux est 128
Par défaut, on peut avoir 4 groupes de 32 boutons. Ces nombres peuvent se changer au début du fichier PecheuxGraphXXXXX.h . Dans les premières lignes on trouve:

#define RADIO_NB_BITS_GROUPE 2 // Nombre de bits pour la définition du groupe radio 0..6
                               // 0: 1 seul groupe de 128 boutons maxi
                               // 1: 2 groupes maxi de 64 boutons maxi chacun
                               // 2: 4 groupes maxi de 32 boutons maxi
                               // 3: 8 groupes maxi de 16 boutons maxi
                               // 4: 16 groupes maxi de 8 boutons maxi
                               // 5: 32 groupes maxi de 4 boutons maxi
                               // 6: 64 paires de boutons maxi

Il suffit de changer le chiffre 2 en rouge pour ajuster les nombres à nos besoins.

On peut attribuer à chaque bouton une valeur comprise entre 0 inclus et le nombre maximum de boutons exclu (par défaut ente 0 et 31, tout excès sera tronqué). On n'est pas obligé, mais cela peut être pratique pour savoir quel bouton est sélectionné.

Un groupe est un ensemble de boutons se désactivant les uns les autres, mais ne touchant pas aux autres groupes. On peut attribuer à chaque groupe un nom GROUPE_0, GROUPE_1, GROUPE_2 ou GROUPE_3. Cela correspond aux nombres de 0 à 3, mais c'est plus lisible.

Trois fonctions vont permettre de gérer efficacement les boutons radio:
- unselectRadio() permet de désélectionner tous les boutons radios d'un groupe
- getRadioValeur() nous donne la valeur du contrôle actif du groupe
- getRadioPointeur() retourne l'adresse du contrôle actif, ce qui nous permet d'accéder à toutes ses données, par exemple sa position.

XXXXZone sont des classes de boutons sans dessin prédéfini. En principe, on affiche une image de fond qui va servir de zone visible pour les boutons de cette classe.

RadioZone est donc une classe de boutons radio sans dessin prédéfini.

 

Exemples d'utilisation

RadioZone bouton(0,0,LARGEUR,HAUTEUR); // Crée bouton sur toute la surface de l'écran
RadioZone bouton(150,100,170,120); // Crée bouton au centre en mode PAYSAGE 320x240
RadioZone bouton = new RadioZone(100,100,120,120); // Crée une instance dynamique bouton
new RadioZone(100,100,120,120); // Crée une instance dynamique bouton sans pointeur dessus

circle(bouton.demiX1+bouton.demiX2, bouton.demiY1+bouton.demiY2, 10); // Cercle centré sur le bouton

bouton.onSelectFunction = &action; // Appel la fonction void action(void) lors de l'appui
bouton.onUnselectFunction = &action; // Appel la fonction void action(void) lors du relâchement

if (bouton.isSelect())... // Si on est en train d'appuyer sur le bouton

groupe = getRadioValeur();//  groupe prend la valeur du bouton sélectionné du groupe GROUPE_0 
groupe = getRadioValeur(GROUPE_1); // groupe prend la valeur du bouton sélectionné du groupe GROUPE_1
unselectRadio(); // Désélectionne les boutons radio du groupe GROUPE_0 

 

Référence

class RadioZone
 public:
  RadioZone(int x1, int y1, int x2, int y2, char valeur = 0, char groupe = 0);
  byte demiX1, demiY1, demiX2, demiY2;
  word buttonColor;
  void (*onSelectFunction)(void);
  void (*onUnselectFunction)(void);
  RadioZone *controleSuivant;
  void select(void);
  void unselect(void);
  boolean isSelected(void);
  virtual void onSelect(void);
  virtual void onUnselect(void)
  virtual void drawButton(void);

Attributs et méthodes

RadioZone(): Constructeur, s'insère dans la liste des contrôles gérées. x1, y1, x2, y2: zone d'action. valeur: numéro du bouton dans le groupe. groupe: numéro du groupe.

demiX1, demiY1, demiX2, demiY2: Occupation de l'espace dans l'écran, coordonnés absolues DIVISEES par 2

buttonColor: Couleur du fond des boutons de la classe

*onSelectFunction: : Pointeur sur la fonction à appeler lorsque le bouton est sélectionné

*onUnselectFunction: : Pointeur sur la fonction à appeler lorsque le bouton est désélectionné

*controleSuivant: Pointeur sur le contrôle suivant dans la liste des contrôles

select(): : Active ce bouton et désactive tous les boutons du groupe.

unselect(): : Désactive ce bouton. Si il était actif, plus aucun bouton de ce groupe ne l'est.

isSelected(): : Informe de l'état du bouton.

onSelect(): : Fonction appelée lors de la sélection. On peut surcharger cette fonction, par défaut elle est vide

onUnselect(): : Fonction appelée lors de la désélection. On peut surcharger cette fonction, par défaut elle est vide

drawButton(): : Fonction appelée lors de l'activation, la désactivation, à l'initialisation de la librairie ou sur appel de la fonction drawControles(). On peut surcharger cette fonction, par défaut elle est vide pour les boutons XXXXZone, et fait ce qu'il faut pour les autres. On peut surcharger cette méthode, mais si on veut garder le dessin par défaut, il faut appeler la méthode drawButton() de la classe mère.

 

Voir aussi

- unselectRadio() Désélectionne tous les boutons radios d'un groupe
- getRadioValeur() Donne la valeur du contrôle actif du groupe
- getRadioPointeur() retourne l'adresse du contrôle actif
- scanEvent(); Moteur de la gestion des événements
- PushZone; Bouton poussoir sans dessin
- CheckZone; Bouton bistable (va vient) sans dessin
- RadioCoche; Bouton radio (un seul bouton actif parmi plusieurs) case à cocher
- RadioCircle; Bouton radio (un seul bouton actif parmi plusieurs) rond

 

Notes

Pour définir un bouton, il faut donner sa zone d'action. Pour des raisons de stockage mémoire et de précision, n'est enregistré que les nombres pairs des valeurs passées. Les nombres impairs sont arrondis au nombre pair inférieur. Ainsi la zone (100,100,201,201) est équivalente à (100,100,200,200).

Si on a plusieurs boutons dont les zones d'actions ont une partie commune, comme les boutons sont analysés dans l'ordre inverse de leur déclaration, la zone commune devient propriété du bouton défini en dernier. Cela évite les conflits. Cela permet aussi d'avoir un bouton dont la zone n'est pas rectangulaire, il peut manquer un coin.

 

Exemples

Premier exemple: 3 boutons, 6 fonctions externes

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-940-RadioZone\Exemple-940-RadioZone.ino (dans votre fichier téléchargé):

// Mise en place de 3 boutons de type RadioZone (bouton radio, pas de dessin
// dans le bouton). Quand on appuie sur un bouton, un disque blanc montre son
// activité

// Version 1:
// - Les boutons ont une définition statique
// - Utilise onSelectFunction pour exécuter l'action à faire
// - Une action différente pour chaque bouton

#include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


// Déclaration statique des instances, doit se faire en dehors du setup
// Dans ce cas les boutons sont accessibles partout
RadioZone boutonHaut(190, 90, 230, 130); // Bouton en haut
RadioZone boutonCentre(190, 140, 230, 180); // Bouton au centre
RadioZone boutonBas(190, 190, 230, 230); // Bouton en bas


// Déclaration de fonctions qui redessinent les boutons
// Ces fonctions ne doivent pas avoir de paramètres et ne doit rien retourner
// Les noms peuvent être quelconques
// On peut avoir 6 fonctions comme c'est le cas ici
// On peut aussi n'avoir qu'une fonction qui fait tout, deux fonctions,l'une
// pour dessiner, l'autre pour effacer, ou 3 fonctions, une par bouton.
// Pour savoir ce qu'il faut faire, si il y a moins de 6 fonctions, on peut
// utiliser:
// - isSelect() pour savoir si il faut dessiner ou effacer
// - getTouchY() pour savoir quel bouton vient d'être activé
// - on peut aussi tout redessiner sans chercher
// Pour effacer les cercle, on peut utiliser:
// - un fillCircle() comme cela a été fait dans d'autres programmes de démo
// - un fillRect() qui va plus vite comme ici
void onSelectActionHaut() { fillCircle(30, 110, 20, WHITE); }
void onUnselectActionHaut() { fillRect(10, 90, 50, 140, BLACK); }
void onSelectActionCentre() { fillCircle(30, 160, 20, WHITE); }
void onUnselectActionCentre() { fillRect(10, 140, 50, 190, BLACK); }
void onSelectActionBas() { fillCircle(30, 210, 20, WHITE); }
void onUnselectActionBas() { fillRect(10, 190, 50, 230, BLACK); }


void setup()
{
  setGraphMode(PAYSAGE); // Initialisation de la carte
  rect(190, 90, 230, 130,GREEN); // Carré du haut
  rect(190, 140, 230, 180); // Carré au centre de l'écran
  rect(190, 190, 230, 230); // Carré du bas
  text(F("Appuyez sur un carré"));
  
  boutonHaut.onSelectFunction=&onSelectActionHaut; // Fonction appelée si on active le bouton du haut de l'écran
  boutonHaut.onUnselectFunction=&onUnselectActionHaut; // Fonction appelée le bouton du haut de l'écran est désactivé
  boutonCentre.onSelectFunction=&onSelectActionCentre; // Fonction appelée si on active le bouton au milieu de l'écran
  boutonCentre.onUnselectFunction=&onUnselectActionCentre; // Fonction appelée si le bouton au milieu de l'écran est désactivé
  boutonBas.onSelectFunction=&onSelectActionBas; // Fonction appelée si on active le bouton en bas de l'écran
  boutonBas.onUnselectFunction=&onUnselectActionBas; // Fonction appelée si le bouton en bas de l'écran est désactivé
}


void loop() 
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

Résultat:

 

Deuxième exemple: 3 boutons RadioZone

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-941-Maison-RadioZone-Volet\Exemple-941-Maison-RadioZone-Volet.ino (dans votre fichier téléchargé):

// L'intérieur d'une maison est dessinée. Trois boutons de type Radio
// permettent d'ouvrir ou de fermer les volets roulants

#include <PecheuxGraph_ILI9341_8bits.h>

// Equivalece des constantes
const byte MONTE = 0;
const byte STOP = 2;
const byte DESCEND = 1;

// Définition des contrôles, doit se faire avant setup
Clock horloge(100); // Le volet se déplace toutes les 100ms
RadioZone boutonMonte(184, 115, 210, 127, MONTE); // Définit une zone cliquable, notre bouton monte
RadioZone boutonStop(184, 129, 210, 141, STOP); // Définit une zone cliquable, notre bouton stop
RadioZone boutonDescend(184, 143, 210, 155, DESCEND); // Définit une zone cliquable, notre bouton descend

// Variables et constantes pour le volet roulant
const word BLEU_CIEL = 0x755E; // Couleur du ciel
const byte HAUT = 55; // Le haut de la fenêtre
const byte BAS = 155; // le bas de la fenêtre
byte position = HAUT - 1; // Position du volet (plus haut que HAUT, donc complètement rentré)


// Fonction appelée tous les 100 ms pour redessiner les volets
void onTimeVolets(void)
{
  switch (getRadioValeur()) // Gestion du mouvement
  {
    case MONTE:
      if (position >= HAUT) position--; // Monte le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en haut
      {
        // Désactivation du bouton haut, choisir une des trois possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton haut
        unselectRadio(); // Désactive tous les boutons
        //static_cast <RadioZone *> (premierControle())->select(); // Active le bouton stop, cela fonctionne car il a été défini en dernier
        fill(160, 30, BLACK); // On éteint la lampe
      }
      break;
    case DESCEND:
      if (position < BAS) position++; // Descend le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en bas
      {
        // Désactivation du bouton bas, choisir une des trois possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton bas
        unselectRadio(); // Désactive tous les boutons
        // static_cast <RadioZone *> (premierControle())->select(); // Active le bouton stop, cela fonctionne car il a été défini en dernier
        fill(160, 30, YELLOW); // On allume la lampe
      }
      // case STOP: // On ne fait pas bouger le volet, rien à faire.
  }
  // Dessin du volet
  if ((getRadioValeur() == MONTE) || (getRadioValeur() == DESCEND)) // Sinon cela clignote aussi à l'arrêt
  {
    // Dessine toutes les lattes en marron
    fillRect(50, HAUT, 107, position, DARK_RED);
    fillRect(114, HAUT, 170, position, DARK_RED);
    // dessine une latte sur 16 en noir
    for (byte latte = 0; latte <= position - HAUT; latte += 16) // N° des lattes à dessiner
    { // Une latte sur 16 est noire
      hLine(50, 107, position - latte, BLACK); // Pour la vitre de gauche
      hLine(114, 170, position - latte); // Pour la vitre de droite
    }
    // Efface la latte en dessous (au cas ou on remonte le volet)
    if (position != BAS) // Seulement si on n'est pas tout en bas, sinon on dessinerait dans le cadre
    {
      hLine(50, 107, position + 1, BLEU_CIEL); // Pour la vitre de gauche
      hLine(114, 170, position + 1); // Pour la vitre de droite
    }
  }
}


void setup()
{
  horloge.onTimeFunction = &onTimeVolets; // Action à faire quand l'horloge donne son tip

  setGraphMode(PAYSAGE); // Initialisation
  setTextCursor(10, 200); // Consigne
  text(F(" Appuyez sur les boutons,\n      près de la porte"));

  maison(0, 3); // Avec 3 boutons à droite
  fillRect(50, 55, 107, 155, BLEU_CIEL); // Vitre de gauche, fond uni
  fillRect(114, 55, 170, 155); // Vitre de droite, fond uni
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

 

Troisième exemple: 6 boutons avec 2 groupes

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-942-Maison-RadioZone_Volets\Exemple-942-Maison-RadioZone_Volets.ino (dans votre fichier téléchargé):

// L'intérieur d'une maison est dessinée. Trois boutons de type Radio
// permettent de fermer le volet gauche, trois autres permettent de fermer le
// volet droit

#include <PecheuxGraph_ILI9341_8bits.h>

// Equivalece des constantes
const byte MONTE = 0;
const byte STOP = 2;
const byte DESCEND = 1;

// Définition des contrôles dynamiques, peut se faire avant ou après setup
// On peut panacher, cela n'a pas d'importance. Normalement, on ne le fait pas.
// Si c'est fait ici, c'est à titre d'exemple.

// Bouton de droite, groupe 0 (pas nécessaire de le préciser, c'est par défaut)
RadioZone *monteDroite = new RadioZone(184, 115, 210, 127, MONTE); // Définit une zone cliquable, notre bouton monte de droite
// Notez que monteDroite est un nom qui ne nous servira pas, mais on en a besoin pour la déclaration

// Boutons de gauche, groupe 1, il faut le dire
RadioZone *stopGauche = new RadioZone(7, 129, 35, 141, STOP, GROUPE_1); // Définit une zone cliquable, notre bouton stop de gauche
RadioZone *descendGauche = new RadioZone(7, 143, 35, 155, DESCEND, GROUPE_1); // Définit une zone cliquable, notre bouton descend de gauche

// On va garder l'horloge statique. Le panachage est complet.
Clock horloge(100); // Le volet se déplace toutes les 100ms



// Variables et constantes pour les volets roulants
const word BLEU_CIEL = 0x755E; // Couleur du ciel
const byte HAUT = 55; // Le haut de la fenêtre
const byte BAS = 155; // le bas de la fenêtre
byte positionDroite = HAUT - 1; // Position du volet droit (plus haut que HAUT, donc complètement rentré)
byte positionGauche = HAUT - 1; // Position du volet gauche (plus haut que HAUT, donc complètement rentré)

void onTimeVolets(void) // Fonction appelée tous les 100 ms pour redessiner les volets
{
  //------------ Volet de droite ----------------
  switch (getRadioValeur()) // Gestion du mouvement
  {
    case MONTE:
      if (positionDroite >= HAUT) positionDroite--; // Monte le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en haut
      {
        // Désactivation du bouton haut, choisir une des deux possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton activé, c'est le bouton haut
        unselectRadio(); // Désactive tous les boutons
        if (positionGauche == HAUT - 1) // si l'autre volet est aussi en haut
          fill(160, 30, BLACK); // On éteint la lampe
      }
      break;
    case DESCEND:
      if (positionDroite < BAS) positionDroite++; // Descend le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en bas
      {
        // Désactivation du bouton haut, choisir une des deux possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton activé
        unselectRadio(); // Désactive tous les boutons
        if (positionGauche == BAS) // si l'autre volet est aussi en bas
          fill(160, 30, YELLOW); // On allume la lampe
      }
  }
  // Dessin du volet
  if ((getRadioValeur() == MONTE) || (getRadioValeur() == DESCEND)) // Sinon cela clignote aussi à l'arrêt
  {
    // Dessine toutes les lattes en marron
    fillRect(114, HAUT, 170, positionDroite, DARK_RED);
    // dessine une latte sur 16 en noir
    for (byte latte = 0; latte <= positionDroite - HAUT; latte += 16) hLine(114, 170, positionDroite - latte, BLACK); // Une latte sur 16 est noire
    // Efface la latte en dessous (au cas ou on remonte le volet)
    if (positionDroite != BAS) hLine(114, 170, positionDroite + 1, BLEU_CIEL); // Seulement si on n'est pas tout en bas, sinon on dessinerait dans le cadre
  }

  //------------ Volet de gauche ----------------
  switch (getRadioValeur(GROUPE_1)) // Gestion du mouvement
  {
    case MONTE:
	  if (positionGauche >= HAUT) positionGauche--; // Monte le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en haut
      {
        // Désactivation du bouton haut, choisir une des deux possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton activé, c'est le bouton haut
        unselectRadio(GROUPE_1); // Désactive tous les boutons
        if (positionDroite == HAUT - 1) // si l'autre volet est aussi en haut
          fill(160, 30, BLACK); // On éteint la lampe
      }
      break;
    case DESCEND: 
	  if (positionGauche < BAS) positionGauche++; // Descend le volet d'un pixel si il n'est pas en haut
      else // on vient d'arriver en bas
      {
        // Désactivation du bouton haut, choisir une des deux possibilités (une seule sans commentaires)
        // getRadioPointeur()->unselect(); // Désactive le bouton activé
        unselectRadio(GROUPE_1); // Désactive tous les boutons
        if (positionDroite == BAS) // si l'autre volet est aussi en bas
          fill(160, 30, YELLOW); // On allume la lampe
      }
  }
  // Dessin du volet
  if ((getRadioValeur(GROUPE_1) == MONTE) || (getRadioValeur(GROUPE_1) == DESCEND)) // Sinon cela clignote aussi à l'arrêt
  {
    // Dessine toutes les lattes en marron
    fillRect(50, HAUT, 107, positionGauche, DARK_RED);
    // dessine une latte sur 16 en noir
    for (byte latte = 0; latte <= positionGauche - HAUT; latte += 16) hLine(50, 107, positionGauche - latte, BLACK); // Une latte sur 16 est noire
    // Efface la latte en dessous (au cas ou on remonte le volet)
    if (positionGauche != BAS) hLine(50, 107, positionGauche + 1, BLEU_CIEL); // Seulement si on n'est pas tout en bas, sinon on dessinerait dans le cadre
  }
}

void setup()
{
  setGraphMode(PAYSAGE); // Initialisation

  // Définition des contrôles dynamiques, après setup
  // Comme on n'a pas besoin de changer un comportement, on n'a pas besoin de garder une trace
  // de l'objet. On se moque de ce que retourne new

  // Boutons de droite, groupe 0 (pas nécessaire de le préciser, c'est par défaut)
  new RadioZone(184, 129, 210, 141, STOP); // Définit une zone cliquable, notre bouton stop de droite
  new RadioZone(184, 143, 210, 155, DESCEND); // Définit une zone cliquable, notre bouton descend de droite

  // Boutons de gauche, groupe 1, il faut le dire
  new RadioZone(7, 115, 35, 127, MONTE, GROUPE_1); // Définit une zone cliquable, notre bouton monte de gauche

  horloge.onTimeFunction = &onTimeVolets; // Action à faire quand l'horloge donne son tip

  // Consigne
  setTextCursor(10, 200);
  text(F("Appuyez sur les boutons,\n de chaque côté de la porte"));

  maison(3, 3); // avec 3 boutons de chaque côté
  fillRect(50, 55, 107, 155, BLEU_CIEL); // Vitre de gauche, ciel uni
  fillRect(114, 55, 170, 155); // Vitre de droite, ciel uni
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

Résultat:

 

Côté technique

En fait RadioZone ne fait que la moitié du travail, car c'est le gestionnaire scanEvent() qui vient demander les propriétés de RadioZone et qui calcule si il est pointé ou pas. il ne reste plus qu'à RadioZone de gérer la sélection ou la désélection. C'est RadioZone qui appelle appelle onSelect(), onSelectFunction, onUnselect(), onUnselectFunction, et qui demande le dessin du bouton.

 

GROUPE_0   <<     >>   RadioCoche