Attention: cette page se réfère à une ancienne version de SFML. Cliquez ici pour passer à la dernière version.

Intégrer à une interface wxWidgets

Introduction

La meilleure façon d'intégrer des graphismes SFML à une interface wxWidgets est de créer un type de contrôle spécialisé. Dans ce tutoriel, nous allons définir une classe wxSFMLCanvas qui servira de classe de base pour créer vos contrôles de rendu personnalisés.

Création d'un contrôle personnalisé

De quoi avons-nous besoin pour définir un contrôle wx/SFML ? Afin de créer un contrôle wxWidgets personnalisé, nous devons dériver de la classe de base wxControl. Et, pour permettre à notre contrôle d'être une vue SFML, nous dérivons également de sf::RenderWindow.

#include <SFML/Graphics.hpp>
#include <wx/wx.h>

class wxSFMLCanvas : public wxControl, public sf::RenderWindow
{
    // ...
};

Qu'allons-nous mettre dans cette classe ? Et bien, pas grand chose au niveau de l'interface publique, étant donné que toutes les fonctions utiles seront fournies par wxControl et sf::RenderWindow. En fait, du point de vue de wxWidgets wxSFMLCanvas sera comme n'importe quel contrôle, et du point de vue de la SFML il sera considéré comme n'importe quelle autre fenêtre de rendu. C'est ça la puissance de l'héritage.

Donc, dans l'interface publique nous avons simplement besoin d'un constructeur standard, qui prendra les paramètres habituels qui définissent un contrôle wxWidgets (parent, identificateur, position, taille, style), et un destructeur qui sera vide mais qui doit être virtuel, car wxSFMLCanvas sera utilisée en tant que classe de base.

Ensuite, dans la partie privée de la classe, nous avons besoin de définir quelques fonctions qui seront rattachées à des évènements utiles. Nous devons intercepter l'évènement idle, qui est l'évènement qui est généré lorsqu'il n'y a aucun autre évènement à traiter, l'évènement paint, et l'évènement erase-background. L'évènement idle va se révéler utile pour mettre à jour notre contrôle aussi souvent que possible. L'évènement paint sera appelé à chaque fois que le contrôle nécessitera un rafraîchissement, donc nous pourrons y placer notre code de rendu SFML. Enfin, nous laisserons l'évènement erase-background vide, nous ne le définissons que pour empêcher wxWidgets de dessiner le fond du contrôle avant chaque rafraîchissement, ce qui causerait des artefacts visuels indésirables.

Nous définissons également une fonction virtuelle, afin de de donner la main à la classe dérivée juste avant de rafraîchir la vue.

Assemblons tout ceci et regardons à quoi va ressembler notre classe :

#include <SFML/Graphics.hpp>
#include <wx/wx.h>

class wxSFMLCanvas : public wxControl, public sf::RenderWindow
{
public :

    wxSFMLCanvas(wxWindow* Parent = NULL, wxWindowID Id = -1, const wxPoint& Position = wxDefaultPosition,
                 const wxSize& Size = wxDefaultSize, long Style = 0);

    virtual ~wxSFMLCanvas();

private :

    DECLARE_EVENT_TABLE()

    virtual void OnUpdate();

    void OnIdle(wxIdleEvent&);

    void OnPaint(wxPaintEvent&);

    void OnEraseBackground(wxEraseEvent&);
};

Presque toutes les fonctions seront vides, seules trois d'entre elles nécessiteront quelques explications : OnIdle, OnPaint et le constructeur. Ce dernier contiendra toute la partie spécifique-à-la-plateforme-et-difficile-à-capter, nous le laissons donc pour la fin.

Commençons par regarder la fonction OnIdle, correspondant donc à l'évènement idle :

void wxSFMLCanvas::OnIdle(wxIdleEvent&)
{
    // On génère un rafraîchissement du contrôle, afin d'assurer un framerate maximum
    Refresh();
}

C'était plutôt facile non ? La fonction Refresh est définie dans wxControl, et génère (entre autres) un évènement paint pour rafraîchir le contrôle. En l'appelant dans l'évènement idle, on s'assure d'avoir un taux de rafraîchissement maximum pour notre vue SFML.

L'évènement paint n'est guère plus compliqué :

void wxSFMLCanvas::OnPaint(wxPaintEvent&)
{
    // On prépare le contrôle à être dessiné
    wxPaintDC Dc(this);

    // On laisse la classe dérivée se mettre à jour et dessiner dans le contrôle
    OnUpdate();

    // On affiche tout ça à l'écran
    Display();
}

La première chose à faire dans la fonction de dessin est de créer un objet wxPaintDC, et lui passer un pointeur vers notre contrôle. Cela va "verrouiller" la zone graphique associée au contrôle, et assurer que nous pourrons dessiner dedans. L'oubli de cette instruction pourrait produire des résultats bizarres.

Regardons maintenant comment lier notre contrôle wxWidgets a une fenêtre de rendu SFML. Cette partie dépend fortement de l'implémentation sous-jacente de wxWidgets, donc ne vous focalisez pas trop dessus.

#ifdef __WXGTK__
    #include <gdk/gdkx.h>
    #include <gtk/gtk.h>
    #include <wx/gtk/win_gtk.h>
#endif

wxSFMLCanvas::wxSFMLCanvas(wxWindow* Parent, wxWindowID Id, const wxPoint& Position, const wxSize& Size, long Style) :
wxControl(Parent, Id, Position, Size, Style)
{
    #ifdef __WXGTK__

        // La version GTK requiert d'aller plus profondément pour trouver
        // l'identificateur X11 du contrôle
        gtk_widget_realize(m_wxwindow);
        gtk_widget_set_double_buffered(m_wxwindow, false);
        GdkWindow* Win = GTK_PIZZA(m_wxwindow)->bin_window;
        XFlush(GDK_WINDOW_XDISPLAY(Win));
        sf::RenderWindow::Create(GDK_WINDOW_XWINDOW(Win));

    #else

        // Testé sous Windows XP seulement (devrait fonctionner sous X11 et
        // les autres versions de Windows - aucune idée concernant MacOS)
        sf::RenderWindow::Create(GetHandle());

    #endif
}

Comme vous pouvez le voir, l'implémentation qui requiert le plus d'attention est celle utilisant GTK. Comme GTK n'est pas une bibliothèque bas niveau, nous devons creuser plus profond pour obtenir l'identificateur interne de la fenêtre. Nous devons également "réaliser" le widget (afin de s'assurer qu'il est effectivement créé), et désactiver le double-tampon fourni par GTK, étant donné que nous avons déjà le nôtre.
Et... oui, GTK_PIZZA est un nom très bizarre, je n'ai toujours pas saisi sa signification.

Pour les autres implémentations (WXMSW, WXX11), donner directement l'identificateur renvoyé par GetHandle sera suffisant.

Création d'une interface wxWidgets et utilisation de notre contrôle

Maintenant que nous avons un composant SFML / wxWidgets générique, créons-en une spécialisation qui va faire quelque chose d'"utile". Ici nous allons définir un contrôle qui affiche un sprite.

class MyCanvas : public wxSFMLCanvas
{
public :

    MyCanvas(wxWindow*  Parent,
             wxWindowID Id,
             wxPoint&   Position,
             wxSize&    Size,
             long       Style = 0) :
    wxSFMLCanvas(Parent, Id, Position, Size, Style)
    {
        // On charge une image et on l'affecte à notre sprite
        myImage.LoadFromFile("sprite.png");
        mySprite.SetImage(myImage);
    }

private :

    virtual void OnUpdate()
    {
        // On efface la vue
        Clear(sf::Color(0, 128, 128));

        // On affiche le sprite dans notre vue SFML
        Draw(mySprite);
    }

    sf::Image  myImage;
    sf::Sprite mySprite;
};

Puis nous créons la fenêtre principale de l'application, dans laquelle nous allons insérer une vue SFML avec notre composant spécialisé :

class MyFrame : public wxFrame
{
public :

    MyFrame() :
    wxFrame(NULL, wxID_ANY, "SFML wxWidgets", wxDefaultPosition, wxSize(800, 600))
    {
        new MyCanvas(this, wxID_ANY, wxPoint(50, 50), wxSize(700, 500));
    }
};

Et enfin, la classe de l'application :

class MyApplication : public wxApp
{
private :

    virtual bool OnInit()
    {
        // On crée la fenêtre principale
        MyFrame* MainFrame = new MyFrame;
        MainFrame->Show();

        return true;
    }
};

IMPLEMENT_APP(MyApplication);

Conclusion

Avec ce composant, vous pouvez intégrer la SFML à vos interfaces wxWidgets très facilement. N'hésitez pas à l'utiliser !
Ceci était le dernier tutoriel concernant le module graphique. Vous pouvez désormais aller créer vos propres applications graphiques, ou bien vous pouvez sauter vers une autre section et apprendre un nouveau module.