Bienvenue, Invité. Merci de vous connecter ou de vous inscrire. Avez-vous oublié d'activer ?

Voir les contributions

Cette section vous permet de consulter les contributions (messages, sujets et fichiers joints) d'un utilisateur. Vous ne pourrez voir que les contributions des zones auxquelles vous avez accès.


Sujets - Kalith

Pages: [1]
1
Suite à la discussion qui a émergé ici, j'ouvre un nouveau topic dédié à cette question.

Je me cite moi-même... :
Citer
Voici une fonction issue de OIS qui est très utile et qui semble manquer à la SFML : obtenir une chaîne de caractère qui décrive le nom localisé d'une touche du clavier (différent de TextEntered : on peut avoir "Esc.", "Del.", "F1", les touches d'accent "^", etc.). On peut s'en servir entre autre pour afficher un écran de personnalisation des touches.

J'ai écrit un patch (en pièce jointe à ce post) qui permet d'implémenter ce comportement dans la classe sf::Keyboard. L'implémentation Windows est fonctionnelle, mais celle pour Linux est incomplète : la chaîne retournée par la fonction est toujours en anglais, et les noms sont un peu moches... Je cherche en ce moment un moyen de contourner le problème. L'implémentation pour OSX est toujours manquante, mais Hiura se proposerait éventuellement pour s'en occuper.

[attachment deleted by admin]

2
Projets SFML / lxgui - "Lua and Xml Graphical User Interface"
« le: Mai 07, 2012, 12:13:53 pm »
Bonjour,

Après avoir codé sur mon temps libre pendant 5 ans, je suis enfin prêt à vous présenter mon plus gros projet : lxgui.


Screenshot de la bibliothèque en action (exemple fourni avec le code source).

Je suis bien conscient qu'il existe déjà des tonnes de bibliothèques qui permettent de gérer un GUI, mais elles ont, je pense, chacune quelque chose qui les rend unique. C'est également le cas pour lxgui. Ses principaux avantages sont :
  • multiplateforme : la bibliothèque est codée en C++ standard (en utilisant les apports du C++11). Les concepts qui dépendent de la plateforme, comme le rendu ou l'input, sont gérés par des plugins (pour le rendu : pour le moment OpenGL pur seulement, pour l'input : SFML ou OIS).
  • extensible : mis à part les composants de base du GUI (gui::frame), toutes les classes "widget" ont étés écrites de façon à se comporter comme des plugins : gui::texture, gui::font_string, gui::button, gui::edit_box, ... L'ajout d'une nouvelle classe est relativement simple et ne nécessite pas de modifier le code source de la bibliothèque.
  • documenté : toutes les classes de la bibliothèque sont documentées. La documentation Doxygen est d’ailleurs inclue avec le code source (elle est également disponible en ligne ici).
  • lecture des données par XML et Lua : il est possible d'utiliser uniquement des fichiers XML (pour structurer le GUI) et des scripts Lua (pour la gestion des événements, etc) pour construire un GUI complet. Il est bien sûr également possible de tout faire en C++.
  • une API familière... : les API XML et Lua sont directement inspirées de celles de World of Warcraft (probablement l'un des meilleurs systèmes que j'ai vu). Il ne s'agit pas d'une simple copie, quelques différences demeurent, mais les fonctionnalités les plus importantes sont là (objets virtuels, héritage, ...).
  • caching : le GUI entier peut être sauvegardé dans une "render target" (pardon pour les anglicismes), de façon à ce que le rendu ne soit effectué que lorsque le GUI est modifié. Ainsi, des interfaces très complexes contenant beaucoup d'objets peuvent être rendues de manière très efficace (en particulier si rien n'est animé, et que le code est basé au maximum sur la réponse aux événements : à titre d'exemple, le screenshot ci-dessus est rendu à 1080 images par secondes quand le "caching" est activé).
J'ai essayé autant que possible de réduire le nombre de dépendances de la bibliothèque, de sorte que la compilation soit la plus simple possible (les fichiers de projet sont inclus pour Code::Blocks, Visual Studio 2010, et CMake). La bibliothèque de GUI en elle même dépend de Lua 5.1 (mais pas 5.2 !) à travers le wrapper C++ "luapp" que j'ai écrit (inclus également). La lecture des fichiers XML est également faite par une bibliothèque de mon cru (elle aussi inclue).
Le seul plugin de rendu actuellement disponible utilise OpenGL (et non SFML, par manque de flexibilité). Il dépend de Freetype pour charger et afficher les polices, ainsi que libpng pour charger les textures (de fait, seuls les fichiers PNG sont supportés).
Pour le plugin d'input, vous pouvez utiliser la SFML2 ou OIS. Malheureusement, puisque la SFML ne propose pas (encore) d'API d'input qui soit indépendante de l'organisation du clavier (AZERTY ou QUERTY par exemple), la classe edit_box ne fonctionnera pas correctement (entre autres) (edit : réglé dans la 1.1.0 !).

Voici une liste des différentes classes qui sont mises à disposition (comme annoncé plus haut, vous pouvez bien sûr en rajouter plein d'autres sans toucher au code de la bibliothèque !) :
  • uiobject (abstrait) : la classe de base. Peut être positionnée à l'écran, et c'est tout.
  • layered_region (abstrait) : peut être rendu à l'écran.
  • frame : peut contenir des layered_regions (triés par calques) ainsi que d'autres frames.
  • texture : peut afficher une texture, un dégradé, ou une simple couleur.
  • font_string : peut afficher du texte.
  • button : une frame sur laquelle ont peut cliquer, et qui possède trois états : "normal", "pushed" et "highligth".
  • check_button : un boutton avec une check box.
  • slider : une frame avec une texture que l'on peut déplacer verticalement ou horizontalement.
  • status_bar : une frame avec une texture qui change de taille d'après une valeur variable (usage typique : barre de santé, ...).
  • edit_box : une boîte de texte éditable (les edit_boxes à plusieurs lignes ne sont pas encore totalement supportés).
  • scroll_frame : une frame avec un contenu déroulable.
Démarrer le GUI en C++ est assez simple (code mis à jour pour la version 1.2.0) :
// On créé une fenêtre SFML
sf::Window mWindow(...);

// On créé un gestionnaire d'input (clavier, souris, ...)
utils::refptr<input::handler_impl> pSFMLHandler(new input::sfml_handler(mWindow));

// On initialise la classe principale gui::manager
gui::manager mManager(
    // On lui donne le gestionnaire d'input
    pSFMLHandler,
    // La langue qui sera utilisée par l'interface
    // (purement informatif : c'est aux addons de se traduire eux-même
    // en utilisant cette valeur)
    "frFR",
    // Les dimensions de la fenêtre de rendu
    mWindow.getSize().x, mWindow.getSize().y,
    // L'implémentation OpenGL du rendu
    utils::refptr<gui::manager_impl>(new gui::gl::manager())
);

// On lit ensuite les fichiers XML et Lua :
//  - d'abord en précisant le dossier dans lequel se trouve le GUI
mManager.add_addon_directory("interface");
//  - puis on créé un contexte Lua
mManager.create_lua([&mManager](){
    // Ce code peut être appelé plus tard, par exemple quand l'utilisateur
    // demande à recharger le GUI (le contexte lua est alors détruit, puis re-créé).
    //  - on spécifie les classes que l'on veut utiliser
    mManager.register_region_type<gui::texture>();
    mManager.register_region_type<gui::font_string>();
    mManager.register_frame_type<gui::button>();
    mManager.register_frame_type<gui::slider>();
    mManager.register_frame_type<gui::edit_box>();
    mManager.register_frame_type<gui::scroll_frame>();
    mManager.register_frame_type<gui::status_bar>();
    //  - on référence dans Lua des fonctions C++ si nécessaire
    // ...
});

//  - puis enfin on charge le tout.
mManager.read_files();

// Dans la boucle principale
while (bRunning)
{
    // On récupère les événements de la fenêtre
    sf::Event mEvent;
    while (mWindow.pollEvent(mEvent))
    {
        // ...

        // On transmet ces événements au gestionnaire d'input
        pSFMLHandler->on_sfml_event(mEvent);
    }

    // On met à jour le GUI
    mManager.update(fDeltaTime);

    // Puis on l'affiche à l'écran
    mManager.render_ui();
}

Avec ces quelques lignes de code, il est possible de créer autant d'addons d'interface en XML et Lua que l'on veut. Considérons un exemple très simple : on veut afficher un compteur qui donne le nombre d'image par seconde en bas à droite de l'écran.
On créé d'abord un nouvel addon, en allant dans le dossier "interface", puis en créant un nouveau dossier que l'on appelle "FPSCounter". Dans ce dossier, on créé un nouveau fichier "table des matières" qui liste tous les fichiers .xml et .lua dont a besoin l'addon, avec quelques autres informations (auteur, version, variables à sauvegarder, ...). Il doit porter le même nom que le dossier, et avoir l'extension ".toc", donc "FPSCounter.toc" :
## Interface: 0001
## Title: Un joli compteur d'IPS
## Version: 1.0
## Author: Kalith
## SavedVariables:

addon.xml

Comme vous le voyez, nous n'allons avoir besoin que d'un seul fichier .xml : "addon.xml". On le créé alors, dans le même dossier. Tout fichier XML doit contenir la balise <Ui> :
<Ui>
</Ui>

Dans cette balise, on créé une frame (qui est donc un genre de conteneur) :
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
    </Frame>
Cela créé une frame nommée "FPSCounter" et qui rempli tout l'écran : la balise <Size> lui donne une taille relative de "1.0" (relative à son parent, mais comme ici elle n'en a pas, ce sera relatif à la taille de l'écran), et la balise <Anchor> la positionne au milieu de l'écran.
Maintenant, à l'intérieur de cette Frame, on créé un objet FontString pour afficher le texte :
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
        <Layers><Layer>
            <FontString name="$parentText" font="interface/fonts/main.ttf" text="" fontHeight="12" justifyH="RIGHT" justifyV="BOTTOM" outline="NORMAL">
                <Anchors>
                    <Anchor point="BOTTOMRIGHT">
                        <Offset>
                            <AbsDimension x="-5" y="-5"/>
                        </Offset>
                    </Anchor>
                </Anchors>
                <Color r="0" g="1" b="0"/>
            </FontString>
        </Layer></Layers>
    </Frame>
Nous avons nommé cet objet "$parentText" : "$parent" est alors automatiquement remplacé par le nom de son parent, donc le nom final est "FPSCounterText". Intuitivement, l'attribut "font" décrit quelle police utiliser pour le rendu (fichier .ttf ou .otf), "fontHeight" la taille de la police, "justifyH" et "justifyV" donnent l'alignement horizontal et vertical, et "outline" créé une bordure noire autour du texte, de façon à ce qu'il soit lisible quelque soit l'arrière plan. On le positionne ensuite au coin en bas à droite (BOTTOM-RIGHT) de son parent, avec un petit décalage, et on lui donne une couleur verte.

Maintenant que la structure du GUI est en place, il nous faut toujours calculer et afficher le nombre d'image par seconde. Pour ce faire, on va définir deux "scripts" pour "FPSCounter" :
        <Scripts>
            <OnLoad>
                -- C'est du code Lua !
                self.update_time = 0.5;
                self.timer = 1.0;
                self.frames = 0;
            </OnLoad>
            <OnUpdate>
                -- C'est du code Lua !
                self.timer = self.timer + arg1;
                self.frames = self.frames + 1;

                if (self.timer > self.update_time) then
                    local fps = self.frames/self.timer;
                    self.Text:set_text("FPS : "..fps);
               
                    self.timer = 0.0;
                    self.frames = 0;
                end
            </OnUpdate>
        </Scripts>

Le script "OnLoad" est exécuté une fois et une seule, quand la Frame est créée. On l'utilise ici pour initialiser plusieurs variables. Le script "OnUpdate" est appelé à chaque image du rendu (à utiliser avec précaution, donc ...). Il fournit le temps écoulé depuis le dernier appel dans la variable "arg1". On l'utilise ici pour compter le nombre de mise à jour (donc le nombre d'image) en fonction du temps, et pour mettre à jour notre compteur toute les demi secondes.
La variable "self" en Lua est l'équivalent du "this" du C++ : c'est une référence à "FPSCounter". À noter, puisque l'on a appelé le FontString "$parentText", on peut utiliser un raccourcis d'écriture assez pratique : "self.Text" (au lieu du nom complet "FPSCounterText"), pour faire référence à notre compteur.

Une fois ceci fait, on a le fichier .xml final :
<Ui>
    <Frame name="FPSCounter">
        <Size>
            <RelDimension x="1.0" y="1.0"/>
        </Size>
        <Anchors>
            <Anchor point="CENTER"/>
        </Anchors>
        <Layers><Layer>
            <FontString name="$parentText" font="interface/fonts/main.ttf" text="" fontHeight="12" justifyH="RIGHT" justifyV="BOTTOM" outline="NORMAL">
                <Anchors>
                    <Anchor point="BOTTOMRIGHT">
                        <Offset>
                            <AbsDimension x="-5" y="-5"/>
                        </Offset>
                    </Anchor>
                </Anchors>
                <Color r="0" g="1" b="0"/>
            </FontString>
        </Layer></Layers>
        <Scripts>
            <OnLoad>
                -- C'est du code Lua !
                self.update_time = 0.5;
                self.timer = 1.0;
                self.frames = 0;
            </OnLoad>
            <OnUpdate>
                -- C'est du code Lua !
                self.timer = self.timer + arg1;
                self.frames = self.frames + 1;

                if (self.timer > self.update_time) then
                    local fps = self.frames/self.timer;
                    self.Text:set_text("FPS : "..math.floor(fps));
               
                    self.timer = 0.0;
                    self.frames = 0;
                end
            </OnUpdate>
        </Scripts>
    </Frame>
</Ui>

... et un addon fonctionnel !
Une dernière chose à faire pour pouvoir le voir fonctionner est d'aller dans le dossier "interface", et créer un fichier "addons.txt". Il va contenir la liste des addons à charger. Dans notre cas, on va juste écrire :
FPSCounter:1
Le "1" signifie "à charger". Si l'on met un "0" ou qu'on supprime cette ligne, l'addon ne sera pas chargé.

Faire la même chose en C++ donnerait le code suivant (mis à jour pour la version 1.2.0) :
// On créé la Frame
gui::frame* pFrame = mManager.create_frame<gui::frame>("FPSCounter");
pFrame->set_rel_dimensions(1.0f, 1.0f);
pFrame->set_abs_point(gui::ANCHOR_CENTER, "", gui::ANCHOR_CENTER);

// ... le FontString
gui::font_string* pFont = pFrame->create_region<gui::font_string>(gui::LAYER_ARTWORK, "$parentText");
pFont->set_abs_point(gui::ANCHOR_BOTTOMRIGHT, "$parent", gui::ANCHOR_BOTTOMRIGHT, -5, -5);
pFont->set_font("interface/fonts/main.ttf", 12);
pFont->set_justify_v(gui::text::ALIGN_BOTTOM);
pFont->set_justify_h(gui::text::ALIGN_RIGHT);
pFont->set_outlined(true);
pFont->set_text_color(gui::color::GREEN);
pFont->notify_loaded();

// On créé les scripts en C++ (on pourrait aussi mettre du code Lua ici)
float update_time = 0.5f, timer = 1.0f;
int frames = 0;
pFrame->define_script("OnUpdate",
    [&](gui::frame* self, gui::event* event) {
        float delta = event->get<float>(0);
        timer += delta;
        ++frames;

        if (timer > update_time)
        {
            gui::font_string* text = self->get_region<gui::font_string>("Text");
            text->set_text("FPS : "+utils::to_string(floor(frames/timer)));
           
            timer = 0.0f;
            frames = 0;
        }
    }
);

// On dit à la Frame qu'elle est maintenant chargée.
pFrame->notify_loaded();

Comme vous pouvez le voir sur la capture d'écran ci-dessus, ce système peut être utilisé pour créer des GUI très complexes (le "File selector" est un véritable explorateur de fichiers !). C'est en partie dû à la puissance du système d'héritage (que je n'ai pas présenté ici) : on créé une frame "modèle" (on dit aussi "virtuelle"), qui peut contenir beaucoup d'objets, et avoir beaucoup de propriétés, puis créer plusieurs frames qui suivront ce "modèle" (elles en "héritent"). Cela permet de réduire la quantité de code nécessaire, et peut également vous aider à faire des GUI cohérents : on peut par exemple créer un modèle de bouton que devront utiliser tous les boutons du GUI, de sorte qu'ils aient tous le même aspect.

Je pense clore ici cette (trop longue) présentation. Il n'y a pas de tutoriaux officiels, et pas de documentation pour l'API en Lua, mais vous pouvez vous renseigner sur des sites pour World of Warcraft en attendant (par exemple WoWWiki (en anglais)). J'espère que certains d'entre vous trouveront cette bibliothèque utile !

Dans l'archive du code source, vous trouverez (dans le dossier "gui/test") un programme test qui est sensé compiler et fonctionner si vous avez installé tout correctement. Le rendu doit être identique à la capture d'écran ci-dessus. Ce programme peut aussi être vu comme une démo : vous pouvez y voir comment s'agencent des addons plus compliqués, et quelques exemples d'héritage.

Téléchargements :
Le projet a sa propre page sur sourceforge : ici.
Vous trouvez des archives .zip et .7z contenant l'intégralité des sources, ainsi qu'un dépôt SVN.

Note : la bibliothèque lxgui ainsi que toutes celles qui l'accompagnent sont mise à disposition sous la licence GNU LGPL 3 (vous pouvez utiliser ces bibliothèques sans avoir à divulguer votre code source. En revanche, toute modification faite sur le code source des bibliothèques doit être rendue publique. Voir "gnu.txt" pour plus d'information).

Pages: [1]
anything