Intégrer à une interface Qt
Introduction
Dans ce tutoriel, nous allons voir comment intégrer une vue SFML à une interface Qt. La façon habituelle d'ajouter une nouvelle fonctionnalité à une interface Qt est d'écrire un widget (composant) personnalisé ; c'est ce que nous allons faire ici.
Création du composant SFML personnalisé
Afin de créer notre widget SFML perso, nous devons dériver de la classe de base QWidget
. Et comme nous
voulons que notre widget soit également une fenêtre de rendu SFML, nous héritons également de
sf::RenderWindow
.
#include <SFML/Graphics.hpp>
#include <Qt/qwidget.h>
class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
...
};
Puis, de quoi cette classe aura besoin pour fonctionner ? Premièrement, d'un constructeur standard définissant les proriétés usuelles des widgets : parent, position, taille. Nous ajoutons à cela un dernier paramètre, qui est la durée d'une frame (l'inverse du taux de rafraîchissement) ; comme Qt ne fournit pas d'évènement idle (qui serait généré à chaque fois que la file d'évènements est vide), nous devons gérer manuellement le rafraîchissement du widget. Le meilleur moyen de le faire est de démarrer un timer, et le connecter à une fonction qui va rafraîchir le widget à la fréquence spécifiée. La valeur par défaut de 0 fait en sorte que le timer génère un rafraîchissement dès qu'il n'y a aucun autre évènement à traiter, ce qui est exactement ce qu'un évènement idle ferait.
Ensuite, nous devons surdéfinir l'évènement show : ce sera un bon endroit pour initialiser notre fenêtre
SFML. Nous ne pouvons pas le faire dans le constructeur, car à ce moment le widget n'a pas encore sa position et
sa taille définitives.
Nous surdéfinissons également l'évènement paint, afin d'y rafraîchir notre vue SFML.
Nous allons aussi définir deux fonctions à l'attention des classes dérivées : OnInit()
, qui sera
appelée dès que la vue SFML sera initialisée, et OnUpdate()
, qui sera appelée avant chaque rafraîchissement
afin de laisser la classe dérivée dessiner des choses dans le widget.
#include <SFML/Graphics.hpp>
#include <Qt/qwidget.h>
#include <Qt/qtimer.h>
class QSFMLCanvas : public QWidget, public sf::RenderWindow
{
public :
QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime = 0);
virtual ~QSFMLCanvas();
private :
virtual void OnInit();
virtual void OnUpdate();
virtual QPaintEngine* paintEngine() const;
virtual void showEvent(QShowEvent*);
virtual void paintEvent(QPaintEvent*);
QTimer myTimer;
bool myInitialized;
};
Jetons maintenant un oeil à l'implémentation.
QSFMLCanvas::QSFMLCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size, unsigned int FrameTime) :
QWidget (Parent),
myInitialized (false)
{
// Mise en place de quelques options pour autoriser le rendu direct dans le widget
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_OpaquePaintEvent);
setAttribute(Qt::WA_NoSystemBackground);
// Changement de la police de focus, pour autoriser notre widget à capter les évènements clavier
setFocusPolicy(Qt::StrongFocus);
// Définition de la position et de la taille du widget
move(Position);
resize(Size);
// Préparation du timer
myTimer.setInterval(FrameTime);
}
Pour commencer, le constructeur initialise deux options pour autoriser l'affichage direct dans le widget.
WA_PaintOnScreen
informe Qt que nous n'utiliserons pas ses fonctions de dessin, et que nous
dessinerons directement dans le widget. WA_NoSystemBackground
empêche l'affichage du fond du widget,
qui causerait un scintillement désagréable et inutile.
On change également la police de focus à Qt::StrongFocus
, afin d'autoriser notre widget à recevoir
les évènements clavier.
Puis, on initialise la position et la taille du widget, et on paramètre l'intervalle du timer pour coller à
la fréquence de rafraîchissement désirée.
#ifdef Q_WS_X11
#include <Qt/qx11info_x11.h>
#include <X11/Xlib.h>
#endif
void QSFMLCanvas::showEvent(QShowEvent*)
{
if (!myInitialized)
{
// Sous X11, il faut valider les commandes qui ont été envoyées au serveur
// afin de s'assurer que SFML aura une vision à jour de la fenêtre
#ifdef Q_WS_X11
XFlush(QX11Info::display());
#endif
// On crée la fenêtre SFML avec l'identificateur du widget
Create(winId());
// On laisse la classe dérivée s'initialiser si besoin
OnInit();
// On paramètre le timer de sorte qu'il génère un rafraîchissement à la fréquence souhaitée
connect(&myTimer, SIGNAL(timeout()), this, SLOT(repaint()));
myTimer.start();
myInitialized = true;
}
}
Dans la fonction showEvent
, qui est appelée lorsque le widget est affiché, nous créons la fenêtre SFML.
Ceci se fait très simplement en appelant la fonction Create
avec l'identificateur interne de la
fenêtre, qui est donné par la fonction winId
. Sous X11 (Unix), nous devons placer un appel système pour
vider la file de messages qui seraient encore en attente, afin de s'assurer que la SFML va bien voir
notre fenêtre.
Une fois la fenêtre SFML initialisée, nous pouvons informer la classe dérivée en appelant la fonction virtuelle
OnInit
.
Enfin, on connecte le timer à la fonction repaint
, qui va rafraîchir le widget et générer un évènement
paint. Et bien entendu, on le démarre.
QPaintEngine* QSFMLCanvas::paintEngine() const
{
return 0;
}
Nous faisons en sorte que la fonction paintEngine
renvoie un moteur de rendu nul.
Cette fonction va de paire avec l'option WA_PaintOnScreen
, pour dire à Qt que nous n'utilisons aucun
de ses moteurs de rendu.
void QSFMLCanvas::paintEvent(QPaintEvent*)
{
// On laisse la classe dérivée faire sa tambouille
OnUpdate();
// On rafraîchit le widget
Display();
}
La fonction paintEvent
est assez simple : on notifie la classe dérivée qu'un rafraîchissement est
sur le point d'être effectué, et on appelle Display
pour mettre à jour notre widget avec la frame
rendue.
Utilisation de notre widget Qt-SFML
Le QSFMLCanvas
que nous venons d'écrire n'est pas utilisable directement, il doit être dérivé.
Créons donc un widget dérivé qui va dessiner quelque chose de sympa :
class MyCanvas : public QSFMLCanvas
{
public :
MyCanvas(QWidget* Parent, const QPoint& Position, const QSize& Size) :
QSFMLCanvas(Parent, Position, Size)
{
}
private :
void OnInit()
{
// On charge une image
myImage.LoadFromFile("datas/qt/sfml.png");
// On paramètre le sprite
mySprite.SetImage(myImage);
mySprite.SetCenter(mySprite.GetSize() / 2.f);
}
void OnUpdate()
{
// On efface l'écran
Clear(sf::Color(0, 128, 0));
// Une petite rotation du sprite
mySprite.Rotate(GetFrameTime() * 100.f);
// Et on l'affiche
Draw(mySprite);
}
sf::Image myImage;
sf::Sprite mySprite;
};
Rien de très compliqué ici : on surdéfinit OnInit
pour charger et initialiser nos ressources
graphiques, et OnUpdate
pour les afficher.
Nous pouvons maintenant créer une fenêtre Qt classique, et y placer une instance de notre widget perso :
int main(int argc, char **argv)
{
QApplication App(argc, argv);
// On crée la fenêtre principale
QFrame* MainFrame = new QFrame;
MainFrame->setWindowTitle("Qt SFML");
MainFrame->resize(400, 400);
MainFrame->show();
//On crée une vue SFML dans la fenêtre principale
MyCanvas* SFMLView = new MyCanvas(MainFrame, QPoint(20, 20), QSize(360, 360));
SFMLView->show();
return App.exec();
}
Conclusion
L'intégration de la SFML dans une interface Qt est simplifiée avec le widget personnalisé que nous venons d'écrire,
n'hésitez pas à l'utiliser et à l'améliorer.
Si vous voulez voir comment la SFML s'intègre à une
interface wxWidgets,
vous pouvez vous rendre au prochain tutoriel.