From 97a065879c067a2dd0f5600549a1b69062f30405 Mon Sep 17 00:00:00 2001 From: Denis Defolie Date: Sun, 11 Oct 2026 22:36:43 +0200 Subject: [PATCH] Visual overhaul: theme, illustrations, redesigned screens (0.3.0) Phase "habillage" of the post-0.1.0 plan. Rendering was reviewed on real screenshots (new tools/screenshot.gd, `make screenshot`), which also caught and fixed map markers overlapping and truncated titles. - Global Godot theme (Atkinson Hyperlegible font, cream/green/gold palette, raised buttons, rounded cards, thick keyboard focus). - Original vector art: forest/marsh backdrops, Plume the owl, stars, exercise-type icons, correct/try-again icons, 10 collection items. - Redesigned world map (markers as cards with icon, title and stars, responsive layout), profile screens, mission intro, exercise host, rule/feedback panels, reward screen and collection grid. - Motion helper (pop-in, fade) honouring an animations setting. - Settings: animations flag; base window 1152x648 with canvas_items. Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 1 + Makefile | 7 +- assets/CREDITS.md | 4 +- assets/characters/owl_plume.svg | 18 + assets/characters/owl_plume.svg.import | 43 +++ assets/environments/forest_backdrop.svg | 107 ++++++ .../environments/forest_backdrop.svg.import | 43 +++ assets/environments/marsh_backdrop.svg | 130 +++++++ assets/environments/marsh_backdrop.svg.import | 43 +++ assets/fonts/AtkinsonHyperlegible-Bold.ttf | Bin 0 -> 55256 bytes .../AtkinsonHyperlegible-Bold.ttf.import | 36 ++ assets/fonts/AtkinsonHyperlegible-Regular.ttf | Bin 0 -> 54348 bytes .../AtkinsonHyperlegible-Regular.ttf.import | 36 ++ assets/fonts/OFL.txt | 92 +++++ assets/interface/icon_correct.svg | 5 + assets/interface/icon_correct.svg.import | 43 +++ assets/interface/icon_dictation.svg | 5 + assets/interface/icon_dictation.svg.import | 43 +++ assets/interface/icon_locked.svg | 5 + assets/interface/icon_locked.svg.import | 43 +++ assets/interface/icon_multiple_choice.svg | 5 + .../interface/icon_multiple_choice.svg.import | 43 +++ assets/interface/icon_sentence_correction.svg | 5 + .../icon_sentence_correction.svg.import | 43 +++ assets/interface/icon_try_again.svg | 5 + assets/interface/icon_try_again.svg.import | 43 +++ .../interface/items/item_carte_ancienne.svg | 5 + .../items/item_carte_ancienne.svg.import | 43 +++ assets/interface/items/item_cle_argentee.svg | 5 + .../items/item_cle_argentee.svg.import | 43 +++ .../items/item_couronne_feuilles.svg | 5 + .../items/item_couronne_feuilles.svg.import | 43 +++ assets/interface/items/item_cristal_echo.svg | 5 + .../items/item_cristal_echo.svg.import | 43 +++ .../interface/items/item_graine_magique.svg | 5 + .../items/item_graine_magique.svg.import | 43 +++ .../interface/items/item_lanterne_foret.svg | 5 + .../items/item_lanterne_foret.svg.import | 43 +++ assets/interface/items/item_plume_doree.svg | 5 + .../items/item_plume_doree.svg.import | 43 +++ .../items/item_trophee_clairiere.svg | 5 + .../items/item_trophee_clairiere.svg.import | 43 +++ assets/interface/items/item_trophee_pont.svg | 5 + .../items/item_trophee_pont.svg.import | 43 +++ assets/interface/items/item_trophee_pre.svg | 5 + .../items/item_trophee_pre.svg.import | 43 +++ assets/interface/star_empty.svg | 4 + assets/interface/star_empty.svg.import | 43 +++ assets/interface/star_full.svg | 4 + assets/interface/star_full.svg.import | 43 +++ assets/interface/theme.tres | 362 ++++++++++++++++++ autoload/settings_manager.gd | 5 +- data/localization/fr.json | 1 + docs/specs/decisions.md | 16 + docs/testing/manual-checklist.md | 3 + project.godot | 13 +- scenes/characters/guide_owl.gd | 14 +- scenes/characters/guide_owl.tscn | 19 +- scenes/exercises/dictation.tscn | 1 + scenes/exercises/exercise_host.gd | 3 + scenes/exercises/exercise_host.tscn | 43 ++- scenes/exercises/multiple_choice.gd | 3 +- scenes/exercises/multiple_choice.tscn | 3 +- scenes/exercises/sentence_correction.gd | 3 + scenes/exercises/sentence_correction.tscn | 1 + scenes/profiles/profile_create.tscn | 50 ++- scenes/profiles/profile_select.gd | 3 + scenes/profiles/profile_select.tscn | 34 +- scenes/ui/backdrop.gd | 24 ++ scenes/ui/backdrop.gd.uid | 1 + scenes/ui/backdrop.tscn | 24 ++ scenes/ui/collection.gd | 35 +- scenes/ui/collection.tscn | 46 ++- scenes/ui/feedback_panel.gd | 6 + scenes/ui/feedback_panel.tscn | 15 +- scenes/ui/reward_screen.gd | 65 +++- scenes/ui/reward_screen.tscn | 93 +++-- scenes/ui/rule_panel.tscn | 5 +- scenes/world/mission_intro.gd | 4 +- scenes/world/mission_intro.tscn | 50 ++- scenes/world/mission_marker.gd | 37 ++ scenes/world/mission_marker.gd.uid | 1 + scenes/world/mission_marker.tscn | 59 +++ scenes/world/world_map.gd | 122 +++--- scenes/world/world_map.tscn | 76 +++- scripts/data/item_icons.gd | 20 + scripts/data/item_icons.gd.uid | 1 + scripts/ui/motion.gd | 26 ++ scripts/ui/motion.gd.uid | 1 + .../test_exercise_host_abandon.gd.uid | 1 + tests/world/test_reward_flow.gd | 9 +- tools/screenshot.gd | 72 ++++ tools/screenshot.gd.uid | 1 + 93 files changed, 2596 insertions(+), 197 deletions(-) create mode 100644 assets/characters/owl_plume.svg create mode 100644 assets/characters/owl_plume.svg.import create mode 100644 assets/environments/forest_backdrop.svg create mode 100644 assets/environments/forest_backdrop.svg.import create mode 100644 assets/environments/marsh_backdrop.svg create mode 100644 assets/environments/marsh_backdrop.svg.import create mode 100644 assets/fonts/AtkinsonHyperlegible-Bold.ttf create mode 100644 assets/fonts/AtkinsonHyperlegible-Bold.ttf.import create mode 100644 assets/fonts/AtkinsonHyperlegible-Regular.ttf create mode 100644 assets/fonts/AtkinsonHyperlegible-Regular.ttf.import create mode 100644 assets/fonts/OFL.txt create mode 100644 assets/interface/icon_correct.svg create mode 100644 assets/interface/icon_correct.svg.import create mode 100644 assets/interface/icon_dictation.svg create mode 100644 assets/interface/icon_dictation.svg.import create mode 100644 assets/interface/icon_locked.svg create mode 100644 assets/interface/icon_locked.svg.import create mode 100644 assets/interface/icon_multiple_choice.svg create mode 100644 assets/interface/icon_multiple_choice.svg.import create mode 100644 assets/interface/icon_sentence_correction.svg create mode 100644 assets/interface/icon_sentence_correction.svg.import create mode 100644 assets/interface/icon_try_again.svg create mode 100644 assets/interface/icon_try_again.svg.import create mode 100644 assets/interface/items/item_carte_ancienne.svg create mode 100644 assets/interface/items/item_carte_ancienne.svg.import create mode 100644 assets/interface/items/item_cle_argentee.svg create mode 100644 assets/interface/items/item_cle_argentee.svg.import create mode 100644 assets/interface/items/item_couronne_feuilles.svg create mode 100644 assets/interface/items/item_couronne_feuilles.svg.import create mode 100644 assets/interface/items/item_cristal_echo.svg create mode 100644 assets/interface/items/item_cristal_echo.svg.import create mode 100644 assets/interface/items/item_graine_magique.svg create mode 100644 assets/interface/items/item_graine_magique.svg.import create mode 100644 assets/interface/items/item_lanterne_foret.svg create mode 100644 assets/interface/items/item_lanterne_foret.svg.import create mode 100644 assets/interface/items/item_plume_doree.svg create mode 100644 assets/interface/items/item_plume_doree.svg.import create mode 100644 assets/interface/items/item_trophee_clairiere.svg create mode 100644 assets/interface/items/item_trophee_clairiere.svg.import create mode 100644 assets/interface/items/item_trophee_pont.svg create mode 100644 assets/interface/items/item_trophee_pont.svg.import create mode 100644 assets/interface/items/item_trophee_pre.svg create mode 100644 assets/interface/items/item_trophee_pre.svg.import create mode 100644 assets/interface/star_empty.svg create mode 100644 assets/interface/star_empty.svg.import create mode 100644 assets/interface/star_full.svg create mode 100644 assets/interface/star_full.svg.import create mode 100644 assets/interface/theme.tres create mode 100644 scenes/ui/backdrop.gd create mode 100644 scenes/ui/backdrop.gd.uid create mode 100644 scenes/ui/backdrop.tscn create mode 100644 scenes/world/mission_marker.gd create mode 100644 scenes/world/mission_marker.gd.uid create mode 100644 scenes/world/mission_marker.tscn create mode 100644 scripts/data/item_icons.gd create mode 100644 scripts/data/item_icons.gd.uid create mode 100644 scripts/ui/motion.gd create mode 100644 scripts/ui/motion.gd.uid create mode 100644 tests/exercises/test_exercise_host_abandon.gd.uid create mode 100644 tools/screenshot.gd create mode 100644 tools/screenshot.gd.uid diff --git a/CLAUDE.md b/CLAUDE.md index 508f70d..4027865 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,6 +19,7 @@ Le `Makefile` est le seul point d'entrée pour les opérations système. Tenir c - `make content-review` : régénère `docs/review/content-review.md` à partir de `data/` - `make generate-dictation-audio` : génère l'audio provisoire des dictées (voix neuronale Piper via `.venv`, `ffmpeg` requis sur la machine) dans `assets/audio/dictations/`, sans écraser l'existant (`--force` pour régénérer) - `make export-linux` / `make export-windows` : exports dans `build/` +- `make screenshot SCENE=… OUT=… ARGS=…` : capture le rendu réel d'une scène dans une fenêtre (outil de développement, ouvre brièvement une fenêtre) - `make package` : réexporte puis produit les archives distribuables (exécutable unique + licence + crédits + notice) et `SHA256SUMS` dans `dist/` ## Conventions de code diff --git a/Makefile b/Makefile index 7ded539..a9cc21f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ PYRIGHT := $(VENV)/bin/pyright PYTHON := $(VENV)/bin/python3 GD_SOURCES := autoload scripts tests -.PHONY: setup run lint format validate-data test check content-review generate-dictation-audio export-linux export-windows package +.PHONY: setup run lint format validate-data test check content-review generate-dictation-audio export-linux export-windows package screenshot setup: tools/setup.sh @@ -45,3 +45,8 @@ export-windows: package: tools/package.sh + +# Capture d'une scene dans une vraie fenetre (voir tools/screenshot.gd) : +# make screenshot SCENE=res://scenes/world/world_map.tscn OUT=/tmp/map.png ARGS="CE2" +screenshot: + $(GODOT_BIN) --path . -s res://tools/screenshot.gd -- $(SCENE) $(OUT) $(ARGS) diff --git a/assets/CREDITS.md b/assets/CREDITS.md index 5dd1e60..df9f4bd 100644 --- a/assets/CREDITS.md +++ b/assets/CREDITS.md @@ -1,10 +1,12 @@ # Sources des assets provisoires -Tous les graphismes et sons actuels sont provisoires, crees pour ce projet, sans asset tiers. +Tous les graphismes et sons actuels sont provisoires et crees pour ce projet ; seules la police et les voix de synthese reposent sur des composants tiers libres, listes ci-dessous. | Fichier | Origine | Licence | |---|---|---| | `icon.svg` | Forme geometrique simple, creee pour ce projet | AGPL-3.0 (code et assets du depot) | +| `fonts/AtkinsonHyperlegible-*.ttf` | Atkinson Hyperlegible, police conçue pour la lisibilité (Braille Institute) | SIL Open Font License 1.1 (`fonts/OFL.txt`) | +| `environments/*.svg`, `characters/*.svg`, `interface/*.svg`, `interface/items/*.svg` | Illustrations vectorielles originales créées pour ce projet (décors, hibou Plume, étoiles, pictogrammes, objets de collection) ; provisoires | AGPL-3.0 (code et assets du dépôt) | | `audio/dictations/*.ogg` (toutes les dictees) | Voix de synthese neuronale generee hors ligne avec Piper (modele `fr_FR-siwis-medium`, entraine sur le corpus SIWIS de l'EPFL), via `tools/generate_dictation_audio.py` | MIT (Piper) ; dataset SIWIS en CC-BY 4.0 (attribution requise) ; fichiers generes verses en AGPL-3.0 avec le reste du depot | Chaque nouvelle dictee ajoutee a `data/dictations/` doit avoir son audio genere avec `make generate-dictation-audio`, puis une ligne ajoutee ici. Toutes les voix actuelles sont provisoires (`audio_provisional: true` dans la donnee, badge « voix provisoire » affiche a l'enfant) : a remplacer par une vraie voix humaine avant diffusion. diff --git a/assets/characters/owl_plume.svg b/assets/characters/owl_plume.svg new file mode 100644 index 0000000..5c16033 --- /dev/null +++ b/assets/characters/owl_plume.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/characters/owl_plume.svg.import b/assets/characters/owl_plume.svg.import new file mode 100644 index 0000000..4559185 --- /dev/null +++ b/assets/characters/owl_plume.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hokvd25q8sx8" +path="res://.godot/imported/owl_plume.svg-5a0c30c1a3ad1c122ff173e9e224403b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/characters/owl_plume.svg" +dest_files=["res://.godot/imported/owl_plume.svg-5a0c30c1a3ad1c122ff173e9e224403b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/environments/forest_backdrop.svg b/assets/environments/forest_backdrop.svg new file mode 100644 index 0000000..f0cea57 --- /dev/null +++ b/assets/environments/forest_backdrop.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/environments/forest_backdrop.svg.import b/assets/environments/forest_backdrop.svg.import new file mode 100644 index 0000000..34fc5ae --- /dev/null +++ b/assets/environments/forest_backdrop.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdho8w07lqr4h" +path="res://.godot/imported/forest_backdrop.svg-759c497caa0fa1755bebe33bd64b97e2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/environments/forest_backdrop.svg" +dest_files=["res://.godot/imported/forest_backdrop.svg-759c497caa0fa1755bebe33bd64b97e2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/environments/marsh_backdrop.svg b/assets/environments/marsh_backdrop.svg new file mode 100644 index 0000000..81a307a --- /dev/null +++ b/assets/environments/marsh_backdrop.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/environments/marsh_backdrop.svg.import b/assets/environments/marsh_backdrop.svg.import new file mode 100644 index 0000000..549e8df --- /dev/null +++ b/assets/environments/marsh_backdrop.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3d2gu2fyacvu" +path="res://.godot/imported/marsh_backdrop.svg-c7f73bcacccc54a2e0f9099485e3bbba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/environments/marsh_backdrop.svg" +dest_files=["res://.godot/imported/marsh_backdrop.svg-c7f73bcacccc54a2e0f9099485e3bbba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/fonts/AtkinsonHyperlegible-Bold.ttf b/assets/fonts/AtkinsonHyperlegible-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ca3f613847de62b8da83d2dab500ec391c8fe477 GIT binary patch literal 55256 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 Ca=@Jc literal 0 HcmV?d00001 diff --git a/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import b/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import new file mode 100644 index 0000000..44a92a7 --- /dev/null +++ b/assets/fonts/AtkinsonHyperlegible-Bold.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bpcx8adosnev6" +path="res://.godot/imported/AtkinsonHyperlegible-Bold.ttf-88b8e4bce39666c760f25dd9a57aebce.fontdata" + +[deps] + +source_file="res://assets/fonts/AtkinsonHyperlegible-Bold.ttf" +dest_files=["res://.godot/imported/AtkinsonHyperlegible-Bold.ttf-88b8e4bce39666c760f25dd9a57aebce.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=4 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/AtkinsonHyperlegible-Regular.ttf b/assets/fonts/AtkinsonHyperlegible-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b3cc4200fcf09d600eac74002ab4ea65b404a536 GIT binary patch literal 54348 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 N)RR(%LbP=Ae*iZmk*xp# literal 0 HcmV?d00001 diff --git a/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import b/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import new file mode 100644 index 0000000..2c6ebcf --- /dev/null +++ b/assets/fonts/AtkinsonHyperlegible-Regular.ttf.import @@ -0,0 +1,36 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bjb7qwgg76e8t" +path="res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-b1b81e810f770645a511deee33c11e03.fontdata" + +[deps] + +source_file="res://assets/fonts/AtkinsonHyperlegible-Regular.ttf" +dest_files=["res://.godot/imported/AtkinsonHyperlegible-Regular.ttf-b1b81e810f770645a511deee33c11e03.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +disable_embedded_bitmaps=true +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +modulate_color_glyphs=false +hinting=1 +subpixel_positioning=4 +keep_rounding_remainders=true +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/OFL.txt b/assets/fonts/OFL.txt new file mode 100644 index 0000000..2befc89 --- /dev/null +++ b/assets/fonts/OFL.txt @@ -0,0 +1,92 @@ +Copyright 2020 Braille Institute of America, Inc. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/interface/icon_correct.svg b/assets/interface/icon_correct.svg new file mode 100644 index 0000000..1f8d3d0 --- /dev/null +++ b/assets/interface/icon_correct.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_correct.svg.import b/assets/interface/icon_correct.svg.import new file mode 100644 index 0000000..97eea82 --- /dev/null +++ b/assets/interface/icon_correct.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qrmkbgkfqrpp" +path="res://.godot/imported/icon_correct.svg-fe6bcc61481d18487d38d1ee8d1f1713.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_correct.svg" +dest_files=["res://.godot/imported/icon_correct.svg-fe6bcc61481d18487d38d1ee8d1f1713.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/icon_dictation.svg b/assets/interface/icon_dictation.svg new file mode 100644 index 0000000..1606cd5 --- /dev/null +++ b/assets/interface/icon_dictation.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_dictation.svg.import b/assets/interface/icon_dictation.svg.import new file mode 100644 index 0000000..7d6453d --- /dev/null +++ b/assets/interface/icon_dictation.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://51fub21m6m3u" +path="res://.godot/imported/icon_dictation.svg-2f1dc15bf770641095d391adf607b065.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_dictation.svg" +dest_files=["res://.godot/imported/icon_dictation.svg-2f1dc15bf770641095d391adf607b065.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/icon_locked.svg b/assets/interface/icon_locked.svg new file mode 100644 index 0000000..37b3adb --- /dev/null +++ b/assets/interface/icon_locked.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_locked.svg.import b/assets/interface/icon_locked.svg.import new file mode 100644 index 0000000..d2adb1c --- /dev/null +++ b/assets/interface/icon_locked.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://t3a58bf0b1om" +path="res://.godot/imported/icon_locked.svg-4ecadd3abe9f5993cfa09be34ca4c5b6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_locked.svg" +dest_files=["res://.godot/imported/icon_locked.svg-4ecadd3abe9f5993cfa09be34ca4c5b6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/icon_multiple_choice.svg b/assets/interface/icon_multiple_choice.svg new file mode 100644 index 0000000..a09ef56 --- /dev/null +++ b/assets/interface/icon_multiple_choice.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_multiple_choice.svg.import b/assets/interface/icon_multiple_choice.svg.import new file mode 100644 index 0000000..30204cc --- /dev/null +++ b/assets/interface/icon_multiple_choice.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://evabcqff8aym" +path="res://.godot/imported/icon_multiple_choice.svg-f1deff20f673f35ff192065f87228616.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_multiple_choice.svg" +dest_files=["res://.godot/imported/icon_multiple_choice.svg-f1deff20f673f35ff192065f87228616.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/icon_sentence_correction.svg b/assets/interface/icon_sentence_correction.svg new file mode 100644 index 0000000..41e18fe --- /dev/null +++ b/assets/interface/icon_sentence_correction.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_sentence_correction.svg.import b/assets/interface/icon_sentence_correction.svg.import new file mode 100644 index 0000000..29f31c3 --- /dev/null +++ b/assets/interface/icon_sentence_correction.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxba7g3fgfls4" +path="res://.godot/imported/icon_sentence_correction.svg-5ec7eb26565706514d6eb77c1829b1eb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_sentence_correction.svg" +dest_files=["res://.godot/imported/icon_sentence_correction.svg-5ec7eb26565706514d6eb77c1829b1eb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/icon_try_again.svg b/assets/interface/icon_try_again.svg new file mode 100644 index 0000000..b17013c --- /dev/null +++ b/assets/interface/icon_try_again.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/icon_try_again.svg.import b/assets/interface/icon_try_again.svg.import new file mode 100644 index 0000000..69942f1 --- /dev/null +++ b/assets/interface/icon_try_again.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgxhl6rvvku8f" +path="res://.godot/imported/icon_try_again.svg-6c536e074f8e778a026b608a4e19c11e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/icon_try_again.svg" +dest_files=["res://.godot/imported/icon_try_again.svg-6c536e074f8e778a026b608a4e19c11e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_carte_ancienne.svg b/assets/interface/items/item_carte_ancienne.svg new file mode 100644 index 0000000..e237514 --- /dev/null +++ b/assets/interface/items/item_carte_ancienne.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_carte_ancienne.svg.import b/assets/interface/items/item_carte_ancienne.svg.import new file mode 100644 index 0000000..ebfa55d --- /dev/null +++ b/assets/interface/items/item_carte_ancienne.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmqns71qq7t6r" +path="res://.godot/imported/item_carte_ancienne.svg-207f863c1e1fa43cbb234f96b29ce806.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_carte_ancienne.svg" +dest_files=["res://.godot/imported/item_carte_ancienne.svg-207f863c1e1fa43cbb234f96b29ce806.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_cle_argentee.svg b/assets/interface/items/item_cle_argentee.svg new file mode 100644 index 0000000..8ea91d8 --- /dev/null +++ b/assets/interface/items/item_cle_argentee.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_cle_argentee.svg.import b/assets/interface/items/item_cle_argentee.svg.import new file mode 100644 index 0000000..415b599 --- /dev/null +++ b/assets/interface/items/item_cle_argentee.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3i1vdgp734hf" +path="res://.godot/imported/item_cle_argentee.svg-7b2fa6b82e4ad27ec9adf3c465385bb3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_cle_argentee.svg" +dest_files=["res://.godot/imported/item_cle_argentee.svg-7b2fa6b82e4ad27ec9adf3c465385bb3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_couronne_feuilles.svg b/assets/interface/items/item_couronne_feuilles.svg new file mode 100644 index 0000000..8e0cd9e --- /dev/null +++ b/assets/interface/items/item_couronne_feuilles.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_couronne_feuilles.svg.import b/assets/interface/items/item_couronne_feuilles.svg.import new file mode 100644 index 0000000..77aa3e4 --- /dev/null +++ b/assets/interface/items/item_couronne_feuilles.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blxqc6nww8i31" +path="res://.godot/imported/item_couronne_feuilles.svg-88412268c71dd68b45cbfbd5d18901db.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_couronne_feuilles.svg" +dest_files=["res://.godot/imported/item_couronne_feuilles.svg-88412268c71dd68b45cbfbd5d18901db.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_cristal_echo.svg b/assets/interface/items/item_cristal_echo.svg new file mode 100644 index 0000000..a503021 --- /dev/null +++ b/assets/interface/items/item_cristal_echo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_cristal_echo.svg.import b/assets/interface/items/item_cristal_echo.svg.import new file mode 100644 index 0000000..d13a935 --- /dev/null +++ b/assets/interface/items/item_cristal_echo.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c41gjir31el76" +path="res://.godot/imported/item_cristal_echo.svg-9bcfe1f35f228b0dc193cfe8fdb9e9cc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_cristal_echo.svg" +dest_files=["res://.godot/imported/item_cristal_echo.svg-9bcfe1f35f228b0dc193cfe8fdb9e9cc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_graine_magique.svg b/assets/interface/items/item_graine_magique.svg new file mode 100644 index 0000000..6dcb872 --- /dev/null +++ b/assets/interface/items/item_graine_magique.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_graine_magique.svg.import b/assets/interface/items/item_graine_magique.svg.import new file mode 100644 index 0000000..c1de9cf --- /dev/null +++ b/assets/interface/items/item_graine_magique.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://if3iy4his26n" +path="res://.godot/imported/item_graine_magique.svg-5a1d4325cb4a33e8cfed8f37fdcffc2e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_graine_magique.svg" +dest_files=["res://.godot/imported/item_graine_magique.svg-5a1d4325cb4a33e8cfed8f37fdcffc2e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_lanterne_foret.svg b/assets/interface/items/item_lanterne_foret.svg new file mode 100644 index 0000000..6ca8625 --- /dev/null +++ b/assets/interface/items/item_lanterne_foret.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_lanterne_foret.svg.import b/assets/interface/items/item_lanterne_foret.svg.import new file mode 100644 index 0000000..0a5d92f --- /dev/null +++ b/assets/interface/items/item_lanterne_foret.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3dciddplnk3f" +path="res://.godot/imported/item_lanterne_foret.svg-1c8a8cc130821e715510a92740a99b26.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_lanterne_foret.svg" +dest_files=["res://.godot/imported/item_lanterne_foret.svg-1c8a8cc130821e715510a92740a99b26.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_plume_doree.svg b/assets/interface/items/item_plume_doree.svg new file mode 100644 index 0000000..1245214 --- /dev/null +++ b/assets/interface/items/item_plume_doree.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_plume_doree.svg.import b/assets/interface/items/item_plume_doree.svg.import new file mode 100644 index 0000000..aefa507 --- /dev/null +++ b/assets/interface/items/item_plume_doree.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jgnssjhtagj" +path="res://.godot/imported/item_plume_doree.svg-74d4e5dc0a7fc3d3e0cddbe4e20a707c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_plume_doree.svg" +dest_files=["res://.godot/imported/item_plume_doree.svg-74d4e5dc0a7fc3d3e0cddbe4e20a707c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_trophee_clairiere.svg b/assets/interface/items/item_trophee_clairiere.svg new file mode 100644 index 0000000..ca83de3 --- /dev/null +++ b/assets/interface/items/item_trophee_clairiere.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_trophee_clairiere.svg.import b/assets/interface/items/item_trophee_clairiere.svg.import new file mode 100644 index 0000000..144a63f --- /dev/null +++ b/assets/interface/items/item_trophee_clairiere.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cnkon70qvwt8b" +path="res://.godot/imported/item_trophee_clairiere.svg-66fdb4230e9bf038b8e39b7d230a04fb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_trophee_clairiere.svg" +dest_files=["res://.godot/imported/item_trophee_clairiere.svg-66fdb4230e9bf038b8e39b7d230a04fb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_trophee_pont.svg b/assets/interface/items/item_trophee_pont.svg new file mode 100644 index 0000000..0cb2a52 --- /dev/null +++ b/assets/interface/items/item_trophee_pont.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_trophee_pont.svg.import b/assets/interface/items/item_trophee_pont.svg.import new file mode 100644 index 0000000..e8b9835 --- /dev/null +++ b/assets/interface/items/item_trophee_pont.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4s28iicgsxa" +path="res://.godot/imported/item_trophee_pont.svg-d54bc3b9c7fe8de96e743f9589ce68f4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_trophee_pont.svg" +dest_files=["res://.godot/imported/item_trophee_pont.svg-d54bc3b9c7fe8de96e743f9589ce68f4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/items/item_trophee_pre.svg b/assets/interface/items/item_trophee_pre.svg new file mode 100644 index 0000000..021de38 --- /dev/null +++ b/assets/interface/items/item_trophee_pre.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/interface/items/item_trophee_pre.svg.import b/assets/interface/items/item_trophee_pre.svg.import new file mode 100644 index 0000000..b0f1e08 --- /dev/null +++ b/assets/interface/items/item_trophee_pre.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cyipl7ovnn3tk" +path="res://.godot/imported/item_trophee_pre.svg-870342a1466e75042ee4ae0cca1274f3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/items/item_trophee_pre.svg" +dest_files=["res://.godot/imported/item_trophee_pre.svg-870342a1466e75042ee4ae0cca1274f3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/star_empty.svg b/assets/interface/star_empty.svg new file mode 100644 index 0000000..d1837cf --- /dev/null +++ b/assets/interface/star_empty.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/interface/star_empty.svg.import b/assets/interface/star_empty.svg.import new file mode 100644 index 0000000..bf98fea --- /dev/null +++ b/assets/interface/star_empty.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dcdkxabiaebx3" +path="res://.godot/imported/star_empty.svg-6e835b3bc52db41523b0c3d2594cd977.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/star_empty.svg" +dest_files=["res://.godot/imported/star_empty.svg-6e835b3bc52db41523b0c3d2594cd977.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/star_full.svg b/assets/interface/star_full.svg new file mode 100644 index 0000000..77b2551 --- /dev/null +++ b/assets/interface/star_full.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/interface/star_full.svg.import b/assets/interface/star_full.svg.import new file mode 100644 index 0000000..7b9c458 --- /dev/null +++ b/assets/interface/star_full.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhcr4bie5exhd" +path="res://.godot/imported/star_full.svg-f0f7cbd280c649250320aebf0658d468.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/interface/star_full.svg" +dest_files=["res://.godot/imported/star_full.svg-f0f7cbd280c649250320aebf0658d468.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/interface/theme.tres b/assets/interface/theme.tres new file mode 100644 index 0000000..900a330 --- /dev/null +++ b/assets/interface/theme.tres @@ -0,0 +1,362 @@ +[gd_resource type="Theme" load_steps=20 format=3] + +[ext_resource type="FontFile" path="res://assets/fonts/AtkinsonHyperlegible-Regular.ttf" id="font_regular"] +[ext_resource type="FontFile" path="res://assets/fonts/AtkinsonHyperlegible-Bold.ttf" id="font_bold"] + +[sub_resource type="StyleBoxFlat" id="btn_normal"] +bg_color = Color(0.184, 0.502, 0.251, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.122, 0.361, 0.173, 1) +border_width_left = 0 +border_width_top = 0 +border_width_right = 0 +border_width_bottom = 6 +content_margin_left = 24 +content_margin_top = 10 +content_margin_right = 24 +content_margin_bottom = 6 + +[sub_resource type="StyleBoxFlat" id="btn_hover"] +bg_color = Color(0.227, 0.561, 0.298, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.122, 0.361, 0.173, 1) +border_width_left = 0 +border_width_top = 0 +border_width_right = 0 +border_width_bottom = 6 +content_margin_left = 24 +content_margin_top = 10 +content_margin_right = 24 +content_margin_bottom = 6 + +[sub_resource type="StyleBoxFlat" id="btn_pressed"] +bg_color = Color(1, 0.784, 0.239, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.149, 0.196, 0.290, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 24 +content_margin_top = 12 +content_margin_right = 24 +content_margin_bottom = 8 + +[sub_resource type="StyleBoxFlat" id="btn_disabled"] +bg_color = Color(0.78, 0.8, 0.78, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.66, 0.69, 0.66, 1) +border_width_left = 0 +border_width_top = 0 +border_width_right = 0 +border_width_bottom = 6 +content_margin_left = 24 +content_margin_top = 10 +content_margin_right = 24 +content_margin_bottom = 6 + +[sub_resource type="StyleBoxFlat" id="btn_focus"] +bg_color = Color(0,0,0,0) +draw_center = false +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 +border_color = Color(0.184, 0.490, 0.820, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 0 +content_margin_top = 0 +content_margin_right = 0 +content_margin_bottom = 0 +expand_margin_left = 3 +expand_margin_top = 3 +expand_margin_right = 3 +expand_margin_bottom = 3 + +[sub_resource type="StyleBoxFlat" id="sec_normal"] +bg_color = Color(0.992, 0.949, 0.816, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 6 +content_margin_left = 20 +content_margin_top = 8 +content_margin_right = 20 +content_margin_bottom = 4 + +[sub_resource type="StyleBoxFlat" id="sec_hover"] +bg_color = Color(1, 0.98, 0.9, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 6 +content_margin_left = 20 +content_margin_top = 8 +content_margin_right = 20 +content_margin_bottom = 4 + +[sub_resource type="StyleBoxFlat" id="sec_pressed"] +bg_color = Color(1, 0.784, 0.239, 1) +corner_radius_top_left = 18 +corner_radius_top_right = 18 +corner_radius_bottom_right = 18 +corner_radius_bottom_left = 18 +border_color = Color(0.149, 0.196, 0.290, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 20 +content_margin_top = 10 +content_margin_right = 20 +content_margin_bottom = 6 + +[sub_resource type="StyleBoxFlat" id="card"] +bg_color = Color(1, 0.965, 0.878, 1) +corner_radius_top_left = 28 +corner_radius_top_right = 28 +corner_radius_bottom_right = 28 +corner_radius_bottom_left = 28 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 20 +content_margin_top = 16 +content_margin_right = 20 +content_margin_bottom = 16 +shadow_color = Color(0, 0, 0, 0.28) +shadow_size = 14 +shadow_offset = Vector2(0, 6) + +[sub_resource type="StyleBoxFlat" id="card_soft"] +bg_color = Color(1, 0.965, 0.878, 1) +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +content_margin_left = 14 +content_margin_top = 10 +content_margin_right = 14 +content_margin_bottom = 10 +shadow_color = Color(0, 0, 0, 0.28) +shadow_size = 8 +shadow_offset = Vector2(0, 6) + +[sub_resource type="StyleBoxFlat" id="marker_normal"] +bg_color = Color(1, 0.965, 0.878, 1) +corner_radius_top_left = 22 +corner_radius_top_right = 22 +corner_radius_bottom_right = 22 +corner_radius_bottom_left = 22 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 7 +content_margin_left = 10 +content_margin_top = 8 +content_margin_right = 10 +content_margin_bottom = 4 +shadow_color = Color(0, 0, 0, 0.28) +shadow_size = 6 +shadow_offset = Vector2(0, 6) + +[sub_resource type="StyleBoxFlat" id="marker_hover"] +bg_color = Color(1, 0.99, 0.93, 1) +corner_radius_top_left = 22 +corner_radius_top_right = 22 +corner_radius_bottom_right = 22 +corner_radius_bottom_left = 22 +border_color = Color(0.122, 0.361, 0.173, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 7 +content_margin_left = 10 +content_margin_top = 8 +content_margin_right = 10 +content_margin_bottom = 4 +shadow_color = Color(0, 0, 0, 0.28) +shadow_size = 6 +shadow_offset = Vector2(0, 6) + +[sub_resource type="StyleBoxFlat" id="marker_pressed"] +bg_color = Color(1, 0.784, 0.239, 1) +corner_radius_top_left = 22 +corner_radius_top_right = 22 +corner_radius_bottom_right = 22 +corner_radius_bottom_left = 22 +border_color = Color(0.149, 0.196, 0.290, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 10 +content_margin_top = 10 +content_margin_right = 10 +content_margin_bottom = 6 + +[sub_resource type="StyleBoxFlat" id="edit_normal"] +bg_color = Color(1, 1, 1, 1) +corner_radius_top_left = 14 +corner_radius_top_right = 14 +corner_radius_bottom_right = 14 +corner_radius_bottom_left = 14 +border_color = Color(0.75, 0.68, 0.52, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +content_margin_left = 16 +content_margin_top = 10 +content_margin_right = 16 +content_margin_bottom = 10 + +[sub_resource type="StyleBoxFlat" id="edit_focus"] +bg_color = Color(1, 1, 1, 1) +corner_radius_top_left = 14 +corner_radius_top_right = 14 +corner_radius_bottom_right = 14 +corner_radius_bottom_left = 14 +border_color = Color(0.184, 0.490, 0.820, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 16 +content_margin_top = 10 +content_margin_right = 16 +content_margin_bottom = 10 + +[sub_resource type="StyleBoxFlat" id="window_panel"] +bg_color = Color(1, 0.965, 0.878, 1) +corner_radius_top_left = 24 +corner_radius_top_right = 24 +corner_radius_bottom_right = 24 +corner_radius_bottom_left = 24 +border_color = Color(0.898, 0.812, 0.604, 1) +border_width_left = 4 +border_width_top = 4 +border_width_right = 4 +border_width_bottom = 4 +content_margin_left = 20 +content_margin_top = 16 +content_margin_right = 20 +content_margin_bottom = 16 +shadow_color = Color(0, 0, 0, 0.28) +shadow_size = 16 +shadow_offset = Vector2(0, 6) + +[sub_resource type="StyleBoxFlat" id="pill"] +bg_color = Color(1, 0.784, 0.239, 0.95) +corner_radius_top_left = 30 +corner_radius_top_right = 30 +corner_radius_bottom_right = 30 +corner_radius_bottom_left = 30 +border_color = Color(0.851, 0.557, 0, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +content_margin_left = 16 +content_margin_top = 6 +content_margin_right = 16 +content_margin_bottom = 6 + +[resource] +default_font = ExtResource("font_regular") +default_font_size = 22 +Label/colors/font_color = Color(0.149, 0.196, 0.290, 1) +Label/colors/font_shadow_color = Color(0, 0, 0, 0) +Button/colors/font_color = Color(1, 1, 1, 1) +Button/colors/font_hover_color = Color(1, 1, 1, 1) +Button/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1) +Button/colors/font_focus_color = Color(1, 1, 1, 1) +Button/colors/font_disabled_color = Color(0.36, 0.4, 0.36, 1) +Button/fonts/font = ExtResource("font_bold") +Button/font_sizes/font_size = 22 +Button/styles/normal = SubResource("btn_normal") +Button/styles/hover = SubResource("btn_hover") +Button/styles/pressed = SubResource("btn_pressed") +Button/styles/disabled = SubResource("btn_disabled") +Button/styles/focus = SubResource("btn_focus") +SecondaryButton/base_type = &"Button" +SecondaryButton/colors/font_color = Color(0.149, 0.196, 0.290, 1) +SecondaryButton/colors/font_hover_color = Color(0.149, 0.196, 0.290, 1) +SecondaryButton/colors/font_focus_color = Color(0.149, 0.196, 0.290, 1) +SecondaryButton/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1) +SecondaryButton/styles/normal = SubResource("sec_normal") +SecondaryButton/styles/hover = SubResource("sec_hover") +SecondaryButton/styles/pressed = SubResource("sec_pressed") +MarkerButton/base_type = &"Button" +MarkerButton/colors/font_color = Color(0.149, 0.196, 0.290, 1) +MarkerButton/colors/font_hover_color = Color(0.149, 0.196, 0.290, 1) +MarkerButton/colors/font_focus_color = Color(0.149, 0.196, 0.290, 1) +MarkerButton/colors/font_pressed_color = Color(0.149, 0.196, 0.290, 1) +MarkerButton/styles/normal = SubResource("marker_normal") +MarkerButton/styles/hover = SubResource("marker_hover") +MarkerButton/styles/pressed = SubResource("marker_pressed") +PanelContainer/styles/panel = SubResource("card") +Panel/styles/panel = SubResource("card") +SoftCard/base_type = &"PanelContainer" +SoftCard/styles/panel = SubResource("card_soft") +Pill/base_type = &"PanelContainer" +Pill/styles/panel = SubResource("pill") +LineEdit/colors/font_color = Color(0.149, 0.196, 0.290, 1) +LineEdit/colors/font_placeholder_color = Color(0.45, 0.48, 0.56, 1) +LineEdit/colors/caret_color = Color(0.149, 0.196, 0.290, 1) +LineEdit/font_sizes/font_size = 24 +LineEdit/styles/normal = SubResource("edit_normal") +LineEdit/styles/focus = SubResource("edit_focus") +HeaderTitle/base_type = &"Label" +HeaderTitle/colors/font_color = Color(1, 1, 1, 1) +HeaderTitle/colors/font_outline_color = Color(0.122, 0.361, 0.173, 1) +HeaderTitle/constants/outline_size = 10 +HeaderTitle/fonts/font = ExtResource("font_bold") +HeaderTitle/font_sizes/font_size = 40 +CardTitle/base_type = &"Label" +CardTitle/fonts/font = ExtResource("font_bold") +CardTitle/font_sizes/font_size = 34 +Emphasis/base_type = &"Label" +Emphasis/fonts/font = ExtResource("font_bold") +Emphasis/font_sizes/font_size = 24 +Small/base_type = &"Label" +Small/font_sizes/font_size = 17 +AcceptDialog/styles/panel = SubResource("window_panel") +Window/styles/embedded_border = SubResource("window_panel") +Window/colors/title_color = Color(0.149, 0.196, 0.290, 1) +Window/fonts/title_font = ExtResource("font_bold") +Window/font_sizes/title_font_size = 26 +Window/constants/title_height = 44 diff --git a/autoload/settings_manager.gd b/autoload/settings_manager.gd index 4182563..15e4aa4 100644 --- a/autoload/settings_manager.gd +++ b/autoload/settings_manager.gd @@ -1,3 +1,6 @@ extends Node ## Volume, taille du texte et autres preferences de l'enfant ou de l'adulte. -## Logique detaillee a partir de l'etape "Scenes" (voir Ordre de construction). +## Pour l'instant : l'interrupteur des animations, lu par Motion. L'ecran de +## reglages qui le pilotera vient avec l'espace adulte (docs/specs/decisions.md). + +var animations_enabled := true diff --git a/data/localization/fr.json b/data/localization/fr.json index e9715fb..3988160 100644 --- a/data/localization/fr.json +++ b/data/localization/fr.json @@ -17,6 +17,7 @@ "profile_create.cancel_button": "Retour", "profile_create.name_required_error": "Choisis un prénom avant de continuer.", "world_map.title": "La forêt des accords", + "world_map.guide_hint": "Choisis un lieu pour commencer l'aventure !", "world_map.title_ce2": "Le marais des homophones", "world_map.review_ce1_button": "Revoir le CE1", "world_map.back_to_ce2_button": "Retour au CE2", diff --git a/docs/specs/decisions.md b/docs/specs/decisions.md index 00a8e24..d171a12 100644 --- a/docs/specs/decisions.md +++ b/docs/specs/decisions.md @@ -288,3 +288,19 @@ Ajouts à l'arborescence proposée : **Tests** : scoring, règles de déblocage, mise à jour de sauvegarde, migration v1→v2, validation du schéma, suivi des issues dans `exercise_host`, écran de récompense et collection de bout en bout. **Non vérifié** (sans écran) : rendu et lisibilité des trois lignes des marqueurs, mise en page de l'écran de récompense et de la collection, défilement de la liste. **À faire (phases suivantes)** : habillage visuel et illustrations des objets, réglages et espace adulte (dont export/restauration des sauvegardes), son et musique. + +## 31. Phase « Habillage visuel » et navigation (version 0.3.0) + +**Navigation (demande utilisateur)** : bouton « Changer de profil » sur la carte (sauvegarde avant de quitter) ; bouton « Quitter la mission » dans l'hôte d'exercice avec confirmation. Quitter ne rapporte ni étoile ni objet et n'est jamais compté comme un échec ; les tentatives déjà faites restent enregistrées (vraies observations d'apprentissage) et sont sauvegardées. + +**Vérification du rendu** : `tools/screenshot.gd` (`make screenshot`) charge une scène dans une vraie fenêtre et enregistre un PNG, avec actions scriptées (`continue_rule`, `answer:N`, `toggle:N`, `type:…`, `validate`, `outcomes:…`). Il a permis de relire le rendu réel et d'y corriger ce que les tests ne voient pas : marqueurs de la carte qui se chevauchaient (zone de carte plus basse que supposé au point 26), titres tronqués, vignettes de progression vides. Le test d'écart des positions (point 26) reste un garde-fou de données ; le rendu réel passe désormais par ces captures. + +**Système visuel** : thème Godot unique (`assets/interface/theme.tres`, appliqué par `project.godot`) : police Atkinson Hyperlegible (libre, conçue pour la lisibilité ; tous les caractères français vérifiés), palette crème/vert/or, boutons à relief, cartes arrondies, focus clavier bleu épais, variantes de type (`SecondaryButton`, `MarkerButton`, `CardTitle`, `HeaderTitle`, `Pill`...). Fenêtre de base 1152x648 avec étirement `canvas_items`. Réussite/erreur : icône (coche / point d'interrogation) en plus du texte, jamais la couleur seule ; étoiles pleines/vides par la forme. + +**Illustrations** (SVG vectoriels originaux, provisoires, générés par script jetable) : décors forêt (CE1) et marais (CE2) — le décor suit le niveau de la règle —, hibou Plume, étoiles, pictogrammes de types d'exercice, icônes de retour, 10 objets de collection. Résolution par identifiant dans un répertoire fixe (`ItemIcons`), repli sur un cadenas. + +**Écrans refaits** : carte (barre du haut : profil, titre, total d'étoiles, collection ; lieux = cartes avec pictogramme, titre, étoiles ; position fractionnelle étalée sur toute la zone et recalculée au redimensionnement ; Plume en bas), choix et création de profil, introduction de mission (Plume porte le texte narratif, doublon supprimé), hôte d'exercice (décor + carte de contenu, indicateur d'exercice en pastille), panneaux de règle et de retour, récompense (étoiles qui apparaissent, objets débloqués illustrés), collection (grille). + +**Animations** : `Motion` (apparition en rebond, fondu) respecte `SettingsManager.animations_enabled` (cahier des charges §11) ; l'écran de réglages qui le pilotera reste à faire (phase suivante). + +**Non vérifié** : tactile, clavier virtuel, rendu sur d'autres résolutions que 1152x648, animations en mouvement (les captures montrent l'état final), rendu sous Windows. diff --git a/docs/testing/manual-checklist.md b/docs/testing/manual-checklist.md index 6facfec..480ede4 100644 --- a/docs/testing/manual-checklist.md +++ b/docs/testing/manual-checklist.md @@ -24,6 +24,9 @@ Tout ce qui suit ne peut pas être vérifié sans écran (rendu, mise en page, a ## Accessibilité et interface +- Habillage (point 31 de `decisions.md`) : relire chaque écran (profils, carte CE1 et CE2, introduction, règle, les trois types d'exercice, retour juste/faux, récompense, collection) ; le rendu à 1152x648 a été relu sur captures, pas les autres tailles ni le tactile. Redimensionner la fenêtre : les lieux de la carte restent entiers et ne se chevauchent pas. +- Navigation : « Changer de profil » sur la carte ramène au choix du profil sans perte ; « Quitter la mission » demande confirmation, ne donne aucune étoile et ramène à la carte. + - Réussite et erreur ne doivent jamais se distinguer **uniquement** par la couleur (vérifier que le texte du titre du panneau de retour diffère bien : « Bravo ! » vs « Pas tout à fait »). - Taille et contraste du texte à vérifier sur un petit écran et un grand écran (redimensionner la fenêtre pendant que le jeu tourne, vérifier que les boutons restent utilisables et ne se chevauchent pas — conteneurs et ancres, pas de position fixe). - Boutons testés à la souris et au clavier (tabulation / entrée). Le tactile n'a pas pu être testé (pas d'export mobile à ce stade). diff --git a/project.godot b/project.godot index 3dc8b4d..ccb1f44 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="Les Aventuriers des mots" -config/version="0.2.0" +config/version="0.3.0" run/main_scene="res://scenes/boot/boot.tscn" config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg" @@ -25,3 +25,14 @@ ExerciseEngine="*res://autoload/exercise_engine.gd" SaveManager="*res://autoload/save_manager.gd" AudioManager="*res://autoload/audio_manager.gd" SettingsManager="*res://autoload/settings_manager.gd" + +[display] + +window/size/viewport_width=1152 +window/size/viewport_height=648 +window/stretch/mode="canvas_items" +window/stretch/aspect="expand" + +[gui] + +theme/custom="res://assets/interface/theme.tres" diff --git a/scenes/characters/guide_owl.gd b/scenes/characters/guide_owl.gd index 3b73cae..bb21405 100644 --- a/scenes/characters/guide_owl.gd +++ b/scenes/characters/guide_owl.gd @@ -1,15 +1,23 @@ extends Control -## Plume, le hibou guide : affiche une remarque ou un indice sous forme de -## texte. Portrait provisoire (forme simple, voir assets/CREDITS.md), a -## remplacer fichier par fichier sans toucher ce script. +## Plume, le hibou guide : affiche une remarque ou un indice dans une bulle. +## Illustration vectorielle provisoire (voir assets/CREDITS.md), a remplacer +## fichier par fichier sans toucher ce script. +@onready var _bubble: Control = $HBox/Bubble @onready var _dialogue_label: Label = %DialogueLabel +func _ready() -> void: + _bubble.visible = _dialogue_label.visible + + func say(text: String) -> void: _dialogue_label.text = text _dialogue_label.visible = true + _bubble.visible = true + Motion.fade_in(_bubble) func clear() -> void: _dialogue_label.visible = false + _bubble.visible = false diff --git a/scenes/characters/guide_owl.tscn b/scenes/characters/guide_owl.tscn index d016460..7f9090b 100644 --- a/scenes/characters/guide_owl.tscn +++ b/scenes/characters/guide_owl.tscn @@ -1,15 +1,16 @@ -[gd_scene load_steps=2 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scenes/characters/guide_owl.gd" id="1"] +[ext_resource type="Texture2D" path="res://assets/characters/owl_plume.svg" id="2"] [node name="GuideOwl" type="Control"] layout_mode = 3 size_flags_horizontal = 3 -custom_minimum_size = Vector2(0, 96) +custom_minimum_size = Vector2(0, 112) script = ExtResource("1") [node name="HBox" type="HBoxContainer" parent="."] -layout_mode = 2 +layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 @@ -17,15 +18,19 @@ grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 12 -[node name="Portrait" type="ColorRect" parent="HBox"] +[node name="Portrait" type="TextureRect" parent="HBox"] layout_mode = 2 -custom_minimum_size = Vector2(80, 80) -color = Color(0.56, 0.75, 0.42, 1) +custom_minimum_size = Vector2(104, 104) +size_flags_vertical = 4 +texture = ExtResource("2") +expand_mode = 1 +stretch_mode = 5 [node name="Bubble" type="PanelContainer" parent="HBox"] +theme_type_variation = &"SoftCard" layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 1 +size_flags_vertical = 4 [node name="DialogueLabel" type="Label" parent="HBox/Bubble"] unique_name_in_owner = true diff --git a/scenes/exercises/dictation.tscn b/scenes/exercises/dictation.tscn index fc218e5..e384984 100644 --- a/scenes/exercises/dictation.tscn +++ b/scenes/exercises/dictation.tscn @@ -29,6 +29,7 @@ theme_override_constants/separation = 16 [node name="InstructionLabel" type="Label" parent="Margin/VBox"] unique_name_in_owner = true +theme_type_variation = &"Emphasis" layout_mode = 2 autowrap_mode = 2 diff --git a/scenes/exercises/exercise_host.gd b/scenes/exercises/exercise_host.gd index 65c6084..767c536 100644 --- a/scenes/exercises/exercise_host.gd +++ b/scenes/exercises/exercise_host.gd @@ -29,6 +29,7 @@ var _pending_outcome := "" @onready var _exercise_container: Control = %ExerciseContainer @onready var _overlay_container: Control = %OverlayContainer @onready var _progress_label: Label = %ProgressLabel +@onready var _progress_pill: Control = %ProgressPill @onready var _abandon_button: Button = %AbandonButton @onready var _abandon_confirm_dialog: ConfirmationDialog = %AbandonConfirmDialog @@ -64,6 +65,7 @@ func _ready() -> void: _rule = ExerciseEngine.get_rule(_mission["rule_id"]) _skill_id = _rule.get("skill", "") _current_difficulty = ProgressManager.next_difficulty_for_skill(_skill_id, 1) + $Backdrop.kind = "marsh" if _rule.get("level", "CE1") == "CE2" else "forest" _show_rule_panel() @@ -114,6 +116,7 @@ func _load_next_exercise() -> void: ## peut faire redonner le meme exercice d'une mission a l'autre tentative : ## sans ce repere, ca ressemble a un blocage plutot qu'a une progression. func _update_progress_label() -> void: + _progress_pill.visible = true _progress_label.text = "%s %d/%d" % [ UIText.get_text("exercise_host.progress_label"), _completed_count + 1, MISSION_LENGTH ] diff --git a/scenes/exercises/exercise_host.tscn b/scenes/exercises/exercise_host.tscn index 945f03f..7aa79e0 100644 --- a/scenes/exercises/exercise_host.tscn +++ b/scenes/exercises/exercise_host.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scenes/exercises/exercise_host.gd" id="1"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] [node name="ExerciseHost" type="Control"] layout_mode = 3 @@ -11,12 +12,36 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +unique_name_in_owner = true + +[node name="ContentCard" type="Panel" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 24.0 +offset_top = 64.0 +offset_right = -24.0 +offset_bottom = -24.0 +grow_horizontal = 2 +grow_vertical = 2 + [node name="ExerciseContainer" type="Control" parent="."] unique_name_in_owner = true layout_mode = 1 anchor_right = 1.0 anchor_bottom = 1.0 -offset_top = 48.0 +offset_left = 40.0 +offset_top = 76.0 +offset_right = -40.0 +offset_bottom = -36.0 grow_horizontal = 2 grow_vertical = 2 @@ -33,7 +58,7 @@ mouse_filter = 2 [node name="HeaderBar" type="MarginContainer" parent="."] layout_mode = 1 anchor_right = 1.0 -offset_bottom = 48.0 +offset_bottom = 56.0 grow_horizontal = 2 theme_override_constants/margin_left = 16 theme_override_constants/margin_top = 8 @@ -45,16 +70,24 @@ layout_mode = 2 [node name="AbandonButton" type="Button" parent="HeaderBar/HBox"] unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" layout_mode = 2 [node name="Spacer" type="Control" parent="HeaderBar/HBox"] layout_mode = 2 size_flags_horizontal = 3 -[node name="ProgressLabel" type="Label" parent="HeaderBar/HBox"] +[node name="ProgressPill" type="PanelContainer" parent="HeaderBar/HBox"] unique_name_in_owner = true +visible = false +theme_type_variation = &"Pill" layout_mode = 2 -horizontal_alignment = 2 + +[node name="ProgressLabel" type="Label" parent="HeaderBar/HBox/ProgressPill"] +unique_name_in_owner = true +theme_type_variation = &"Emphasis" +layout_mode = 2 +horizontal_alignment = 1 [node name="AbandonConfirmDialog" type="ConfirmationDialog" parent="."] unique_name_in_owner = true diff --git a/scenes/exercises/multiple_choice.gd b/scenes/exercises/multiple_choice.gd index 6f3c7bd..6c53a1d 100644 --- a/scenes/exercises/multiple_choice.gd +++ b/scenes/exercises/multiple_choice.gd @@ -21,7 +21,8 @@ func load_exercise(exercise: Dictionary) -> void: for i in answers.size(): var button := Button.new() button.text = answers[i] - button.custom_minimum_size = Vector2(0, 56) + button.custom_minimum_size = Vector2(0, 64) + button.add_theme_font_size_override("font_size", 28) button.pressed.connect(_on_answer_pressed.bind(i)) _answers_box.add_child(button) diff --git a/scenes/exercises/multiple_choice.tscn b/scenes/exercises/multiple_choice.tscn index ed7b2cb..d4f0e47 100644 --- a/scenes/exercises/multiple_choice.tscn +++ b/scenes/exercises/multiple_choice.tscn @@ -29,6 +29,7 @@ theme_override_constants/separation = 16 [node name="InstructionLabel" type="Label" parent="Margin/VBox"] unique_name_in_owner = true +theme_type_variation = &"Emphasis" layout_mode = 2 autowrap_mode = 2 @@ -36,7 +37,7 @@ autowrap_mode = 2 unique_name_in_owner = true layout_mode = 2 autowrap_mode = 2 -theme_override_font_sizes/font_size = 24 +theme_type_variation = &"CardTitle" [node name="AnswersBox" type="VBoxContainer" parent="Margin/VBox"] unique_name_in_owner = true diff --git a/scenes/exercises/sentence_correction.gd b/scenes/exercises/sentence_correction.gd index 088b2ac..2ed80fb 100644 --- a/scenes/exercises/sentence_correction.gd +++ b/scenes/exercises/sentence_correction.gd @@ -33,6 +33,9 @@ func load_exercise(exercise: Dictionary) -> void: var button := Button.new() button.text = tokens[i]["text"] button.toggle_mode = true + button.theme_type_variation = &"SecondaryButton" + button.custom_minimum_size = Vector2(0, 60) + button.add_theme_font_size_override("font_size", 30) button.toggled.connect(_on_token_toggled.bind(i)) _tokens_box.add_child(button) diff --git a/scenes/exercises/sentence_correction.tscn b/scenes/exercises/sentence_correction.tscn index e8ae1c3..f158221 100644 --- a/scenes/exercises/sentence_correction.tscn +++ b/scenes/exercises/sentence_correction.tscn @@ -29,6 +29,7 @@ theme_override_constants/separation = 16 [node name="InstructionLabel" type="Label" parent="Margin/VBox"] unique_name_in_owner = true +theme_type_variation = &"Emphasis" layout_mode = 2 autowrap_mode = 2 diff --git a/scenes/profiles/profile_create.tscn b/scenes/profiles/profile_create.tscn index 21ad344..bafb7f6 100644 --- a/scenes/profiles/profile_create.tscn +++ b/scenes/profiles/profile_create.tscn @@ -1,7 +1,9 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=4 format=3] [ext_resource type="Script" path="res://scenes/profiles/profile_create.gd" id="1"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] + [sub_resource type="ButtonGroup" id="SchoolLevelGroup"] [node name="ProfileCreate" type="Control"] @@ -13,6 +15,14 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + [node name="Margin" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 @@ -25,58 +35,70 @@ theme_override_constants/margin_top = 32 theme_override_constants/margin_right = 32 theme_override_constants/margin_bottom = 32 -[node name="VBox" type="VBoxContainer" parent="Margin"] +[node name="Center" type="CenterContainer" parent="Margin"] layout_mode = 2 -theme_override_constants/separation = 16 -[node name="TitleLabel" type="Label" parent="Margin/VBox"] +[node name="Card" type="PanelContainer" parent="Margin/Center"] +layout_mode = 2 +custom_minimum_size = Vector2(640, 0) + +[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"] +layout_mode = 2 +theme_override_constants/separation = 14 + +[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 -theme_override_font_sizes/font_size = 32 +theme_type_variation = &"CardTitle" +horizontal_alignment = 1 -[node name="NameLabel" type="Label" parent="Margin/VBox"] +[node name="NameLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 -[node name="NameEdit" type="LineEdit" parent="Margin/VBox"] +[node name="NameEdit" type="LineEdit" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 custom_minimum_size = Vector2(0, 48) -[node name="ErrorLabel" type="Label" parent="Margin/VBox"] +[node name="ErrorLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 autowrap_mode = 2 -[node name="LevelLabel" type="Label" parent="Margin/VBox"] +[node name="LevelLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 -[node name="LevelBox" type="HBoxContainer" parent="Margin/VBox"] +[node name="LevelBox" type="HBoxContainer" parent="Margin/Center/Card/VBox"] layout_mode = 2 theme_override_constants/separation = 16 -[node name="CE1Button" type="Button" parent="Margin/VBox/LevelBox"] +[node name="CE1Button" type="Button" parent="Margin/Center/Card/VBox/LevelBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 custom_minimum_size = Vector2(0, 56) +theme_type_variation = &"SecondaryButton" toggle_mode = true button_group = SubResource("SchoolLevelGroup") -[node name="CE2Button" type="Button" parent="Margin/VBox/LevelBox"] +[node name="CE2Button" type="Button" parent="Margin/Center/Card/VBox/LevelBox"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 custom_minimum_size = Vector2(0, 56) +theme_type_variation = &"SecondaryButton" toggle_mode = true button_group = SubResource("SchoolLevelGroup") -[node name="ConfirmButton" type="Button" parent="Margin/VBox"] +[node name="ConfirmButton" type="Button" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 custom_minimum_size = Vector2(0, 56) -[node name="CancelButton" type="Button" parent="Margin/VBox"] +[node name="CancelButton" type="Button" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" layout_mode = 2 +custom_minimum_size = Vector2(0, 48) diff --git a/scenes/profiles/profile_select.gd b/scenes/profiles/profile_select.gd index 9db9691..82cb73d 100644 --- a/scenes/profiles/profile_select.gd +++ b/scenes/profiles/profile_select.gd @@ -41,14 +41,17 @@ func _refresh_profile_list() -> void: func _build_profile_row(summary: Dictionary) -> Control: var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 10) var select_button := Button.new() select_button.text = "%s (%s)" % [summary["display_name"], summary["school_level"]] select_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + select_button.custom_minimum_size = Vector2(0, 56) select_button.pressed.connect(_on_profile_selected.bind(summary["profile_id"])) row.add_child(select_button) var delete_button := Button.new() + delete_button.theme_type_variation = &"SecondaryButton" delete_button.text = UIText.get_text("profile_select.delete_button") delete_button.pressed.connect(_on_delete_requested.bind(summary["profile_id"])) row.add_child(delete_button) diff --git a/scenes/profiles/profile_select.tscn b/scenes/profiles/profile_select.tscn index 93470cf..cf6a0d4 100644 --- a/scenes/profiles/profile_select.tscn +++ b/scenes/profiles/profile_select.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scenes/profiles/profile_select.gd" id="1"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] [node name="ProfileSelect" type="Control"] layout_mode = 3 @@ -11,6 +12,14 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + [node name="Margin" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 @@ -23,31 +32,40 @@ theme_override_constants/margin_top = 32 theme_override_constants/margin_right = 32 theme_override_constants/margin_bottom = 32 -[node name="VBox" type="VBoxContainer" parent="Margin"] +[node name="Center" type="CenterContainer" parent="Margin"] +layout_mode = 2 + +[node name="Card" type="PanelContainer" parent="Margin/Center"] +layout_mode = 2 +custom_minimum_size = Vector2(720, 0) + +[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"] layout_mode = 2 theme_override_constants/separation = 16 -[node name="TitleLabel" type="Label" parent="Margin/VBox"] +[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 -theme_override_font_sizes/font_size = 32 +theme_type_variation = &"CardTitle" +horizontal_alignment = 1 -[node name="Scroll" type="ScrollContainer" parent="Margin/VBox"] +[node name="Scroll" type="ScrollContainer" parent="Margin/Center/Card/VBox"] layout_mode = 2 +custom_minimum_size = Vector2(0, 260) size_flags_vertical = 3 -[node name="ProfileList" type="VBoxContainer" parent="Margin/VBox/Scroll"] +[node name="ProfileList" type="VBoxContainer" parent="Margin/Center/Card/VBox/Scroll"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 theme_override_constants/separation = 8 -[node name="EmptyStateLabel" type="Label" parent="Margin/VBox"] +[node name="EmptyStateLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 autowrap_mode = 2 -[node name="NewProfileButton" type="Button" parent="Margin/VBox"] +[node name="NewProfileButton" type="Button" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 custom_minimum_size = Vector2(0, 56) diff --git a/scenes/ui/backdrop.gd b/scenes/ui/backdrop.gd new file mode 100644 index 0000000..84fe7bc --- /dev/null +++ b/scenes/ui/backdrop.gd @@ -0,0 +1,24 @@ +extends Control +## Decor plein ecran derriere un ecran (illustration vectorielle provisoire, +## voir assets/CREDITS.md). "forest" pour le CE1, "marsh" pour le CE2. + +const TEXTURES := { + "forest": "res://assets/environments/forest_backdrop.svg", + "marsh": "res://assets/environments/marsh_backdrop.svg", +} + +@export_enum("forest", "marsh") var kind: String = "forest": + set(value): + kind = value + _apply() + +@onready var _picture: TextureRect = $Picture + + +func _ready() -> void: + _apply() + + +func _apply() -> void: + if _picture != null: + _picture.texture = load(TEXTURES.get(kind, TEXTURES["forest"])) diff --git a/scenes/ui/backdrop.gd.uid b/scenes/ui/backdrop.gd.uid new file mode 100644 index 0000000..b84f4cb --- /dev/null +++ b/scenes/ui/backdrop.gd.uid @@ -0,0 +1 @@ +uid://xc0ialw4cfk0 diff --git a/scenes/ui/backdrop.tscn b/scenes/ui/backdrop.tscn new file mode 100644 index 0000000..8d3085e --- /dev/null +++ b/scenes/ui/backdrop.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scenes/ui/backdrop.gd" id="1"] + +[node name="Backdrop" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +script = ExtResource("1") + +[node name="Picture" type="TextureRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +expand_mode = 1 +stretch_mode = 6 diff --git a/scenes/ui/collection.gd b/scenes/ui/collection.gd index 445cec5..cd84042 100644 --- a/scenes/ui/collection.gd +++ b/scenes/ui/collection.gd @@ -2,9 +2,11 @@ extends Control ## Collection d'objets gagnes avec les etoiles. Un objet non debloque reste ## mysterieux mais indique comment l'obtenir (jamais de mystere frustrant). +const ICON_SIZE := 72.0 + @onready var _title_label: Label = %TitleLabel @onready var _progress_label: Label = %ProgressLabel -@onready var _items_box: VBoxContainer = %ItemsBox +@onready var _items_box: GridContainer = %ItemsBox @onready var _back_button: Button = %BackButton @@ -26,20 +28,39 @@ func _ready() -> void: ) +## Une ligne = pictogramme + texte (le Label s'appelle "TextLabel"). func _build_row(item: Dictionary, is_owned: bool) -> Control: + var card := PanelContainer.new() + card.theme_type_variation = &"SoftCard" + card.size_flags_horizontal = Control.SIZE_EXPAND_FILL + + var row := HBoxContainer.new() + row.add_theme_constant_override("separation", 12) + card.add_child(row) + + var icon := TextureRect.new() + icon.texture = ( + ItemIcons.texture_for(item["item_id"]) if is_owned else ItemIcons.locked_texture() + ) + icon.custom_minimum_size = Vector2(ICON_SIZE, ICON_SIZE) + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.size_flags_vertical = Control.SIZE_SHRINK_CENTER + row.add_child(icon) + var label := Label.new() + label.name = "TextLabel" label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - label.custom_minimum_size = Vector2(560, 0) + label.size_flags_horizontal = Control.SIZE_EXPAND_FILL + label.size_flags_vertical = Control.SIZE_SHRINK_CENTER if is_owned: - label.text = ( - "%s — %s\n%s" - % [item["title"], UIText.get_text("collection.owned_marker"), item["description"]] - ) + label.text = "%s\n%s" % [item["title"], item["description"]] else: label.text = ( "%s\n%s" % [UIText.get_text("collection.locked_title"), _unlock_hint(item["unlock"])] ) - return label + row.add_child(label) + return card func _unlock_hint(unlock: Dictionary) -> String: diff --git a/scenes/ui/collection.tscn b/scenes/ui/collection.tscn index bcddfa7..25bc27b 100644 --- a/scenes/ui/collection.tscn +++ b/scenes/ui/collection.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scenes/ui/collection.gd" id="1"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] [node name="Collection" type="Control"] layout_mode = 3 @@ -11,6 +12,14 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + [node name="Margin" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 @@ -18,37 +27,44 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_constants/margin_left = 24 +theme_override_constants/margin_left = 40 theme_override_constants/margin_top = 24 -theme_override_constants/margin_right = 24 +theme_override_constants/margin_right = 40 theme_override_constants/margin_bottom = 24 -[node name="VBox" type="VBoxContainer" parent="Margin"] +[node name="Card" type="PanelContainer" parent="Margin"] +layout_mode = 2 + +[node name="VBox" type="VBoxContainer" parent="Margin/Card"] layout_mode = 2 theme_override_constants/separation = 12 -[node name="TitleLabel" type="Label" parent="Margin/VBox"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_font_sizes/font_size = 32 -horizontal_alignment = 1 - -[node name="ProgressLabel" type="Label" parent="Margin/VBox"] +[node name="TitleLabel" type="Label" parent="Margin/Card/VBox"] unique_name_in_owner = true +theme_type_variation = &"CardTitle" layout_mode = 2 horizontal_alignment = 1 -[node name="Scroll" type="ScrollContainer" parent="Margin/VBox"] +[node name="ProgressLabel" type="Label" parent="Margin/Card/VBox"] +unique_name_in_owner = true +theme_type_variation = &"Emphasis" +layout_mode = 2 +horizontal_alignment = 1 + +[node name="Scroll" type="ScrollContainer" parent="Margin/Card/VBox"] layout_mode = 2 size_flags_vertical = 3 +horizontal_scroll_mode = 0 -[node name="ItemsBox" type="VBoxContainer" parent="Margin/VBox/Scroll"] +[node name="ItemsBox" type="GridContainer" parent="Margin/Card/VBox/Scroll"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 -theme_override_constants/separation = 16 +theme_override_constants/h_separation = 16 +theme_override_constants/v_separation = 12 +columns = 2 -[node name="BackButton" type="Button" parent="Margin/VBox"] +[node name="BackButton" type="Button" parent="Margin/Card/VBox"] unique_name_in_owner = true layout_mode = 2 custom_minimum_size = Vector2(0, 56) diff --git a/scenes/ui/feedback_panel.gd b/scenes/ui/feedback_panel.gd index 937e6f2..e7bc80f 100644 --- a/scenes/ui/feedback_panel.gd +++ b/scenes/ui/feedback_panel.gd @@ -5,6 +5,10 @@ extends Control signal continue_pressed +const ICON_CORRECT := preload("res://assets/interface/icon_correct.svg") +const ICON_TRY_AGAIN := preload("res://assets/interface/icon_try_again.svg") + +@onready var _icon: TextureRect = %Icon @onready var _title_label: Label = %TitleLabel @onready var _hint_box: VBoxContainer = %HintBox @onready var _hint_label: Label = %HintLabel @@ -21,6 +25,8 @@ func _ready() -> void: func show_result(result: CorrectionResult, support_level: AttemptFlow.SupportLevel) -> void: + _icon.texture = ICON_CORRECT if result.is_correct else ICON_TRY_AGAIN + Motion.pop_in(_icon) _title_label.text = ( UIText.get_text("feedback_panel.correct_title") if result.is_correct diff --git a/scenes/ui/feedback_panel.tscn b/scenes/ui/feedback_panel.tscn index 965a251..4013c22 100644 --- a/scenes/ui/feedback_panel.tscn +++ b/scenes/ui/feedback_panel.tscn @@ -31,7 +31,7 @@ grow_vertical = 2 [node name="Panel" type="PanelContainer" parent="CenterContainer"] layout_mode = 2 -custom_minimum_size = Vector2(480, 0) +custom_minimum_size = Vector2(560, 0) [node name="Margin" type="MarginContainer" parent="CenterContainer/Panel"] layout_mode = 2 @@ -44,10 +44,19 @@ theme_override_constants/margin_bottom = 24 layout_mode = 2 theme_override_constants/separation = 12 -[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"] +[node name="Icon" type="TextureRect" parent="CenterContainer/Panel/Margin/VBox"] unique_name_in_owner = true layout_mode = 2 -theme_override_font_sizes/font_size = 28 +custom_minimum_size = Vector2(72, 72) +size_flags_horizontal = 4 +expand_mode = 1 +stretch_mode = 5 + +[node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"] +unique_name_in_owner = true +theme_type_variation = &"CardTitle" +layout_mode = 2 +horizontal_alignment = 1 [node name="HintBox" type="VBoxContainer" parent="CenterContainer/Panel/Margin/VBox"] unique_name_in_owner = true diff --git a/scenes/ui/reward_screen.gd b/scenes/ui/reward_screen.gd index 7bcf810..05f7f46 100644 --- a/scenes/ui/reward_screen.gd +++ b/scenes/ui/reward_screen.gd @@ -5,23 +5,37 @@ extends Control ## au moins une etoile. Sauvegarde groupee ici, en fin de mission ## (docs/specs/decisions.md point 7). +const STAR_FULL := preload("res://assets/interface/star_full.svg") +const STAR_EMPTY := preload("res://assets/interface/star_empty.svg") +const STAR_SIZE := 88.0 +const ITEM_SIZE := 72.0 + +@onready var _backdrop: Control = %Backdrop @onready var _title_label: Label = %TitleLabel +@onready var _stars_row: HBoxContainer = %StarsRow @onready var _stars_label: Label = %StarsLabel @onready var _message_label: Label = %MessageLabel @onready var _record_label: Label = %RecordLabel @onready var _total_label: Label = %TotalLabel +@onready var _new_items_box: VBoxContainer = %NewItemsBox @onready var _new_items_label: Label = %NewItemsLabel +@onready var _new_items_row: HBoxContainer = %NewItemsRow @onready var _collection_button: Button = %CollectionButton @onready var _continue_button: Button = %ContinueButton func _ready() -> void: + var mission := ExerciseEngine.get_mission(GameManager.pending_mission_id) + var rule := ExerciseEngine.get_rule(mission.get("rule_id", "")) + _backdrop.kind = "marsh" if rule.get("level", "CE1") == "CE2" else "forest" + var summary := ProgressManager.complete_mission( GameManager.pending_mission_id, GameManager.last_outcomes ) ProgressManager.save_now() _title_label.text = UIText.get_text("reward_screen.title") + _show_stars(summary["stars"]) _stars_label.text = ( "%s %d/%d" % [UIText.get_text("reward_screen.stars_label"), summary["stars"], MissionScoring.MAX_STARS] @@ -32,17 +46,7 @@ func _ready() -> void: _total_label.text = ( "%s %d" % [UIText.get_text("reward_screen.total_label"), summary["total_stars"]] ) - - var new_items: Array = summary["new_items"] - _new_items_label.visible = not new_items.is_empty() - if not new_items.is_empty(): - var titles: Array[String] = [] - for item in ExerciseEngine.get_reward_items(): - if new_items.has(item["item_id"]): - titles.append(item["title"]) - _new_items_label.text = ( - "%s\n%s" % [UIText.get_text("reward_screen.new_items_label"), "\n".join(titles)] - ) + _show_new_items(summary["new_items"]) _collection_button.text = UIText.get_text("reward_screen.collection_button") _collection_button.pressed.connect(_on_collection_pressed) @@ -50,6 +54,45 @@ func _ready() -> void: _continue_button.pressed.connect(_on_continue_pressed) +func _show_stars(earned: int) -> void: + for i in MissionScoring.MAX_STARS: + var star := TextureRect.new() + star.texture = STAR_FULL if i < earned else STAR_EMPTY + star.custom_minimum_size = Vector2(STAR_SIZE, STAR_SIZE) + star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + _stars_row.add_child(star) + if i < earned: + Motion.pop_in(star, 0.25 + 0.2 * i) + + +func _show_new_items(new_items: Array) -> void: + _new_items_box.visible = not new_items.is_empty() + if new_items.is_empty(): + return + _new_items_label.text = UIText.get_text("reward_screen.new_items_label") + var index := 0 + for item in ExerciseEngine.get_reward_items(): + if not new_items.has(item["item_id"]): + continue + var holder := VBoxContainer.new() + var icon := TextureRect.new() + icon.texture = ItemIcons.texture_for(item["item_id"]) + icon.custom_minimum_size = Vector2(ITEM_SIZE, ITEM_SIZE) + icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + icon.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + holder.add_child(icon) + var name_label := Label.new() + name_label.text = item["title"] + name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + name_label.theme_type_variation = &"Small" + holder.add_child(name_label) + _new_items_row.add_child(holder) + Motion.pop_in(holder, 0.8 + 0.15 * index) + index += 1 + + func _on_collection_pressed() -> void: GameManager.pending_mission_id = "" GameManager.goto(GameManager.SCENE_COLLECTION) diff --git a/scenes/ui/reward_screen.tscn b/scenes/ui/reward_screen.tscn index b8100cd..833e506 100644 --- a/scenes/ui/reward_screen.tscn +++ b/scenes/ui/reward_screen.tscn @@ -1,6 +1,8 @@ -[gd_scene load_steps=2 format=3] +[gd_scene load_steps=4 format=3] [ext_resource type="Script" path="res://scenes/ui/reward_screen.gd" id="1"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] +[ext_resource type="Texture2D" path="res://assets/characters/owl_plume.svg" id="3"] [node name="RewardScreen" type="Control"] layout_mode = 3 @@ -11,59 +13,104 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") -[node name="CenterContainer" type="CenterContainer" parent="."] +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 - -[node name="VBox" type="VBoxContainer" parent="CenterContainer"] -layout_mode = 2 -theme_override_constants/separation = 20 - -[node name="TitleLabel" type="Label" parent="CenterContainer/VBox"] unique_name_in_owner = true -layout_mode = 2 -theme_override_font_sizes/font_size = 36 -horizontal_alignment = 1 -[node name="StarsLabel" type="Label" parent="CenterContainer/VBox"] +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 32 +theme_override_constants/margin_top = 24 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 24 + +[node name="Center" type="CenterContainer" parent="Margin"] +layout_mode = 2 + +[node name="Card" type="PanelContainer" parent="Margin/Center"] +layout_mode = 2 +custom_minimum_size = Vector2(720, 0) + +[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"] +layout_mode = 2 +theme_override_constants/separation = 12 + +[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true +theme_type_variation = &"CardTitle" layout_mode = 2 horizontal_alignment = 1 -[node name="MessageLabel" type="Label" parent="CenterContainer/VBox"] +[node name="StarsRow" type="HBoxContainer" parent="Margin/Center/Card/VBox"] +unique_name_in_owner = true +layout_mode = 2 +alignment = 1 +theme_override_constants/separation = 10 + +[node name="StarsLabel" type="Label" parent="Margin/Center/Card/VBox"] +unique_name_in_owner = true +theme_type_variation = &"Emphasis" +layout_mode = 2 +horizontal_alignment = 1 + +[node name="MessageLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 horizontal_alignment = 1 autowrap_mode = 2 -custom_minimum_size = Vector2(480, 0) -[node name="RecordLabel" type="Label" parent="CenterContainer/VBox"] +[node name="RecordLabel" type="Label" parent="Margin/Center/Card/VBox"] +unique_name_in_owner = true +theme_type_variation = &"Emphasis" +layout_mode = 2 +horizontal_alignment = 1 + +[node name="TotalLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 horizontal_alignment = 1 -[node name="TotalLabel" type="Label" parent="CenterContainer/VBox"] +[node name="NewItemsBox" type="VBoxContainer" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true layout_mode = 2 -horizontal_alignment = 1 +theme_override_constants/separation = 6 -[node name="NewItemsLabel" type="Label" parent="CenterContainer/VBox"] +[node name="NewItemsLabel" type="Label" parent="Margin/Center/Card/VBox/NewItemsBox"] unique_name_in_owner = true +theme_type_variation = &"Emphasis" layout_mode = 2 horizontal_alignment = 1 autowrap_mode = 2 -custom_minimum_size = Vector2(480, 0) -[node name="CollectionButton" type="Button" parent="CenterContainer/VBox"] +[node name="NewItemsRow" type="HBoxContainer" parent="Margin/Center/Card/VBox/NewItemsBox"] unique_name_in_owner = true layout_mode = 2 -custom_minimum_size = Vector2(200, 56) +alignment = 1 +theme_override_constants/separation = 16 -[node name="ContinueButton" type="Button" parent="CenterContainer/VBox"] +[node name="Buttons" type="HBoxContainer" parent="Margin/Center/Card/VBox"] +layout_mode = 2 +theme_override_constants/separation = 16 + +[node name="CollectionButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"] +unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" +layout_mode = 2 +size_flags_horizontal = 3 +custom_minimum_size = Vector2(0, 56) + +[node name="ContinueButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"] unique_name_in_owner = true layout_mode = 2 -custom_minimum_size = Vector2(200, 56) +size_flags_horizontal = 3 +custom_minimum_size = Vector2(0, 56) diff --git a/scenes/ui/rule_panel.tscn b/scenes/ui/rule_panel.tscn index 3103131..f883875 100644 --- a/scenes/ui/rule_panel.tscn +++ b/scenes/ui/rule_panel.tscn @@ -31,7 +31,7 @@ grow_vertical = 2 [node name="Panel" type="PanelContainer" parent="CenterContainer"] layout_mode = 2 -custom_minimum_size = Vector2(480, 0) +custom_minimum_size = Vector2(560, 0) [node name="Margin" type="MarginContainer" parent="CenterContainer/Panel"] layout_mode = 2 @@ -46,8 +46,9 @@ theme_override_constants/separation = 16 [node name="TitleLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"] unique_name_in_owner = true +theme_type_variation = &"CardTitle" layout_mode = 2 -theme_override_font_sizes/font_size = 28 +horizontal_alignment = 1 [node name="StatementLabel" type="Label" parent="CenterContainer/Panel/Margin/VBox"] unique_name_in_owner = true diff --git a/scenes/world/mission_intro.gd b/scenes/world/mission_intro.gd index 52ecc3a..80c3cf7 100644 --- a/scenes/world/mission_intro.gd +++ b/scenes/world/mission_intro.gd @@ -4,7 +4,6 @@ extends Control ## (voir "Points durs" du prompt de lancement, Sens de l'aventure). @onready var _title_label: Label = %TitleLabel -@onready var _narrative_label: Label = %NarrativeLabel @onready var _guide: Control = %Guide @onready var _start_button: Button = %StartButton @onready var _back_button: Button = %BackButton @@ -13,8 +12,9 @@ extends Control func _ready() -> void: var mission := ExerciseEngine.get_mission(GameManager.pending_mission_id) _title_label.text = mission.get("title", "") - _narrative_label.text = mission.get("narrative_context", "") _guide.say(mission.get("narrative_context", "")) + var rule := ExerciseEngine.get_rule(mission.get("rule_id", "")) + $Backdrop.kind = "marsh" if rule.get("level", "CE1") == "CE2" else "forest" _start_button.text = UIText.get_text("mission_intro.start_button") _back_button.text = UIText.get_text("mission_intro.back_button") diff --git a/scenes/world/mission_intro.tscn b/scenes/world/mission_intro.tscn index e655a08..0721bcb 100644 --- a/scenes/world/mission_intro.tscn +++ b/scenes/world/mission_intro.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=4 format=3] [ext_resource type="Script" path="res://scenes/world/mission_intro.gd" id="1"] [ext_resource type="PackedScene" path="res://scenes/characters/guide_owl.tscn" id="2"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] [node name="MissionIntro" type="Control"] layout_mode = 3 @@ -12,6 +13,14 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + [node name="Margin" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 @@ -19,40 +28,45 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_constants/margin_left = 32 -theme_override_constants/margin_top = 32 -theme_override_constants/margin_right = 32 -theme_override_constants/margin_bottom = 32 +theme_override_constants/margin_left = 48 +theme_override_constants/margin_top = 40 +theme_override_constants/margin_right = 48 +theme_override_constants/margin_bottom = 40 -[node name="VBox" type="VBoxContainer" parent="Margin"] +[node name="Center" type="CenterContainer" parent="Margin"] layout_mode = 2 -theme_override_constants/separation = 16 -[node name="TitleLabel" type="Label" parent="Margin/VBox"] -unique_name_in_owner = true -layout_mode = 2 -theme_override_font_sizes/font_size = 32 - -[node name="NarrativeLabel" type="Label" parent="Margin/VBox"] +[node name="Card" type="PanelContainer" parent="Margin/Center"] +layout_mode = 2 +custom_minimum_size = Vector2(760, 0) + +[node name="VBox" type="VBoxContainer" parent="Margin/Center/Card"] +layout_mode = 2 +theme_override_constants/separation = 18 + +[node name="TitleLabel" type="Label" parent="Margin/Center/Card/VBox"] unique_name_in_owner = true +theme_type_variation = &"CardTitle" layout_mode = 2 +horizontal_alignment = 1 autowrap_mode = 2 -size_flags_vertical = 3 -[node name="Guide" type="Control" parent="Margin/VBox" instance=ExtResource("2")] +[node name="Guide" type="Control" parent="Margin/Center/Card/VBox" instance=ExtResource("2")] unique_name_in_owner = true layout_mode = 2 +custom_minimum_size = Vector2(0, 150) -[node name="Buttons" type="HBoxContainer" parent="Margin/VBox"] +[node name="Buttons" type="HBoxContainer" parent="Margin/Center/Card/VBox"] layout_mode = 2 theme_override_constants/separation = 16 -[node name="BackButton" type="Button" parent="Margin/VBox/Buttons"] +[node name="BackButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"] unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" layout_mode = 2 custom_minimum_size = Vector2(0, 56) -[node name="StartButton" type="Button" parent="Margin/VBox/Buttons"] +[node name="StartButton" type="Button" parent="Margin/Center/Card/VBox/Buttons"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 diff --git a/scenes/world/mission_marker.gd b/scenes/world/mission_marker.gd new file mode 100644 index 0000000..40a5fb2 --- /dev/null +++ b/scenes/world/mission_marker.gd @@ -0,0 +1,37 @@ +extends Button +## Un lieu de la carte : pictogramme du type d'exercice, titre, etoiles +## gagnees (pleines/vides : la forme porte l'information, pas la couleur) et +## etat de la competence. Scene autonome : la carte la configure et ecoute +## `pressed`. + +const STAR_FULL := preload("res://assets/interface/star_full.svg") +const STAR_EMPTY := preload("res://assets/interface/star_empty.svg") +const TYPE_ICONS := { + "multiple_choice": "res://assets/interface/icon_multiple_choice.svg", + "sentence_correction": "res://assets/interface/icon_sentence_correction.svg", + "dictation": "res://assets/interface/icon_dictation.svg", +} +const STAR_SIZE := 24.0 + +@onready var _type_icon: TextureRect = %TypeIcon +@onready var _title_label: Label = %TitleLabel +@onready var _stars_row: HBoxContainer = %StarsRow +@onready var _status_label: Label = %StatusLabel + + +func setup(mission: Dictionary, stars: int, max_stars: int, status_text: String) -> void: + _title_label.text = mission["title"] + _status_label.text = status_text + _status_label.visible = not status_text.is_empty() + _type_icon.texture = load(TYPE_ICONS.get(mission["exercise_type"], TYPE_ICONS["multiple_choice"])) + + for child in _stars_row.get_children(): + child.queue_free() + for i in max_stars: + var star := TextureRect.new() + star.texture = STAR_FULL if i < stars else STAR_EMPTY + star.custom_minimum_size = Vector2(STAR_SIZE, STAR_SIZE) + star.expand_mode = TextureRect.EXPAND_IGNORE_SIZE + star.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED + star.mouse_filter = Control.MOUSE_FILTER_IGNORE + _stars_row.add_child(star) diff --git a/scenes/world/mission_marker.gd.uid b/scenes/world/mission_marker.gd.uid new file mode 100644 index 0000000..9d044cf --- /dev/null +++ b/scenes/world/mission_marker.gd.uid @@ -0,0 +1 @@ +uid://cd6db5ude6pwq diff --git a/scenes/world/mission_marker.tscn b/scenes/world/mission_marker.tscn new file mode 100644 index 0000000..2fd7ade --- /dev/null +++ b/scenes/world/mission_marker.tscn @@ -0,0 +1,59 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://scenes/world/mission_marker.gd" id="1"] +[ext_resource type="Texture2D" path="res://assets/interface/star_empty.svg" id="2"] + +[node name="MissionMarker" type="Button"] +theme_type_variation = &"MarkerButton" +custom_minimum_size = Vector2(220, 120) +script = ExtResource("1") + +[node name="Content" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 2 +theme_override_constants/separation = 2 +alignment = 1 + +[node name="Header" type="HBoxContainer" parent="Content"] +layout_mode = 2 +mouse_filter = 2 +theme_override_constants/separation = 8 +alignment = 1 + +[node name="TypeIcon" type="TextureRect" parent="Content/Header"] +unique_name_in_owner = true +layout_mode = 2 +custom_minimum_size = Vector2(36, 36) +size_flags_vertical = 4 +mouse_filter = 2 +expand_mode = 1 +stretch_mode = 5 + +[node name="TitleLabel" type="Label" parent="Content/Header"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +mouse_filter = 2 +theme_override_font_sizes/font_size = 18 +autowrap_mode = 3 +max_lines_visible = 3 +text_overrun_behavior = 3 +vertical_alignment = 1 + +[node name="StarsRow" type="HBoxContainer" parent="Content"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +alignment = 1 + +[node name="StatusLabel" type="Label" parent="Content"] +unique_name_in_owner = true +layout_mode = 2 +mouse_filter = 2 +theme_type_variation = &"Small" +horizontal_alignment = 1 diff --git a/scenes/world/world_map.gd b/scenes/world/world_map.gd index fe9be89..4a9d6b0 100644 --- a/scenes/world/world_map.gd +++ b/scenes/world/world_map.gd @@ -3,20 +3,22 @@ extends Control ## (cahier des charges §15) se limitait a trois lieux ; le perimetre a ete ## etendu (docs/specs/decisions.md, points 25 et 27). La position de chaque ## mission vient de data/missions/*.json (fractions 0..1 de la zone de -## carte), jamais d'une position fixe en dur dans le script. Un profil CE2 -## peut basculer sur la carte CE1 pour revoir les notions precedentes. +## carte), jamais d'une position fixe en dur dans le script : chaque lieu est +## replace quand la zone change de taille. Un profil CE2 peut basculer sur la +## carte CE1 pour revoir les notions precedentes. -const MARKER_HALF_WIDTH := 80.0 -const MARKER_HALF_HEIGHT := 48.0 +const MARKER_SCENE := preload("res://scenes/world/mission_marker.tscn") var _viewed_level := "CE1" +@onready var _backdrop: Control = %Backdrop @onready var _title_label: Label = %TitleLabel @onready var _map_area: Control = %MapArea @onready var _level_toggle_button: Button = %LevelToggleButton @onready var _total_stars_label: Label = %TotalStarsLabel @onready var _collection_button: Button = %CollectionButton @onready var _profile_button: Button = %ProfileButton +@onready var _guide: Control = %Guide func _ready() -> void: @@ -27,19 +29,19 @@ func _ready() -> void: _collection_button.pressed.connect(_on_collection_pressed) _profile_button.text = UIText.get_text("world_map.change_profile_button") _profile_button.pressed.connect(_on_change_profile_pressed) + _map_area.resized.connect(_layout_markers) + _guide.say(UIText.get_text("world_map.guide_hint")) _refresh() func _refresh() -> void: - var title_key := "world_map.title_ce2" if _viewed_level == "CE2" else "world_map.title" - _title_label.text = UIText.get_text(title_key) - var toggle_key := ( - "world_map.review_ce1_button" if _viewed_level == "CE2" else "world_map.back_to_ce2_button" - ) - _level_toggle_button.text = UIText.get_text(toggle_key) - _total_stars_label.text = ( - "%s %d" % [UIText.get_text("world_map.total_stars_label"), ProgressManager.stars()] + var is_ce2 := _viewed_level == "CE2" + _backdrop.kind = "marsh" if is_ce2 else "forest" + _title_label.text = UIText.get_text("world_map.title_ce2" if is_ce2 else "world_map.title") + _level_toggle_button.text = UIText.get_text( + "world_map.review_ce1_button" if is_ce2 else "world_map.back_to_ce2_button" ) + _total_stars_label.text = str(ProgressManager.stars()) _populate_missions() @@ -48,8 +50,64 @@ func _populate_missions() -> void: _map_area.remove_child(child) child.queue_free() + var index := 0 for mission: Dictionary in ExerciseEngine.get_missions_for_level(_viewed_level): - _map_area.add_child(_build_marker(mission)) + var marker: Button = MARKER_SCENE.instantiate() + _map_area.add_child(marker) + marker.set_meta("map_position", mission["map_position"]) + marker.setup( + mission, + ProgressManager.mission_best_stars(mission["mission_id"]), + MissionScoring.MAX_STARS, + _status_label_for(mission) + ) + marker.pressed.connect(_on_mission_pressed.bind(mission["mission_id"])) + Motion.pop_in(marker, 0.04 * index) + index += 1 + _layout_markers() + + +## Replace chaque lieu selon sa position fractionnelle. Les positions du +## niveau affiche sont etalees sur toute la zone (la plus petite colle au +## bord, la plus grande au bord oppose) : les lieux restent entierement +## visibles et ne se chevauchent pas, quelle que soit la taille de la fenetre. +func _layout_markers() -> void: + var markers: Array[Control] = [] + var low := Vector2(INF, INF) + var high := Vector2(-INF, -INF) + for child in _map_area.get_children(): + var control := child as Control + if control == null or not control.has_meta("map_position"): + continue + var fraction: Dictionary = control.get_meta("map_position") + var point := Vector2(float(fraction["x"]), float(fraction["y"])) + low = Vector2(minf(low.x, point.x), minf(low.y, point.y)) + high = Vector2(maxf(high.x, point.x), maxf(high.y, point.y)) + markers.append(control) + + for control in markers: + var fraction: Dictionary = control.get_meta("map_position") + var free_space := _map_area.size - control.size + control.position = Vector2( + _spread(float(fraction["x"]), low.x, high.x) * maxf(free_space.x, 0.0), + _spread(float(fraction["y"]), low.y, high.y) * maxf(free_space.y, 0.0) + ) + + +func _spread(value: float, low: float, high: float) -> float: + if high - low < 0.0001: + return 0.5 + return (value - low) / (high - low) + + +func _status_label_for(mission: Dictionary) -> String: + var rule: Dictionary = ExerciseEngine.get_rule(mission["rule_id"]) + var skill_id: String = rule.get("skill", "") + var skill_state := ProgressManager.get_skill_state(skill_id) + var status: String = skill_state["status"] + if status == "decouverte": + return "" + return UIText.get_text("world_map.status_%s" % status) ## Revient au choix du profil sans rien perdre : la progression est ecrite @@ -68,44 +126,6 @@ func _on_level_toggle_pressed() -> void: _refresh() -func _build_marker(mission: Dictionary) -> Control: - var button := Button.new() - button.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - button.text = ( - "%s\n%s\n%s %d/%d" - % [ - mission["title"], - _status_label_for(mission), - UIText.get_text("world_map.mission_stars_label"), - ProgressManager.mission_best_stars(mission["mission_id"]), - MissionScoring.MAX_STARS, - ] - ) - - var map_position: Dictionary = mission["map_position"] - var x: float = map_position["x"] - var y: float = map_position["y"] - button.anchor_left = x - button.anchor_right = x - button.anchor_top = y - button.anchor_bottom = y - button.offset_left = -MARKER_HALF_WIDTH - button.offset_right = MARKER_HALF_WIDTH - button.offset_top = -MARKER_HALF_HEIGHT - button.offset_bottom = MARKER_HALF_HEIGHT - - button.pressed.connect(_on_mission_pressed.bind(mission["mission_id"])) - return button - - -func _status_label_for(mission: Dictionary) -> String: - var rule: Dictionary = ExerciseEngine.get_rule(mission["rule_id"]) - var skill_id: String = rule.get("skill", "") - var skill_state := ProgressManager.get_skill_state(skill_id) - var status: String = skill_state["status"] - return UIText.get_text("world_map.status_%s" % status) - - func _on_mission_pressed(mission_id: String) -> void: GameManager.pending_mission_id = mission_id GameManager.goto(GameManager.SCENE_MISSION_INTRO) diff --git a/scenes/world/world_map.tscn b/scenes/world/world_map.tscn index b071b46..7e14e66 100644 --- a/scenes/world/world_map.tscn +++ b/scenes/world/world_map.tscn @@ -1,7 +1,9 @@ -[gd_scene load_steps=3 format=3] +[gd_scene load_steps=5 format=3] [ext_resource type="Script" path="res://scenes/world/world_map.gd" id="1"] [ext_resource type="PackedScene" path="res://scenes/characters/guide_owl.tscn" id="2"] +[ext_resource type="PackedScene" path="res://scenes/ui/backdrop.tscn" id="backdrop"] +[ext_resource type="Texture2D" path="res://assets/interface/star_full.svg" id="3"] [node name="WorldMap" type="Control"] layout_mode = 3 @@ -12,55 +14,89 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1") -[node name="Background" type="ColorRect" parent="."] +[node name="Backdrop" parent="." instance=ExtResource("backdrop")] +unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -color = Color(0.3, 0.52, 0.33, 1) -[node name="VBox" type="VBoxContainer" parent="."] +[node name="Margin" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 14 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 10 + +[node name="VBox" type="VBoxContainer" parent="Margin"] +layout_mode = 2 +theme_override_constants/separation = 8 + +[node name="TopBar" type="HBoxContainer" parent="Margin/VBox"] +layout_mode = 2 theme_override_constants/separation = 12 -[node name="TitleLabel" type="Label" parent="VBox"] +[node name="ProfileButton" type="Button" parent="Margin/VBox/TopBar"] unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" layout_mode = 2 -theme_override_font_sizes/font_size = 32 +custom_minimum_size = Vector2(0, 52) + +[node name="TitleLabel" type="Label" parent="Margin/VBox/TopBar"] +unique_name_in_owner = true +theme_type_variation = &"HeaderTitle" +layout_mode = 2 +size_flags_horizontal = 3 horizontal_alignment = 1 -[node name="TotalStarsLabel" type="Label" parent="VBox"] +[node name="StarsBadge" type="PanelContainer" parent="Margin/VBox/TopBar"] +theme_type_variation = &"Pill" +layout_mode = 2 + +[node name="Row" type="HBoxContainer" parent="Margin/VBox/TopBar/StarsBadge"] +layout_mode = 2 + +[node name="StarIcon" type="TextureRect" parent="Margin/VBox/TopBar/StarsBadge/Row"] +layout_mode = 2 +custom_minimum_size = Vector2(34, 34) +size_flags_vertical = 4 +texture = ExtResource("3") +expand_mode = 1 +stretch_mode = 5 + +[node name="TotalStarsLabel" type="Label" parent="Margin/VBox/TopBar/StarsBadge/Row"] +unique_name_in_owner = true +theme_type_variation = &"Emphasis" +layout_mode = 2 + +[node name="CollectionButton" type="Button" parent="Margin/VBox/TopBar"] unique_name_in_owner = true layout_mode = 2 -horizontal_alignment = 1 +custom_minimum_size = Vector2(0, 52) -[node name="MapArea" type="Control" parent="VBox"] +[node name="MapArea" type="Control" parent="Margin/VBox"] unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 -[node name="LevelToggleButton" type="Button" parent="VBox"] -unique_name_in_owner = true +[node name="BottomBar" type="HBoxContainer" parent="Margin/VBox"] layout_mode = 2 -custom_minimum_size = Vector2(0, 48) +theme_override_constants/separation = 12 -[node name="CollectionButton" type="Button" parent="VBox"] +[node name="Guide" type="Control" parent="Margin/VBox/BottomBar" instance=ExtResource("2")] unique_name_in_owner = true layout_mode = 2 -custom_minimum_size = Vector2(0, 48) -[node name="ProfileButton" type="Button" parent="VBox"] -unique_name_in_owner = true -layout_mode = 2 -custom_minimum_size = Vector2(0, 48) - -[node name="Guide" type="Control" parent="VBox" instance=ExtResource("2")] +[node name="LevelToggleButton" type="Button" parent="Margin/VBox/BottomBar"] unique_name_in_owner = true +theme_type_variation = &"SecondaryButton" layout_mode = 2 +size_flags_vertical = 4 +custom_minimum_size = Vector2(0, 52) diff --git a/scripts/data/item_icons.gd b/scripts/data/item_icons.gd new file mode 100644 index 0000000..c87f4a5 --- /dev/null +++ b/scripts/data/item_icons.gd @@ -0,0 +1,20 @@ +class_name ItemIcons +extends RefCounted +## Illustration d'un objet de la collection, resolue par identifiant dans un +## repertoire fixe (CLAUDE.md : jamais de chemin libre dans les donnees). Un +## objet sans illustration retombe sur un cadenas : jamais d'erreur a l'ecran. + +const ITEM_DIR := "res://assets/interface/items" +const FALLBACK := "res://assets/interface/icon_locked.svg" +const LOCKED := "res://assets/interface/icon_locked.svg" + + +static func texture_for(item_id: String) -> Texture2D: + var path := "%s/%s.svg" % [ITEM_DIR, item_id] + if ResourceLoader.exists(path): + return load(path) + return load(FALLBACK) + + +static func locked_texture() -> Texture2D: + return load(LOCKED) diff --git a/scripts/data/item_icons.gd.uid b/scripts/data/item_icons.gd.uid new file mode 100644 index 0000000..03ec59a --- /dev/null +++ b/scripts/data/item_icons.gd.uid @@ -0,0 +1 @@ +uid://b4y5au3wsxe00 diff --git a/scripts/ui/motion.gd b/scripts/ui/motion.gd new file mode 100644 index 0000000..3401e74 --- /dev/null +++ b/scripts/ui/motion.gd @@ -0,0 +1,26 @@ +class_name Motion +extends RefCounted +## Petites animations d'interface (apparition, rebond). Toutes respectent le +## reglage d'animations (cahier des charges §11 : possibilite de les +## desactiver) : elles ne font alors rien et l'element est affiche tel quel. + + +## Fait grossir `node` depuis rien avec un leger rebond. +static func pop_in(node: Control, delay: float = 0.0) -> void: + if not SettingsManager.animations_enabled: + return + node.scale = Vector2.ZERO + await node.get_tree().process_frame + node.pivot_offset = node.size / 2.0 + var tween := node.create_tween() + tween.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) + tween.tween_property(node, "scale", Vector2.ONE, 0.4).set_delay(delay) + + +## Fait apparaitre `node` en fondu. +static func fade_in(node: CanvasItem, duration: float = 0.25) -> void: + if not SettingsManager.animations_enabled: + return + node.modulate.a = 0.0 + var tween := node.create_tween() + tween.tween_property(node, "modulate:a", 1.0, duration) diff --git a/scripts/ui/motion.gd.uid b/scripts/ui/motion.gd.uid new file mode 100644 index 0000000..6840dfe --- /dev/null +++ b/scripts/ui/motion.gd.uid @@ -0,0 +1 @@ +uid://bjlpq2eeaynf2 diff --git a/tests/exercises/test_exercise_host_abandon.gd.uid b/tests/exercises/test_exercise_host_abandon.gd.uid new file mode 100644 index 0000000..e3d1757 --- /dev/null +++ b/tests/exercises/test_exercise_host_abandon.gd.uid @@ -0,0 +1 @@ +uid://bgjhnrqowck7h diff --git a/tests/world/test_reward_flow.gd b/tests/world/test_reward_flow.gd index 9c78032..98efd90 100644 --- a/tests/world/test_reward_flow.gd +++ b/tests/world/test_reward_flow.gd @@ -83,5 +83,10 @@ func test_collection_lists_owned_and_mysterious_items() -> void: var rows := collection.get_node("%ItemsBox").get_children() assert_eq(rows.size(), ExerciseEngine.get_reward_items().size()) - assert_string_contains((rows[0] as Label).text, "Graine magique") - assert_string_contains((rows[1] as Label).text, UIText.get_text("collection.locked_title")) + assert_string_contains( + (rows[0].find_child("TextLabel", true, false) as Label).text, "Graine magique" + ) + assert_string_contains( + (rows[1].find_child("TextLabel", true, false) as Label).text, + UIText.get_text("collection.locked_title") + ) diff --git a/tools/screenshot.gd b/tools/screenshot.gd new file mode 100644 index 0000000..c54052b --- /dev/null +++ b/tools/screenshot.gd @@ -0,0 +1,72 @@ +extends SceneTree +## Outil de developpement : charge une scene, attend quelques images puis +## enregistre le rendu en PNG. Usage (fenetre reelle, pas --headless) : +## godot --path . --resolution 1152x648 -s res://tools/screenshot.gd -- \ +## res://scenes/world/world_map.tscn out.png [CE1|CE2] [mission_id] +## Cree un profil temporaire pour que les scenes qui lisent la progression +## fonctionnent, puis le supprime. + + +func _initialize() -> void: + var args := OS.get_cmdline_user_args() + var scene_path: String = args[0] + var out_path: String = args[1] + var level: String = args[2] if args.size() > 2 else "CE1" + + var profiles: Node = root.get_node("ProfileManager") + var game: Node = root.get_node("GameManager") + var profile_id: String = profiles.call("create_profile", "Capture", level) + profiles.call("select_profile", profile_id) + if args.size() > 3: + game.set("pending_mission_id", args[3]) + + for arg in args: + if arg.begins_with("outcomes:"): + var typed: Array[String] = [] + for part in arg.trim_prefix("outcomes:").split(","): + typed.append(part) + game.set("last_outcomes", typed) + + var scene: Node = load(scene_path).instantiate() + root.add_child(scene) + for i in 5: + await process_frame + # Actions optionnelles sur exercise_host, a partir du 5e argument : + # continue_rule | answer: | toggle: | type: | validate + # | continue_feedback + for action in args.slice(4): + if action.begins_with("outcomes:"): + continue + await _run_action(scene, action) + for i in 5: + await process_frame + for i in 90: + await process_frame + var image := root.get_texture().get_image() + image.save_png(out_path) + profiles.call("delete_profile", profile_id) + quit(0) + + +func _run_action(scene: Node, action: String) -> void: + var overlay: Node = scene.get_node("%OverlayContainer") + var exercise: Node = scene.get_node("%ExerciseContainer").get_child(0) if scene.get_node("%ExerciseContainer").get_child_count() > 0 else null + var parts := action.split(":", true, 1) + match parts[0]: + "continue_rule": + overlay.get_child(0).emit_signal("continued") + "answer": + exercise.emit_signal("answer_submitted", int(parts[1])) + "toggle": + exercise.get_node("%TokensBox").get_child(int(parts[1])).emit_signal("toggled", true) + "type": + var field: LineEdit = ( + exercise.get_node("%TextEdit") + if exercise.has_node("%TextEdit") + else exercise.get_node("%CorrectionsBox").get_child(0) + ) + field.text = parts[1] + "validate": + exercise.get_node("%ValidateButton").emit_signal("pressed") + "continue_feedback": + overlay.get_child(overlay.get_child_count() - 1).get_node("%ContinueButton").emit_signal("pressed") diff --git a/tools/screenshot.gd.uid b/tools/screenshot.gd.uid new file mode 100644 index 0000000..383f653 --- /dev/null +++ b/tools/screenshot.gd.uid @@ -0,0 +1 @@ +uid://su7hm1yepfuw