|
JustAnotherDnDGame 0.1.0
Jeu de rôle tactique au d20, vue de dessus, en C++/Qt
|
Statut : livré (LOT-54, EX-EDIT-045). Deux points restent hors périmètre, actés dans l'épic : le point d'entrée depuis le panneau Textures n'est pas câblé, et l'aperçu d'animation n'est pas livré (l'aperçu de raccords, lui, l'est).
Depuis le LOT-42, l'apparence d'un niveau tient dans des fichiers image : planches de skins, fonds, objets, plans picturaux, images d'animation. Le LOT-43 a donné de quoi les importer, les renommer, les recharger à chaud — mais pas de quoi les modifier. Corriger un pixel mal placé demandait d'ouvrir un éditeur externe, enregistrer, revenir, attendre le rechargement, constater, recommencer. Sur un raccord automatique à seize cases, où la justesse d'un bord ne se voit qu'une fois la planche assemblée dans un niveau, cette boucle est le principal coût du travail.
L'atelier ferme la boucle : dessiner, voir le résultat dans le niveau, corriger — sans quitter l'application.
Ce lot s'exécute derrière LOT-56 et LOT-57 et bâtit sur ce qu'ils livrent. Le canevas n'invente ni son habillage, ni ses commandes, ni son affichage d'état : il réutilise les jetons de design, le catalogue d'actions et le modèle de barre d'état déjà en place. C'est ce qui explique qu'un lot d'apparence tienne en huit tâches tout en livrant des fonctions d'édition — la plomberie d'interface était déjà payée.
hmi::encodeImageFile est le pendant exact de decodeImageFile (LOT-40) : même type d'image en mémoire (hmi::DecodedImage, RGBA8888, alpha non prémultiplié), si bien que décoder puis réencoder restitue les mêmes pixels, canal alpha compris.
L'écriture est atomique — fichier temporaire, puis remplacement — et ce n'est pas de la prudence gratuite : le rechargement à chaud (LOT-43) surveille ce même dossier et peut lire pendant qu'on écrit. Une écriture directe exposerait un PNG à demi écrit, que le chargeur rejetterait en affichant la texture manquante — au pire moment, celui où l'auteur vient d'appuyer sur « Enregistrer ». Le fichier temporaire ne subsiste ni après un succès, ni après un échec.
L'export d'atlas en ligne de commande (--export-atlas) passe désormais par ce même chemin d'écriture, plutôt que par une seconde implémentation.
HMI/Editor/PixelOperations.h contient tout ce que font les outils, sous forme de fonctions libres sans état, sans Qt ni GPU (EX-NFR-010) — donc entièrement testables. Le widget, lui, ne fait que traduire des gestes en appels.
Chaque opération renvoie une PixelRegion : le rectangle de pixels réellement touché. Ce n'est pas une commodité d'affichage, c'est ce qui borne le coût mémoire de l'historique (ci-dessous).
Les outils de région (TACHE-06) suivent le même principe : flipHorizontal et flipVertical, rotateClockwise et son inverse, moveRegion, et le couple copyRegion / pasteClipboard par un hmi::PixelClipboard — un tampon autonome, qui survit donc à la fermeture de l'image dont il provient.
PixelHistory est une pile d'annulation/rétablissement propre au canevas, totalement indépendante de celle de core::LevelDraft (Éditeur de niveaux). C'est un critère d'acceptation explicite du lot : annuler un coup de pinceau ne doit jamais annuler la pose d'une tuile, et réciproquement. Les deux historiques ne se rencontrent qu'au niveau du dispatch de l'action (voir plus bas), jamais dans leurs données.
Deux traits le distinguent de l'historique du brouillon :
Un geste complet produit une seule entrée : un glisser de pinceau, si long soit-il, s'annule d'un coup. Découper par événement de souris rendrait l'annulation inutilisable.
HMI/Editor/PixelCanvasGeometry.h isole les conversions vue ↔ image en fonctions pures : imagePixelScreenRect (où dessiner un pixel image), screenToImagePixel (quel pixel est sous le curseur), et les commandes de zoom. Le zoom est toujours entier (EX-ARCH-022) : un facteur fractionnaire donnerait des pixels de largeurs inégales à l'écran, ce qui rend le travail au pixel près impossible. La grille de pixels n'apparaît qu'au-delà d'un seuil de zoom — en deçà, elle ferait plus de bruit que de repère.
Cette séparation n'est pas cosmétique : c'est cette géométrie qui détermine le pixel survolé affiché dans la barre d'état, et elle doit rester juste à tout zoom, tout décalage de vue et toute échelle d'affichage. Elle réutilise pour cela thumbnailPixelSize (Système de design et architecture de l'information, LOT-56) plutôt que de redéfinir sa propre règle de netteté.
hmi::PixelCanvas (QWidget) ne fait que le reste : affichage au plus proche voisin, damier de transparence, gestes de souris. Son fond et son damier sont tirés des jetons de portée invariante (identityTokens) : ils représentent l'absence de couleur, pas une surface d'interface, et ne doivent donc pas changer avec le thème clair/sombre du châssis — sans quoi l'artiste jugerait ses couleurs sur un fond mouvant.
PixelPalette persiste une palette de projet sur disque. extractPalette recense les couleurs d'une image ouverte (avec leur fréquence), et nearestPaletteColor sert le mode « contraindre à la palette », qui interdit de poser une couleur hors palette. C'est la discipline qui donne à un jeu de sprites son unité : sans contrainte, chaque retouche introduit un nouveau ton presque identique au précédent.
validAssetSizes (PixelAssetIO.h) propose, pour une famille d'asset, une courte progression de tailles dérivées du contrat d'asset (hmi::assetDimensionContract, EX-REN-007) — jamais une seconde description des mêmes règles. L'intention est de rendre une création non conforme impossible, plutôt que de la refuser après coup au chargement. Une famille à dimensions libres (fond, police) renvoie une liste vide : l'appelant offre alors une saisie libre.
isBitmask16Candidate reconnaît une planche à raccords à ses dimensions, bitmaskCellAtPixel dit quelle case de voisinage porte un pixel donné, et buildAutotileAssemblyPreview assemble un aperçu de la planche telle qu'elle apparaîtra une fois raccordée. C'est la réponse directe au problème posé en tête de page : la justesse d'un bord de raccord ne se juge pas case par case, mais sur l'assemblage.
Le canevas et le niveau ont chacun leur historique, mais l'application n'expose qu'une action Annuler et qu'une action Refaire (EX-IHM-062). Le tri se fait par le contexte d'édition actif : hmi::PixelCanvas implémente EditContextTarget — la même interface que hmi::GameViewport — et MainWindow réassigne sa cible au widget qui reçoit le focus clavier. Le dispatch existant n'a pas été modifié pour accueillir l'atelier : c'est précisément le seuil que LOT-57 avait posé en prévision de ce lot (voir Système de design et architecture de l'information).
De la même façon, les outils du canevas forment un groupe d'actions exclusif distinct de celui des outils de niveau (EditorActionGroup::PixelTools), avec ses icônes dessinées par code et sa barre d'outils dédiée ; et l'état du canevas (asset ouvert, modifications, outil, pixel survolé, zoom, couleur courante) étend le modèle de barre d'état de LOT-57 d'un contexte d'édition d'asset, au lieu d'en dupliquer un second.