Journal Clay Style Sheet

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
8
30
juil.
2025

Bonjour Nal!

Je dois me confesser, aujourd'hui, j'ai craqué. J'ai succombé à ce que l'on appelle "l'over engineering".

Depuis quelques temps, je développe un jeu de plateau&carte en C++, inspiré des échecs et de Magic The Gathering. Pour l'interface graphique du jeu (menus, boutons, etc…), j'ai décidé d'utiliser Clay. C'est une bibliothèque C qui s'inspire pas mal des CSS Flexbox pour la disposition des éléments de ton interface graphique. J'ai fait un moteur de rendu pour Clay avec NanoVG, j'en ai un peu parlé ici (l'écriture de la 3è partie concernant l'intégration de stb_textedit est en cours).

Mais voilà, je dois gérer maintenant le style de mon interface graphique en C++. J'ai des monts de code copié/collé partout, c'est pas facile à maintenir.

C'est l'excuse parfaite pour procrastiner au lieu d'avancer sur le jeu !

J'ai donc ajouté la bibliothèque lexy à mon projet, et j'ai créé un parser pour un dialecte de CSS. Je l'ai nommé "Clay Style Sheet".

C'est de l'over-engineering comme on en fait plus. Je n'avais pas vraiment besoin de ça, mais c'était amusant à faire.

Je te partage le code si tu es curieux et que tu veux avoir les yeux qui saignent en lisant 1500 lignes de code C++ :

https://gist.github.com/linkdd/03389d8907c0cef7d07f551865b54d8f

  • # Clay

    Posté par  (site web personnel) . Évalué à 5 (+3/-0).

    Hello, intéressant.

    Je ne connaissais pas Clay, et ai regardé un peu l' exemple SDL3
    ça me fait beaucoup penser à ImGUI, autre lib UI très utilisée dans le monde du JV, qui ne se sert pas des widgets du système -donc souple, mais il faut la brancher manuellement avec le moteur qu'on va utiliser.

    Sauf qu'ici la syntaxe à base de structs imbriquées est très différente, et me fait justement penser à une hiérarchie XML/JSON -qui est ce que tu "wrappes" donc organiquement avec ta lib CSS, c'est ça ?

    PS: c'est quoi ton jeu ;-) ?

    • [^] # Re: Clay

      Posté par  (site web personnel) . Évalué à 4 (+2/-0).

      J'utilise "Dear ImGui" aussi, mais plutôt pour l'UI de l'éditeur / mode debug.

      Je la trouve puissante, mais difficile à styliser (peut être un problème chaise/clavier)

      Comme tu dis, ici la syntaxe à base de structure etc… rappelle HTML&CSS, et permet de créer des composants fonctionnels comme on le ferait en React. La grosse différence avec ImGui, c'est que Clay ne stocke aucun état. C'est à toi de gérer ça (cela a été un challenge tout particulier lorsque j'ai implémenté mes textbox avec stb_textedit).

      Ma lib "CSS" permet de créer les structures Clay_ElementDeclaration et Clay_TextElementConfig en amont, afin de pouvoir les donner aux macros CLAY(), CLAY_TEXT() etc…

      A la base, j'avais voulu séparer le style de la logique de l'UI (de la même manière, en HTML&CSS on veut éviter les styles "inline" et plutôt utiliser des classes CSS). Je me retrouvait avec des 100aines de lignes de code juste pour le style en amont, et ici c'est la faute de C++ :

      En C++23 (je sais plus depuis quand), lorsque l'on initialise une structure avec des "designated initializers", ils doivent être TOUS spécifiés, et dans le BON ORDRE :

      struct foo_type {
        int a;
        int b;
        int c;
      };
      
      // ERROR
      auto foo = foo_type{
        .b = 1,
      };
      
      // ERROR
      auto foo = foo_type{
        .b = 1,
        .c = 1,
        .a = 1,
      };
      
      // OK
      auto foo = foo_type{
        .a = 1,
        .b = 1,
        .c = 1,
      };

      Pourtant, en C23, les 3 exemples ci-dessus fonctionnent.

      Je peux t'assurer que c'était chiant avec les structures de Clay et pas mal de ces macros.

      Je me retrouvais avec ça au final:

      auto foo = foo_type{};
      foo.b = 1;

      Du coup, pour chaque élément Clay, je devais avoir un Clay_ElementDeclaration de prêt. J'avais donc une structure immense avec plein de variable membre, et un fichier immense qui répertoriait tout les styles. Et je devais stocker les ids des images et polices de caractères après les avoir ajouté à l'atlas de mon moteur de rendu NanoVG.

      C'était vraiment pénible, et j'ai essayé de résister pendant 3 semaines avec la pensée "je dois avancer sur le jeu, c'est pas un vrai problème". J'ai fini par craquer :D

      https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg

    • [^] # Re: C'est quoi ton jeu

      Posté par  (site web personnel) . Évalué à 4 (+2/-0).

      Chess meets Magic --> Wizard Arena

      Un tour est découpé en plusieurs phases :

      • Begin Phase : on réinitialise certaines stats des pièces sur le terrain (points de mouvement par exemple)
      • Draw Phase : pioche une carte
      • Cast Phase : tu joue des cartes (effet de zone, ou invocation)
      • Move Phase : tu déplace tes pièces
      • Battle Phase : tu choisis les pièces qui se battent (parmi celle qui sont suffisamment proche)
      • End Phase

      Chaque joueur commence avec un pièce sur le terrain qui fait office de roi. Quand elle meure, la partie est perdue.

      Je vais peut être ajouter une seconde cast phase après la battle phase et avoir des types de cartes qui ne peuvent être jouées que pendant certaines phases, et autoriser le joueur adverse à jouer des cartes pendant la End Phase, comme à Magic. Tout ça doit être playtesté, mais avant ça je dois avoir quelque chose de jouable. Je bosse pas mal sur la partie réseau en ce moment, j'ai aussi craqué et implémenté en C++ un modèle acteur minimal avec des channels à la Golang.

      https://streamable.com/0o1bqs & https://streamable.com/4yi4j4

      https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg

      • [^] # Re: C'est quoi ton jeu

        Posté par  (site web personnel) . Évalué à 4 (+2/-0). Dernière modification le 30 juillet 2025 à 13:53.

        C'est vrai que Dear ImGui a toujours une apparence très élémentaire…
        Par comparaison, les écrans de Clay vendent du rêve !

        Ma lib "CSS" permet de créer les structures Clay_ElementDeclaration et Clay_TextElementConfig en amont, afin de pouvoir les donner aux macros CLAY(), CLAY_TEXT() etc…

        Oui je trouve très bien que ta page le décrive -on en aurait eu besoin tôt ou tard.
        D'ailleurs même ton exemple de base ( my_layout() ) est bien plus parlant que celui fourni par Clay AMHA. Il est plus simple et adapté à un nouvel arrivant comme moi !

        En C++23 (je sais plus depuis quand), lorsque l'on initialise une structure avec des "designated initializers", ils doivent être TOUS spécifiés, et dans le BON ORDRE :

        Ah sérieux? J'utilise pas mal de C23, mais en C++ on est coincés à une version antérieure… bon à savoir.
        C'est aberrant du coup, quoique bien que ça ait participé à la naissance de Clay Style Sheet ! (pour nous au moins… pour toi, à voir si tu échappes au burnout 😄).

        Chess meets Magic --> Wizard Arena

        Ça a l'air plutôt sympa (je suis un ancien joueur MTG 😉).
        Sauf erreurs, les channels sont aussi ce que font la plupart des frameworks récents en Rust.
        Tiens-nous au courant, perso je me ferai un plaisir de l'essayer -et de voir enfin la fameuse interface 😜.

        • [^] # Re: C'est quoi ton jeu

          Posté par  (site web personnel) . Évalué à 4 (+2/-0).

          Ah sérieux? J'utilise pas mal de C23, mais en C++ on est coincés à une version antérieure… bon à savoir.

          Les "designated initializers" c'était une fonctionnalité de C depuis longtemps :

          struct foo_type {
            int a;
            int b;
          };
          
          struct foo_type foos[10] = {
            [2] = { .a = 1 },
            [4] = { .b = 1 }
          };

          C++20 introduit la fonctionnalité pour les types agrégats (structures), mais pour les tableaux toujours pas (donc pas de [x] = ...).

          Sauf erreurs, les channels sont aussi ce que font la plupart des frameworks récents en Rust.

          En effet, des runtime async comme tokio proposent des oneshot channel (un producer, un consumer, un seul message), et des mpsc channel (multiple producer, single consumer).

          Mais bon, à force de faire du Rust async, j'ai fini par développer une aversion épidermique pour ce langage. Si on ajoute à ça un écosystème pas mature, et des une perte de contrôle totale sur les dépendances ou mon target/ devient le nouveau node_modules/ (et le seul lot de consolation, c'est qu'ici c'est un langage compilé donc les symboles inutilisés peuvent être stripped out), mon choix se porte désormais sur Go malgré tout les défauts que je lui trouve (et il y en a).

          Mais ici, j'avais le jeu en C++, donc j'implémente le serveur aussi en C++ :)

          Tiens-nous au courant, perso je me ferai un plaisir de l'essayer -et de voir enfin la fameuse interface 😜.

          J'essayerai ! J'ai un discord sur lequel il y a quasiment personne, et j'ai aussi rejoint le discord d'un dev indépendant qui bosse sur Daisy Train, je trouve ça motivant d'être entouré de gens avec des compétences diverses et variées qui tous bossent sur leurs projets :)

          https://link-society.com - https://kubirds.com - https://github.com/link-society/flowg

          • [^] # Re: C'est quoi ton jeu

            Posté par  (site web personnel) . Évalué à 3 (+1/-0).

            une perte de contrôle totale sur les dépendances ou mon target/ devient le nouveau node_modules/

            Sur ce dernier point, il y a un contournement : forcer en assumant de gérer soi-même.
            Je n'ai pas encore d'exemple sur mon GitHub (faut à chaque fois nettoyer, mettre une licence..) mais en gros ça revient à faire ça:

            git clone https://github.com/.../ipc-channel --single-branch VER_201
            

            Dans Cargo.toml:

            [dependencies]
            ipc-channel = { path = "./ipc-channel" }
            

            (en "prod", vaut mieux utiliser soit build.rs, soit un folder sous-module qui sera renseigné dans .gitmodules et peuplé à l'initial "git clone … --recurse-submodules")

            Sur le reste, on est d'accord. En fait ce n'est peut-être pas le meilleur langage pour le jeu vidéo ;-).

            J'ai un discord sur lequel il y a quasiment personne, et j'ai aussi rejoint le discord d'un dev indépendant qui bosse sur Daisy Train

            Si tu trouves mon pseudo sur le serveur "The Rust Programming Language", n'hésite pas à m'inviter ;-).

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.