VGUI partie 3 : Les boutons

Écrit le 03/07/2003 par Kurim
Dernière mise à jour : 30/01/2006

Créer des boutons

On peut maintenant voir comment créer des boutons. Ce que je vous conseille de faire, c'est de créer une liste de boutons, c'est-à-dire de créer qu'un bouton mais avec un index pour en avoir plusieurs. Par exemple, si vous voulez faire 6 boutons pour votre menu, dans vgui_monmenu.h, rajoutez :

CommandButton *m_pButtons[6];

Ensuite pour créer les boutons, dans vgui_monmenu.cpp, faites un For comme ceci (à la suite de la création du dernier élément ) :

for (int i = 1; i <= 6; i++) 
{ 
    m_pButtons[i] = new CommandButton( "", 0, 0, MONMENU_BUTTON_SIZE_X, MONMENU_BUTTON_SIZE_Y, true); 
    m_pButtons[i]->setParent( m_pWindow ); 
    m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); 
    char sz[32]; 
    sprintf( sz, "%d", i ); 
    m_pButtons[i]->setBoundKey( sz[0] ); 
    m_pButtons[i]->addActionSignal( new CMenuHandler_StringCommand( "give weapon_crowbar", true ) ); 
    m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) );    
}

Donc là on crée chacun des boutons un après l'autre. Le premier s'appelle m_pButtons[1], le deuxième [2], etc. on ne précise pas encore leur coordonnés, on le fera après un a un pour chaque bouton, on donne seulement la taille qui est pareil pour tous les boutons. On set tous les paramètres qui seront commun a tous les boutons, comme l'alignement du texte et le parent. On définit la touche raccourci avec setBoundKey(), ici on mets 1 pour le bouton 1, 2 pour le 2, etc. ensuite on créer l'ActionSignal, c'est ce que doit faire le bouton lorsqu'on clique dessus. Il y a 4 actions différentes qui sont déjà créer, mais vous pouvez créer vous même la votre (cf. dernière partie du tut). Ces actions sont :

CMenuHandler_LabelInput( ActionSignal *pSignal ) 
CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) 
CMenuHandler_StringCommand( char *pszCommand ) ou  ( char *pszCommand, int iClose ) 
CMenuHandler_TextWindow( int iState )

Par contre la désolé, mais LabelInput et PopupSubMenuInput() je sais pas qu'est ce que ça fais... StringCommand() ça sert à exécuter une commande console, comme si vous l'écrivez (par exemple « give weapon_crowbar » et quand le joueur appuiera sur le bouton, il recevra le pied de biche, ce qu'on a mis ici comme action pour tout les boutons). Utilisez la deuxième construction avec iClose = true si vous voulez que le bouton ferme le menu en agissant. Remarque : il existe des actions dérivées de StringCommand(), qui lancent de façon particulière la commande console, c StringCommandWatch() et StringCommandClassSelect(), vous pouvez aller voir leur code pour les comprendre mais de toute façon elle ne sont d'aucune utilité pour un nouveau menu. TextWindows() sert soit à faire disparaître le menu (mettez 0 pour iState), soit à lancer un autre menu (là mettez l'id du menu pour l'afficher)

Le InputSignal() correspond à des actions exécuté par le menu dans d'autre circonstance que le clic (passage de la souris au dessus...). Il en existe trois, mais je les expliquerai pas parce que seul MenuButtonOver() est utilisé dans le code pour les menu et je ne c pas qu'est ce que ça change exactement. Voici quand même leur nom :

CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton )// 1è param : le menu, mettez "this" - 2è param : l'index du bouton 
CHandler_ButtonHighlight( Button *pButton ) 
CHandler_CommandButtonHighlight( CommandButton *pButton )// celle-la est en fait dérivée de ButtonHighlight

Voilà, après avoir déclaré ce qui était commun a tous les boutons (remarque : le ActionSignal() peut être différent pour chaque bouton, dans ce cas au lieu de le mettre dans le for mettez-le après comme ce qui suit, un différent pour chaque bouton), on déclare les paramètres propre à chaque bouton, juste après le for. Voici un exemple dans lequel on change leur nom et leur emplacement (car ils se superposent tous vu qu'on les a créés au même endroit), d'ailleurs ça suppose que vous avez fait les defines pour :

m_pButtons[1]->setText( "Une action"); 
m_pButtons[1]->setPos( MONMENU_BUTTON1_X, MONMENU_BUTTON1_Y ); 
m_pButtons[2]->setText( "Une autre action"); 
m_pButtons[2]->setPos( MONMENU_BUTTON2_X, MONMENU_BUTTON2_Y ); 
m_pButtons[3]->setText( "Encore une autre"); 
m_pButtons[3]->setPos( MONMENU_BUTTON3_X, MONMENU_BUTTON3_Y ); 
m_pButtons[4]->setText( "Une de plus"); 
m_pButtons[4]->setPos( MONMENU_BUTTON4_X, MONMENU_BUTTON4_Y ); 
m_pButtons[5]->setText( "Une cinquieme"); 
m_pButtons[5]->setPos( MONMENU_BUTTON5_X, MONMENU_BUTTON5_Y ); 
m_pButtons[6]->setText( "Une derniere"); 
m_pButtons[6]->setPos( MONMENU_BUTTON6_X, MONMENU_BUTTON6_Y );

Si vous voulez pas être obligé de faire autant de #define et de setPos() qu'il y a de boutons, vous pouvez essayer d'utiliser une technique comme celle utilisée pour le TeamMenu (allez voir dans son fichier ), en intégrant la position dans le for en augmentant la distance qui doit changer à chaque fois. Maintenant il reste encore un détail à régler, c'est de créer une fonction qui va « associer » un bouton à un chiffre, pour faire fonctionner la touche raccourci q'on a mis avec setBoundKey(). D'ailleurs, quand je dis touche de raccourci, ça veut dire un chiffre, le chiffre qui correspond à l'index du bouton. Et il faut aussi créer la fonction qui va gérer la surbrillance. Donc déclarez ces fonctions dans vgui_MonMenu.h, à la suite des autres :

virtual bool SlotInput( int iSlot );// pour les raccourcis 
virtual void SetActiveInfo( int iInput );// pour la surbrillance

Puis dans vgui_MonMenu.cpp, à la fin du fichier, rajoutez :

bool CMonMenuPanel::SlotInput( int iSlot ) 
{ 
    if ( (iSlot < 1) || (iSlot > 7) ) // Si la touche ne correspond pas à un bind...
        return false; // On retourne false 
    if ( iSlot == 7) // si c le bouton d'annulation (cf. fin de cette partie pour faire le bind du bouton d'annule) 
    { 
        m_pCancelButton->fireActionSignal(); 
        return true; 
    } 
if (m_pButtons[iSlot]) // Si le bouton correspondant existe... 
    {    
    m_pButtons[iSlot]->fireActionSignal(); // On lance l'actionSignal 
    return true; // et on retourne true 
    } 

    return false; // si on en arrive la sans avoir rien retourner ben on retourne false.. 
} 
void CMonMenuPanel::SetActiveInfo( int iInput ) 
{ 
    m_pCancelButton->setArmed( false ); // ici... 
    for (int i = 1; i <= 6; i++) 
    { 
        m_pButtons[i]->setArmed( false ); // et là, on désactive la surbrillance de tous les boutons 
    } 

    // 7 pour le bouton d'annulation. 
    if (iInput == 7) 
    { 
        m_pCancelButton->setArmed( true ); // si c 7, on met ce bouton en surbrillance 
    } 
    else 
    { 
        m_pButtons[iInput]->setArmed( true ); // si c autre chose, on met le bouton correspondant 
    } 
}

Et n'oubliez pas de rajoutez m_pCancelButton->setBoundKey( '7' ); dans la création du bouton d'annulation !

Voilà la tout est dit dans le commentaire. On peut donc passer à la dernière partie, faire son propre ActionSignal() pour un bouton.

Créer une action pour un bouton

Bon c bien beau de faire des boutons, mais des fois, on aimerait bien que ça fasse autre chose qu'une commande console ou fermer le menu (enfin je précise que si vous voulez que votre action influe sur le serveur, il FAUT utiliser une commande console). Et pour faire ça, ben il faut tout simplement faire un nouvel ActionSignal() pour le bouton. On va le déclarer et le créer dans TeamFortressViewport.h. Cherchez :

class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand 
{ 
... 
};

Et mettez après :

class CMenuHandler_PrintText : public ActionSignal 
{ 
protected: 
    char    m_pszCommand[255]; 
    int m_iCloseVGUIMenu; 

public: 
    CMenuHandler_PrintText( char *pszCommand ) 
    { 
        sprintf( m_pszCommand, pszCommand ); 
        m_iCloseVGUIMenu = false; 
    } 

    CMenuHandler_PrintText( char *pszCommand, int iClose ) 
    { 
        sprintf( m_pszCommand, pszCommand ); 
        m_iCloseVGUIMenu = iClose; 
    } 

    virtual void actionPerformed(Panel* panel) 
    { 
        CenterPrint( m_pszCommand ); 

        if (m_iCloseVGUIMenu) 
            gViewPort->HideTopMenu(); 
        else 
            gViewPort->HideCommandMenu(); 
    } 
};

Voilà, donc les explications : ici on veut faire un actionsignal qui affiche un message qui dit au joueur quel bouton du VGUI il a enfoncé. On déclare d'abord deux variables, une pour récupérer le texte à afficher et l'autre pour savoir s'il faut fermer le menu après exécution de l'actionsignal. L'action signal doit donc pouvoir lire une chaîne de caractères, avec le paramètre *pszCommand, puis il l'affiche à l'écran avec la commande CenterPrint(). Ensuite, on déclare deux fonctions, une appelée avec qu'un paramètre qui affiche seulement le message et l'autre appelé avec deux paramètres ferme en plus le menu. Lorsqu'on affecte un actionsignal à un bouton et que le joueur clique dessus, on exécute d'abord la fonction attachée au bouton, puis après on exécute la fonction actionPerformed(), qui exécute véritablement l'action. Ici elle affiche le message récupéré dans m_pszCommand avec la fonction CenterPrint, puis, si nécessaire, elle ferme le menu. On peut donc maintenant affecter l'action a nos boutons, reprenez le fichier du menu et changez :

for (int i = 1; i <= 6; i++) 
{ 
    m_pButtons[i] = new CommandButton( "", 10, 10, MONMENU_BUTTON_SIZE_X, MONMENU_BUTTON_SIZE_Y, true); 
    m_pButtons[i]->setParent( m_pWindow ); 
    m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); 
    char sz[32]; 
    sprintf( sz, "%d", i ); 
    m_pButtons[i]->setBoundKey( sz[0] ); 
    char TxtMsg[255]; 
    sprintf( TxtMsg, "VOUS AVEZ APPUIEZ SUR LE BOUTON %d", i ); 
    m_pButtons[i]->addActionSignal( new CMenuHandler_PrintText(TxtMsg) ); 
    //    m_pButtons[i]->addActionSignal( new CMenuHandler_StringCommand( "give weapon_crowbar", true ) ); 
    m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); 
}

Voila, maintenant quand le joueur clique sur un bouton, ça lui affiche le n° du bouton qu'il a enfoncé. Si vous voulez que ça ferme le menu, vous pouvez rajouter ça :

m_pButtons[i]->addActionSignal( new CMenuHandler_PrintText(TxtMsg, true) );

Bon ça c'était qu'un exemple d'ActionSignal() créé soi même, vous pouvez en faire d'autre qui agissent sur n'importe quoi du client, vous pouvez accéder au viewport par gViewPort (gViewPort->truc_a_acceder) et au HUD par gHUD (gHUD.truc_a_acceder).

Voila c'est tout, j'ai essayé d'expliquer un maximum comment placer des éléments dans le menu, j'espère que ce que j'ai écrit c'est compréhensible et que vous arriverez à faire des beaux menus en VGUI... Si vous avez des questions, des remarques ou des trucs à corriger dans le tut, écrivez-moi.