Add settings screen and adult space with progress report and save export/restore (0.4.0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 22:49:01 +02:00
parent 97a065879c
commit dbf513e686
45 changed files with 1390 additions and 14 deletions
+6
View File
@@ -12,6 +12,8 @@ const SCENE_MISSION_INTRO := "res://scenes/world/mission_intro.tscn"
const SCENE_EXERCISE_HOST := "res://scenes/exercises/exercise_host.tscn"
const SCENE_REWARD := "res://scenes/ui/reward_screen.tscn"
const SCENE_COLLECTION := "res://scenes/ui/collection.tscn"
const SCENE_SETTINGS := "res://scenes/ui/settings.tscn"
const SCENE_ADULT_SPACE := "res://scenes/ui/adult_space.tscn"
## Mission choisie sur la carte, lue par mission_intro puis exercise_host et
## reward_screen. Remise a vide par reward_screen au retour sur la carte.
@@ -21,6 +23,10 @@ var pending_mission_id: String = ""
## (MissionScoring.OUTCOME_*), transmis par exercise_host a reward_screen.
var last_outcomes: Array[String] = []
## Scene a laquelle l'ecran de reglages rend la main (carte ou selection de
## profil, selon d'ou il a ete ouvert).
var settings_return_scene: String = SCENE_WORLD_MAP
func goto(scene_path: String) -> void:
# Differe le changement de scene : l'appeler depuis le _ready() de la
+5
View File
@@ -48,5 +48,10 @@ func list_profile_summaries() -> Array[Dictionary]:
return summaries
## Identifiant neuf pour un profil restaure depuis une sauvegarde exportee.
func new_profile_id() -> String:
return _generate_profile_id()
func _generate_profile_id() -> String:
return "profile_%d_%d" % [Time.get_unix_time_from_system(), randi() % 10000]
+108 -3
View File
@@ -1,6 +1,111 @@
extends Node
## Volume, taille du texte et autres preferences de l'enfant ou de l'adulte.
## 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).
## Volume, taille du texte et animations. Les reglages sont ceux de l'appareil
## (user://settings.json), pas ceux d'un profil. Les valeurs sont nettoyees par
## AppSettings a chaque lecture ; un fichier absent ou abime redonne les valeurs
## par defaut sans erreur. Le volume agit sur le bus principal ; la taille du
## texte met a l'echelle les tailles du theme global.
signal changed
const SETTINGS_PATH := "user://settings.json"
var animations_enabled := true
var volume := 0.8
var text_scale_index := 0
var _base_font_sizes: Dictionary = {}
var _base_default_font_size := 0
func _ready() -> void:
load_from_disk()
apply()
func text_scale() -> float:
return AppSettings.TEXT_SCALES[text_scale_index]
## Taille d'un texte fixe dans un script, mise a l'echelle du reglage.
func scaled_font_size(base: int) -> int:
return int(round(base * text_scale()))
func set_volume(value: float) -> void:
volume = clampf(value, 0.0, 1.0)
_commit()
func set_text_scale_index(index: int) -> void:
text_scale_index = clampi(index, 0, AppSettings.TEXT_SCALES.size() - 1)
_commit()
func set_animations_enabled(enabled: bool) -> void:
animations_enabled = enabled
_commit()
func load_from_disk() -> void:
var raw: Variant = null
if FileAccess.file_exists(SETTINGS_PATH):
var file := FileAccess.open(SETTINGS_PATH, FileAccess.READ)
if file != null:
raw = JSON.parse_string(file.get_as_text())
var clean := AppSettings.sanitize(raw)
volume = clean["volume"]
text_scale_index = clean["text_scale_index"]
animations_enabled = clean["animations_enabled"]
## Ecriture atomique : fichier temporaire puis remplacement.
func save_to_disk() -> bool:
var data := AppSettings.defaults()
data["volume"] = volume
data["text_scale_index"] = text_scale_index
data["animations_enabled"] = animations_enabled
var tmp_path := SETTINGS_PATH + ".tmp"
var file := FileAccess.open(tmp_path, FileAccess.WRITE)
if file == null:
return false
file.store_string(JSON.stringify(data))
file.close()
var dir := DirAccess.open("user://")
if dir == null:
return false
return dir.rename(tmp_path, SETTINGS_PATH) == OK
## Applique les reglages au moteur : volume du bus principal et tailles du
## theme global.
func apply() -> void:
AudioServer.set_bus_volume_db(0, AppSettings.volume_db(volume))
_apply_text_scale()
func _commit() -> void:
apply()
save_to_disk()
changed.emit()
func _apply_text_scale() -> void:
var theme := ThemeDB.get_project_theme()
if theme == null:
return
# Les tailles d'origine sont memorisees une fois : on recalcule toujours
# a partir d'elles pour ne pas accumuler les facteurs.
if _base_font_sizes.is_empty():
_base_default_font_size = theme.default_font_size
for type_name in theme.get_font_size_type_list():
for item_name in theme.get_font_size_list(type_name):
_base_font_sizes["%s/%s" % [type_name, item_name]] = theme.get_font_size(
item_name, type_name
)
var factor := text_scale()
theme.default_font_size = int(round(_base_default_font_size * factor))
for key: String in _base_font_sizes:
var parts := key.split("/")
if AppSettings.UNSCALED_THEME_TYPES.has(parts[0]):
continue
theme.set_font_size(parts[1], parts[0], int(round(_base_font_sizes[key] * factor)))