feat(ui): captura F9, tema a data/ui.yaml i retocs estètics
Captura de pantalla amb F9: volca el canvas ARGB a screenshots/scr_<timestamp>.png via stb_image_write. Tot el tema d'UI (colors, timings, fonts, flag hud.bg_enabled) passa a data/ui.yaml via UiConfig; els valors de Defaults::UI queden com a fallbacks si el fitxer falta o li manca alguna clau. Retocs estètics: paleta "uzebox" per a la UI, strings del HUD (P1/P2/V/T/PIRAMIDE/SALA) traduïts a locale, i fons del marcador opcional amb backdrop continu de franja per evitar el trencament visual durant el shake o l'animació d'entrada dels panells.
This commit is contained in:
1 parent
82b229222f
commit
dc8a7723e3
26 files changed
+2591
-208
No files matched your search
+6
-3
@@ -9,11 +9,14 @@ Checks:
|
|||||||
- -bugprone-easily-swappable-parameters
|
- -bugprone-easily-swappable-parameters
|
||||||
- -bugprone-narrowing-conversions
|
- -bugprone-narrowing-conversions
|
||||||
- -modernize-avoid-c-arrays,-warnings-as-errors
|
- -modernize-avoid-c-arrays,-warnings-as-errors
|
||||||
# Fals positiu recurrent en codi C de tercers (source/external/stb_vorbis.c).
|
# Fals positius recurrents en codi C de tercers dins source/external/.
|
||||||
# HeaderFilterRegex no aplica a clang-analyzer-*, així que cal desactivar
|
# HeaderFilterRegex no aplica a clang-analyzer-*, així que cal desactivar
|
||||||
# explícitament. El check inferència sobre variables locals de legacy C
|
# explícitament. El primer: inferència sobre variables locals de legacy C
|
||||||
# genera reports que no reflecteixen bugs reals.
|
# (stb_vorbis.c). El segon: ús de malloc() a stb_image_write.h, que
|
||||||
|
# l'analitzador marca com a possible "size=0" en rutes que a la pràctica
|
||||||
|
# no es donen.
|
||||||
- -clang-analyzer-core.UndefinedBinaryOperatorResult
|
- -clang-analyzer-core.UndefinedBinaryOperatorResult
|
||||||
|
- -clang-analyzer-optin.portability.UnixAPI
|
||||||
|
|
||||||
WarningsAsErrors: '*'
|
WarningsAsErrors: '*'
|
||||||
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
# Headers nostres (excloem source/external/ que conté dependències de tercers no editables)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ set(APP_SOURCES
|
|||||||
source/core/audio/audio_adapter.cpp
|
source/core/audio/audio_adapter.cpp
|
||||||
source/core/audio/jail_audio.cpp
|
source/core/audio/jail_audio.cpp
|
||||||
source/core/locale/locale.cpp
|
source/core/locale/locale.cpp
|
||||||
|
source/core/ui/ui_config.cpp
|
||||||
source/core/input/key_config.cpp
|
source/core/input/key_config.cpp
|
||||||
source/core/input/global_inputs.cpp
|
source/core/input/global_inputs.cpp
|
||||||
source/core/input/input_state.cpp
|
source/core/input/input_state.cpp
|
||||||
@@ -66,6 +67,7 @@ set(APP_SOURCES
|
|||||||
source/core/rendering/notifier.cpp
|
source/core/rendering/notifier.cpp
|
||||||
source/core/rendering/render_info.cpp
|
source/core/rendering/render_info.cpp
|
||||||
source/core/rendering/gif.cpp
|
source/core/rendering/gif.cpp
|
||||||
|
source/core/rendering/screenshot.cpp
|
||||||
source/core/rendering/surface.cpp
|
source/core/rendering/surface.cpp
|
||||||
source/core/rendering/text.cpp
|
source/core/rendering/text.cpp
|
||||||
source/core/rendering/sprite/sprite.cpp
|
source/core/rendering/sprite/sprite.cpp
|
||||||
|
|||||||
@@ -29,18 +29,18 @@ keys:
|
|||||||
- id: toggle_vsync
|
- id: toggle_vsync
|
||||||
code: "F8"
|
code: "F8"
|
||||||
desc: "Activa / desactiva VSync"
|
desc: "Activa / desactiva VSync"
|
||||||
- id: menu_toggle
|
- id: screenshot
|
||||||
code: "F9"
|
code: "F9"
|
||||||
desc: "Obri / tanca el menú de servei"
|
desc: "Captura de pantalla (PNG)"
|
||||||
- id: toggle_render_info
|
- id: toggle_render_info
|
||||||
code: "F12"
|
|
||||||
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
|
||||||
- id: toggle_render_time
|
|
||||||
code: "F10"
|
code: "F10"
|
||||||
desc: "Mostra o amaga el temps de joc dins de l'overlay"
|
desc: "Cicle de l'overlay de debug: apagat / dalt / baix"
|
||||||
- id: pause
|
- id: pause
|
||||||
code: "F11"
|
code: "F11"
|
||||||
desc: "Pausa el joc (o botó START del mando)"
|
desc: "Pausa el joc (o botó START del mando)"
|
||||||
|
- id: menu_toggle
|
||||||
|
code: "F12"
|
||||||
|
desc: "Obri / tanca el menú de servei"
|
||||||
- id: editor_toggle
|
- id: editor_toggle
|
||||||
code: "9"
|
code: "9"
|
||||||
desc: "Entra/surt del mode editor (només Debug)"
|
desc: "Entra/surt del mode editor (només Debug)"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ input:
|
|||||||
shader_off: "Shaders desactivats"
|
shader_off: "Shaders desactivats"
|
||||||
vsync_on: "VSync activat"
|
vsync_on: "VSync activat"
|
||||||
vsync_off: "VSync desactivat"
|
vsync_off: "VSync desactivat"
|
||||||
|
screenshot: "Captura"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Escalat sense"
|
scaling_disabled: "Escalat sense"
|
||||||
@@ -27,6 +28,14 @@ input:
|
|||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: error guardant el mapa"
|
editor_save_error: "Editor: error guardant el mapa"
|
||||||
|
|
||||||
|
hud:
|
||||||
|
p1: "P1"
|
||||||
|
p2: "P2"
|
||||||
|
lives: "Vides"
|
||||||
|
treasures: "Tres."
|
||||||
|
pyramid: "Piramide"
|
||||||
|
room: "Sala"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
root: "Menú de servei"
|
root: "Menú de servei"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ input:
|
|||||||
shader_off: "Shaders off"
|
shader_off: "Shaders off"
|
||||||
vsync_on: "VSync on"
|
vsync_on: "VSync on"
|
||||||
vsync_off: "VSync off"
|
vsync_off: "VSync off"
|
||||||
|
screenshot: "Screenshot"
|
||||||
shader: "Shader"
|
shader: "Shader"
|
||||||
preset: "Preset"
|
preset: "Preset"
|
||||||
scaling_disabled: "Scaling off"
|
scaling_disabled: "Scaling off"
|
||||||
@@ -27,6 +28,14 @@ input:
|
|||||||
editor_off: "Editor OFF"
|
editor_off: "Editor OFF"
|
||||||
editor_save_error: "Editor: map save failed"
|
editor_save_error: "Editor: map save failed"
|
||||||
|
|
||||||
|
hud:
|
||||||
|
p1: "P1"
|
||||||
|
p2: "P2"
|
||||||
|
lives: "L"
|
||||||
|
treasures: "T"
|
||||||
|
pyramid: "PYRAMID"
|
||||||
|
room: "ROOM"
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
titles:
|
titles:
|
||||||
root: "Service menu"
|
root: "Service menu"
|
||||||
|
|||||||
+14
-14
@@ -39,20 +39,20 @@ geometry:
|
|||||||
|
|
||||||
data:
|
data:
|
||||||
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
- [ 1,230,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,231, 1]
|
- [ 1, 91,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105,105, 92, 1]
|
||||||
- [ 1,202,292,260,292,292,260,292,292,260,292,292,260,292,260,292,292,260,260,200, 1]
|
- [ 1, 98,113,113, 95,113, 94,113,113,113,113,113,113,113, 94,113,102,113,113, 96, 1]
|
||||||
- [ 1,202, 32, 32, 2, 32, 65, 34, 34, 32, 65, 2, 32, 32, 65, 32, 34, 32, 32,200, 1]
|
- [ 1, 98, 8, 8, 6, 8, 17, 8, 10, 8, 17, 8, 8, 8, 17, 8, 8, 8, 8, 96, 1]
|
||||||
- [ 1,202, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 34, 2, 2, 2, 32,200, 1]
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
- [ 1,202, 65, 32, 32, 33, 33, 32, 32, 32, 32, 32, 2, 32, 32, 32, 34, 32, 65,200, 1]
|
- [ 1, 98, 17, 8, 9, 2, 8, 10, 8, 8, 8, 8, 10, 8, 8, 26, 8, 8, 17, 96, 1]
|
||||||
- [ 1,202, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32,200, 1]
|
- [ 1, 98, 8, 2, 2, 2, 2, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
- [ 1,202, 65, 32, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 34, 32, 32, 32, 65,200, 1]
|
- [ 1, 98, 17, 8, 8, 26, 8, 8, 9, 8, 8, 8, 8, 8, 10, 8, 6, 8, 17, 96, 1]
|
||||||
- [ 1,202, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32,200, 1]
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
- [ 1,202, 65, 32, 32, 32, 33, 33, 32, 32, 32, 32, 2, 32,181, 32, 32, 32, 65,200, 1]
|
- [ 1, 98, 17, 8, 10, 8, 8, 8, 2, 8, 8, 8, 9, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||||
- [ 1,202, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32,200, 1]
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 6, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
- [ 1,202, 65, 32, 33, 2, 34, 32, 32, 32, 32, 32, 32, 33, 32, 32, 34, 32, 65,200, 1]
|
- [ 1, 98, 17, 8, 10, 8, 8, 10, 2, 8, 8, 26, 8, 8, 8, 8, 10, 8, 17, 96, 1]
|
||||||
- [ 1,202, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32, 2, 2, 2, 32,200, 1]
|
- [ 1, 98, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 2, 2, 2, 8, 96, 1]
|
||||||
- [ 1,202, 32, 32, 34, 34, 65, 32, 2, 32, 65, 32, 34, 33, 65, 32, 32, 32, 32,200, 1]
|
- [ 1, 98, 8, 10, 6, 6, 17, 8, 8, 8, 17, 8, 9, 8, 17, 8, 2, 8, 8, 96, 1]
|
||||||
- [ 1,262,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,263, 1]
|
- [ 1, 99, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89, 89,100, 1]
|
||||||
|
|
||||||
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
# Spawns d'entitats per a aquest nivell. Es llegeixen des de
|
||||||
# EntityManager::loadFromLevel al GameScene. Convencions:
|
# EntityManager::loadFromLevel al GameScene. Convencions:
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
JASC-PAL
|
||||||
|
0100
|
||||||
|
256
|
||||||
|
0 0 0
|
||||||
|
36 0 0
|
||||||
|
72 0 0
|
||||||
|
109 0 0
|
||||||
|
145 0 0
|
||||||
|
182 0 0
|
||||||
|
218 0 0
|
||||||
|
255 0 0
|
||||||
|
0 36 0
|
||||||
|
36 36 0
|
||||||
|
72 36 0
|
||||||
|
109 36 0
|
||||||
|
145 36 0
|
||||||
|
182 36 0
|
||||||
|
218 36 0
|
||||||
|
255 36 0
|
||||||
|
0 72 0
|
||||||
|
36 72 0
|
||||||
|
72 72 0
|
||||||
|
109 72 0
|
||||||
|
145 72 0
|
||||||
|
182 72 0
|
||||||
|
218 72 0
|
||||||
|
255 72 0
|
||||||
|
0 109 0
|
||||||
|
36 109 0
|
||||||
|
72 109 0
|
||||||
|
109 109 0
|
||||||
|
145 109 0
|
||||||
|
182 109 0
|
||||||
|
218 109 0
|
||||||
|
255 109 0
|
||||||
|
0 145 0
|
||||||
|
36 145 0
|
||||||
|
72 145 0
|
||||||
|
109 145 0
|
||||||
|
145 145 0
|
||||||
|
182 145 0
|
||||||
|
218 145 0
|
||||||
|
255 145 0
|
||||||
|
0 182 0
|
||||||
|
36 182 0
|
||||||
|
72 182 0
|
||||||
|
109 182 0
|
||||||
|
145 182 0
|
||||||
|
182 182 0
|
||||||
|
218 182 0
|
||||||
|
255 182 0
|
||||||
|
0 218 0
|
||||||
|
36 218 0
|
||||||
|
72 218 0
|
||||||
|
109 218 0
|
||||||
|
145 218 0
|
||||||
|
182 218 0
|
||||||
|
218 218 0
|
||||||
|
255 218 0
|
||||||
|
0 255 0
|
||||||
|
36 255 0
|
||||||
|
72 255 0
|
||||||
|
109 255 0
|
||||||
|
145 255 0
|
||||||
|
182 255 0
|
||||||
|
218 255 0
|
||||||
|
255 255 0
|
||||||
|
0 0 85
|
||||||
|
36 0 85
|
||||||
|
72 0 85
|
||||||
|
109 0 85
|
||||||
|
145 0 85
|
||||||
|
182 0 85
|
||||||
|
218 0 85
|
||||||
|
255 0 85
|
||||||
|
0 36 85
|
||||||
|
36 36 85
|
||||||
|
72 36 85
|
||||||
|
109 36 85
|
||||||
|
145 36 85
|
||||||
|
182 36 85
|
||||||
|
218 36 85
|
||||||
|
255 36 85
|
||||||
|
0 72 85
|
||||||
|
36 72 85
|
||||||
|
72 72 85
|
||||||
|
109 72 85
|
||||||
|
145 72 85
|
||||||
|
182 72 85
|
||||||
|
218 72 85
|
||||||
|
255 72 85
|
||||||
|
0 109 85
|
||||||
|
36 109 85
|
||||||
|
72 109 85
|
||||||
|
109 109 85
|
||||||
|
145 109 85
|
||||||
|
182 109 85
|
||||||
|
218 109 85
|
||||||
|
255 109 85
|
||||||
|
0 145 85
|
||||||
|
36 145 85
|
||||||
|
72 145 85
|
||||||
|
109 145 85
|
||||||
|
145 145 85
|
||||||
|
182 145 85
|
||||||
|
218 145 85
|
||||||
|
255 145 85
|
||||||
|
0 182 85
|
||||||
|
36 182 85
|
||||||
|
72 182 85
|
||||||
|
109 182 85
|
||||||
|
145 182 85
|
||||||
|
182 182 85
|
||||||
|
218 182 85
|
||||||
|
255 182 85
|
||||||
|
0 218 85
|
||||||
|
36 218 85
|
||||||
|
72 218 85
|
||||||
|
109 218 85
|
||||||
|
145 218 85
|
||||||
|
182 218 85
|
||||||
|
218 218 85
|
||||||
|
255 218 85
|
||||||
|
0 255 85
|
||||||
|
36 255 85
|
||||||
|
72 255 85
|
||||||
|
109 255 85
|
||||||
|
145 255 85
|
||||||
|
182 255 85
|
||||||
|
218 255 85
|
||||||
|
255 255 85
|
||||||
|
0 0 170
|
||||||
|
36 0 170
|
||||||
|
72 0 170
|
||||||
|
109 0 170
|
||||||
|
145 0 170
|
||||||
|
182 0 170
|
||||||
|
218 0 170
|
||||||
|
255 0 170
|
||||||
|
0 36 170
|
||||||
|
36 36 170
|
||||||
|
72 36 170
|
||||||
|
109 36 170
|
||||||
|
145 36 170
|
||||||
|
182 36 170
|
||||||
|
218 36 170
|
||||||
|
255 36 170
|
||||||
|
0 72 170
|
||||||
|
36 72 170
|
||||||
|
72 72 170
|
||||||
|
109 72 170
|
||||||
|
145 72 170
|
||||||
|
182 72 170
|
||||||
|
218 72 170
|
||||||
|
255 72 170
|
||||||
|
0 109 170
|
||||||
|
36 109 170
|
||||||
|
72 109 170
|
||||||
|
109 109 170
|
||||||
|
145 109 170
|
||||||
|
182 109 170
|
||||||
|
218 109 170
|
||||||
|
255 109 170
|
||||||
|
0 145 170
|
||||||
|
36 145 170
|
||||||
|
72 145 170
|
||||||
|
109 145 170
|
||||||
|
145 145 170
|
||||||
|
182 145 170
|
||||||
|
218 145 170
|
||||||
|
255 145 170
|
||||||
|
0 182 170
|
||||||
|
36 182 170
|
||||||
|
72 182 170
|
||||||
|
109 182 170
|
||||||
|
145 182 170
|
||||||
|
182 182 170
|
||||||
|
218 182 170
|
||||||
|
255 182 170
|
||||||
|
0 218 170
|
||||||
|
36 218 170
|
||||||
|
72 218 170
|
||||||
|
109 218 170
|
||||||
|
145 218 170
|
||||||
|
182 218 170
|
||||||
|
218 218 170
|
||||||
|
255 218 170
|
||||||
|
0 255 170
|
||||||
|
36 255 170
|
||||||
|
72 255 170
|
||||||
|
109 255 170
|
||||||
|
145 255 170
|
||||||
|
182 255 170
|
||||||
|
218 255 170
|
||||||
|
255 255 170
|
||||||
|
0 0 255
|
||||||
|
36 0 255
|
||||||
|
72 0 255
|
||||||
|
109 0 255
|
||||||
|
145 0 255
|
||||||
|
182 0 255
|
||||||
|
218 0 255
|
||||||
|
255 0 255
|
||||||
|
0 36 255
|
||||||
|
36 36 255
|
||||||
|
72 36 255
|
||||||
|
109 36 255
|
||||||
|
145 36 255
|
||||||
|
182 36 255
|
||||||
|
218 36 255
|
||||||
|
255 36 255
|
||||||
|
0 72 255
|
||||||
|
36 72 255
|
||||||
|
72 72 255
|
||||||
|
109 72 255
|
||||||
|
145 72 255
|
||||||
|
182 72 255
|
||||||
|
218 72 255
|
||||||
|
255 72 255
|
||||||
|
0 109 255
|
||||||
|
36 109 255
|
||||||
|
72 109 255
|
||||||
|
109 109 255
|
||||||
|
145 109 255
|
||||||
|
182 109 255
|
||||||
|
218 109 255
|
||||||
|
255 109 255
|
||||||
|
0 145 255
|
||||||
|
36 145 255
|
||||||
|
72 145 255
|
||||||
|
109 145 255
|
||||||
|
145 145 255
|
||||||
|
182 145 255
|
||||||
|
218 145 255
|
||||||
|
255 145 255
|
||||||
|
0 182 255
|
||||||
|
36 182 255
|
||||||
|
72 182 255
|
||||||
|
109 182 255
|
||||||
|
145 182 255
|
||||||
|
182 182 255
|
||||||
|
218 182 255
|
||||||
|
255 182 255
|
||||||
|
0 218 255
|
||||||
|
36 218 255
|
||||||
|
72 218 255
|
||||||
|
109 218 255
|
||||||
|
145 218 255
|
||||||
|
182 218 255
|
||||||
|
218 218 255
|
||||||
|
255 218 255
|
||||||
|
0 255 255
|
||||||
|
36 255 255
|
||||||
|
72 255 255
|
||||||
|
109 255 255
|
||||||
|
145 255 255
|
||||||
|
182 255 255
|
||||||
|
218 255 255
|
||||||
|
255 255 255
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 4.9 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
# Tema de la UI: índexs de paleta, timings, fonts i flags visuals. Tota
|
||||||
|
# clau absent cau al valor compilat a Defaults::UI (veure defaults.hpp).
|
||||||
|
|
||||||
|
colors:
|
||||||
|
render_info_text: 58
|
||||||
|
render_info_shadow: 16
|
||||||
|
canvas_bg: 72
|
||||||
|
notification_text: 232
|
||||||
|
notification_bg: 64
|
||||||
|
exit_text: 71
|
||||||
|
exit_bg: 2
|
||||||
|
menu_bg: 2
|
||||||
|
menu_border: 232
|
||||||
|
menu_title: 237
|
||||||
|
menu_label: 58
|
||||||
|
menu_value: 232
|
||||||
|
menu_cursor: 71
|
||||||
|
menu_disabled: 16
|
||||||
|
hud_bg: 9
|
||||||
|
hud_label: 58
|
||||||
|
hud_value: 187
|
||||||
|
hud_flash: 237
|
||||||
|
|
||||||
|
timings:
|
||||||
|
notification_ms: 4000
|
||||||
|
exit_ms: 4000
|
||||||
|
|
||||||
|
fonts:
|
||||||
|
hud:
|
||||||
|
name: "8bithud"
|
||||||
|
gif: "data/fonts/8bithud.gif"
|
||||||
|
fnt: "data/fonts/8bithud.fnt"
|
||||||
|
kerning: 1
|
||||||
|
placeholder:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 1
|
||||||
|
menu_title:
|
||||||
|
name: "ari-w9500-display"
|
||||||
|
gif: "data/fonts/ari-w9500-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
menu_items:
|
||||||
|
name: "ari-w9500-condensed-display"
|
||||||
|
gif: "data/fonts/ari-w9500-condensed-display.gif"
|
||||||
|
fnt: "data/fonts/ari-w9500-condensed-display.fnt"
|
||||||
|
kerning: 0
|
||||||
|
|
||||||
|
hud:
|
||||||
|
bg_enabled: true
|
||||||
|
entry_ms: 800 # durada de l'animació d'entrada dels tres blocs
|
||||||
|
flash_ms: 250 # durada del flash quan un jugador rep un hit
|
||||||
|
shake_ms: 300 # durada del shake que acompanya el flash
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
#include "core/rendering/notifier.hpp"
|
#include "core/rendering/notifier.hpp"
|
||||||
#include "core/rendering/render_info.hpp"
|
#include "core/rendering/render_info.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
|
#include "core/rendering/screenshot.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/options_apply.hpp"
|
#include "game/options_apply.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
@@ -24,7 +26,7 @@ namespace GlobalInputs {
|
|||||||
// La lliguem a la durada de la notificació EXIT perquè el missatge i
|
// La lliguem a la durada de la notificació EXIT perquè el missatge i
|
||||||
// la confirmació expiren alhora — si no, l'usuari podria veure el
|
// la confirmació expiren alhora — si no, l'usuari podria veure el
|
||||||
// text però ja no ser dins del rang de confirmació (o al revés).
|
// text però ja no ser dins del rang de confirmació (o al revés).
|
||||||
constexpr float EXIT_WINDOW_S = static_cast<float>(Notification::EXIT.time_ms) / 1000.0F;
|
// El valor concret viu a data/ui.yaml (timings.exit_ms) via UiConfig.
|
||||||
constexpr int MIN_ZOOM = 1;
|
constexpr int MIN_ZOOM = 1;
|
||||||
constexpr int MAX_ZOOM = 6;
|
constexpr int MAX_ZOOM = 6;
|
||||||
|
|
||||||
@@ -67,6 +69,18 @@ namespace GlobalInputs {
|
|||||||
notifyLocale(NEW_VSYNC ? "input.vsync_on" : "input.vsync_off");
|
notifyLocale(NEW_VSYNC ? "input.vsync_on" : "input.vsync_off");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Volca el canvas ARGB actual a PNG dins de `screenshots/`. Si ha
|
||||||
|
// fallat l'escriptura, el handler no mostra notificació (retorn buit).
|
||||||
|
void onScreenshot() {
|
||||||
|
const auto* buffer = Screen::get().getCanvasArgb();
|
||||||
|
const int W = Screen::get().getCanvasWidth();
|
||||||
|
const int H = Screen::get().getCanvasHeight();
|
||||||
|
const std::string FILENAME = Screenshot::save(buffer, W, H);
|
||||||
|
if (!FILENAME.empty()) {
|
||||||
|
Notification::notify(Locale::get().text("input.screenshot") + ' ' + FILENAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onToggleShader() {
|
void onToggleShader() {
|
||||||
const bool NEW_ENABLED = !Options::video().shader.enabled;
|
const bool NEW_ENABLED = !Options::video().shader.enabled;
|
||||||
Options::setShaderEnabled(NEW_ENABLED);
|
Options::setShaderEnabled(NEW_ENABLED);
|
||||||
@@ -140,17 +154,13 @@ namespace GlobalInputs {
|
|||||||
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
|
Options::setVideoRenderInfoPosition(static_cast<Defaults::RenderInfoPosition>(NEXT));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onToggleRenderTime() {
|
|
||||||
Options::setVideoRenderInfoShowTime(!Options::video().render_info_show_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onExit() {
|
void onExit() {
|
||||||
if (exit_window_s > 0.0F) {
|
if (exit_window_s > 0.0F) {
|
||||||
quit_requested = true;
|
quit_requested = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exit_window_s = EXIT_WINDOW_S;
|
exit_window_s = static_cast<float>(UiConfig::get().exitMs()) / 1000.0F;
|
||||||
Notification::notify(Locale::get().text("input.press_again_exit"), Notification::EXIT);
|
Notification::notify(Locale::get().text("input.press_again_exit"), Notification::exitPreset());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -171,6 +181,8 @@ namespace GlobalInputs {
|
|||||||
onToggleShader();
|
onToggleShader();
|
||||||
} else if (SC == KeyConfig::get().scancode("toggle_vsync")) {
|
} else if (SC == KeyConfig::get().scancode("toggle_vsync")) {
|
||||||
onToggleVSync();
|
onToggleVSync();
|
||||||
|
} else if (SC == KeyConfig::get().scancode("screenshot")) {
|
||||||
|
onScreenshot();
|
||||||
} else if (SC == KeyConfig::get().scancode("next_shader")) {
|
} else if (SC == KeyConfig::get().scancode("next_shader")) {
|
||||||
onNextShader();
|
onNextShader();
|
||||||
} else if (SC == KeyConfig::get().scancode("next_preset")) {
|
} else if (SC == KeyConfig::get().scancode("next_preset")) {
|
||||||
@@ -179,8 +191,6 @@ namespace GlobalInputs {
|
|||||||
onNextScalingMode();
|
onNextScalingMode();
|
||||||
} else if (SC == KeyConfig::get().scancode("toggle_render_info")) {
|
} else if (SC == KeyConfig::get().scancode("toggle_render_info")) {
|
||||||
onCycleRenderInfo();
|
onCycleRenderInfo();
|
||||||
} else if (SC == KeyConfig::get().scancode("toggle_render_time")) {
|
|
||||||
onToggleRenderTime();
|
|
||||||
} else if (SC == KeyConfig::get().scancode("exit")) {
|
} else if (SC == KeyConfig::get().scancode("exit")) {
|
||||||
onExit();
|
onExit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "core/rendering/notifier.hpp"
|
#include "core/rendering/notifier.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "core/ui/ui_config.hpp"
|
||||||
|
|
||||||
// Temes per a les notificacions de pantalla. Un "preset" captura els dos
|
// Temes per a les notificacions de pantalla. Un "preset" captura els dos
|
||||||
// colors (text i fons, índexs sobre la paleta "ui") i el temps de visibilitat
|
// colors (text i fons, índexs sobre la paleta "ui") i el temps de visibilitat
|
||||||
// en mil·lisegons. Per a llançar una notificació amb un preset concret,
|
// en mil·lisegons. Els valors concrets viuen a data/ui.yaml (carregat per
|
||||||
// cridar Notification::notify(text, preset); si s'omet, s'usa DEFAULT.
|
// UiConfig); per llançar una notificació amb un preset concret, cridar
|
||||||
|
// Notification::notify(text, preset). Sense preset s'usa el DEFAULT.
|
||||||
namespace Notification {
|
namespace Notification {
|
||||||
|
|
||||||
struct Preset {
|
struct Preset {
|
||||||
@@ -19,27 +20,36 @@ namespace Notification {
|
|||||||
int time_ms;
|
int time_ms;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tema per defecte: text clar sobre fons fosc. Els valors concrets
|
// Preset per defecte: text clar sobre fons fosc. Es construeix en cada
|
||||||
// viuen a `Defaults::UI` — tocar l'aparença global es fa allà.
|
// crida a partir d'UiConfig perquè els valors no són constexpr: viuen a
|
||||||
constexpr Preset DEFAULT{
|
// data/ui.yaml i es carreguen a l'arrencada.
|
||||||
.text_color = Defaults::UI::NOTIFICATION_TEXT,
|
[[nodiscard]] inline auto defaultPreset() -> Preset {
|
||||||
.bg_color = Defaults::UI::NOTIFICATION_BG,
|
return {
|
||||||
.time_ms = Defaults::UI::NOTIFICATION_TIME_MS,
|
.text_color = UiConfig::get().notificationText(),
|
||||||
|
.bg_color = UiConfig::get().notificationBg(),
|
||||||
|
.time_ms = UiConfig::get().notificationMs(),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Preset per al missatge de confirmació d'eixida (ESC doble). Color
|
// Preset per al missatge de confirmació d'eixida (ESC doble). Color
|
||||||
// d'accent per a distingir-lo d'una notificació informativa; la durada
|
// d'accent per distingir-lo d'una notificació informativa; la durada
|
||||||
// coincideix amb la finestra durant la qual la segona pulsació d'ESC
|
// coincideix amb la finestra durant la qual la segona pulsació d'ESC
|
||||||
// confirma el tancament — veure GlobalInputs::onExit.
|
// confirma el tancament — veure GlobalInputs::onExit.
|
||||||
constexpr Preset EXIT{
|
[[nodiscard]] inline auto exitPreset() -> Preset {
|
||||||
.text_color = Defaults::UI::EXIT_TEXT,
|
return {
|
||||||
.bg_color = Defaults::UI::EXIT_BG,
|
.text_color = UiConfig::get().exitText(),
|
||||||
.time_ms = Defaults::UI::EXIT_TIME_MS,
|
.bg_color = UiConfig::get().exitBg(),
|
||||||
|
.time_ms = UiConfig::get().exitMs(),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Helper de conveniència: aplica un preset (per defecte DEFAULT) cridant
|
// Helper de conveniència: aplica un preset (per defecte el DEFAULT)
|
||||||
// la API crua de Notifier. La direcció game → core és la correcta.
|
// cridant la API crua de Notifier. La direcció game → core és correcta.
|
||||||
inline void notify(const std::string& text, const Preset& preset = DEFAULT) {
|
inline void notify(const std::string& text) {
|
||||||
|
const auto P = defaultPreset();
|
||||||
|
Notifier::get().notify(text, P.text_color, P.bg_color, P.time_ms);
|
||||||
|
}
|
||||||
|
inline void notify(const std::string& text, const Preset& preset) {
|
||||||
Notifier::get().notify(text, preset.text_color, preset.bg_color, preset.time_ms);
|
Notifier::get().notify(text, preset.text_color, preset.bg_color, preset.time_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include "core/rendering/screenshot.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "external/stb_image_write.h"
|
||||||
|
|
||||||
|
namespace Screenshot {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Crea la carpeta `screenshots/` si no existeix i torna la ruta
|
||||||
|
// completa al fitxer. El paràmetre `filename` rep el nom nu (sense
|
||||||
|
// directori) per poder mostrar-lo a la notificació.
|
||||||
|
auto generateFilePath(std::string& filename) -> std::string {
|
||||||
|
std::filesystem::create_directories("screenshots");
|
||||||
|
|
||||||
|
const auto NOW = std::chrono::system_clock::now();
|
||||||
|
const std::time_t TIME = std::chrono::system_clock::to_time_t(NOW);
|
||||||
|
const std::tm* tm = std::localtime(&TIME);
|
||||||
|
|
||||||
|
char timestamp[20];
|
||||||
|
std::strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm);
|
||||||
|
|
||||||
|
filename = std::string("scr_") + timestamp + ".png";
|
||||||
|
return "screenshots/" + filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converteix ARGB8888 → RGBA8888 in-place i escriu el PNG. stb_image_write
|
||||||
|
// espera RGBA en ordre byte (little-endian: R al byte baix, A al byte alt).
|
||||||
|
auto writePng(std::uint32_t* buffer, int width, int height, const std::string& filepath) -> bool {
|
||||||
|
const int TOTAL = width * height;
|
||||||
|
for (int i = 0; i < TOTAL; ++i) {
|
||||||
|
const std::uint32_t P = buffer[i];
|
||||||
|
const std::uint32_t A = (P >> 24) & 0xFF;
|
||||||
|
const std::uint32_t R = (P >> 16) & 0xFF;
|
||||||
|
const std::uint32_t G = (P >> 8) & 0xFF;
|
||||||
|
const std::uint32_t B = P & 0xFF;
|
||||||
|
buffer[i] = R | (G << 8) | (B << 16) | (A << 24);
|
||||||
|
}
|
||||||
|
// NOLINTNEXTLINE(clang-analyzer-optin.portability.UnixAPI) — fals positiu dins de stb_image_write
|
||||||
|
return stbi_write_png(filepath.c_str(), width, height, 4, buffer, width * 4) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto save(const std::uint32_t* buffer, int width, int height) -> std::string {
|
||||||
|
// Còpia local per no tocar el canvas original durant la conversió.
|
||||||
|
std::vector<std::uint32_t> copy(buffer, buffer + (static_cast<size_t>(width) * height));
|
||||||
|
|
||||||
|
std::string filename;
|
||||||
|
const std::string FILEPATH = generateFilePath(filename);
|
||||||
|
|
||||||
|
return writePng(copy.data(), width, height, FILEPATH) ? filename : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Screenshot
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Volca un buffer ARGB8888 a un fitxer PNG dins de la carpeta `screenshots/`
|
||||||
|
// (relativa al CWD). El nom es genera amb un timestamp i la funció retorna
|
||||||
|
// el nom del fitxer creat, o una cadena buida si l'escriptura ha fallat.
|
||||||
|
namespace Screenshot {
|
||||||
|
|
||||||
|
auto save(const std::uint32_t* buffer, int width, int height) -> std::string;
|
||||||
|
|
||||||
|
} // namespace Screenshot
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "core/scene/scene.hpp"
|
#include "core/scene/scene.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/debug_config.hpp"
|
#include "game/debug_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/game_scene.hpp"
|
#include "game/scenes/game_scene.hpp"
|
||||||
@@ -32,7 +33,6 @@
|
|||||||
#include "game/scenes/logo_scene.hpp"
|
#include "game/scenes/logo_scene.hpp"
|
||||||
#include "game/scenes/title_scene.hpp"
|
#include "game/scenes/title_scene.hpp"
|
||||||
#include "game/ui/service_menu.hpp"
|
#include "game/ui/service_menu.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
Director::Director() {
|
Director::Director() {
|
||||||
@@ -97,15 +97,19 @@ Director::Director() {
|
|||||||
// Hotkeys globals. GlobalInputs::update() les consumirà a cada frame.
|
// Hotkeys globals. GlobalInputs::update() les consumirà a cada frame.
|
||||||
KeyConfig::get().load("data/input/keys.yaml");
|
KeyConfig::get().load("data/input/keys.yaml");
|
||||||
|
|
||||||
|
// Tema d'UI (colors, timings, fonts, flags). Qualsevol clau absent a
|
||||||
|
// data/ui.yaml cau al valor compilat a Defaults::UI.
|
||||||
|
UiConfig::get().load("data/ui.yaml");
|
||||||
|
|
||||||
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
|
// Recursos compartits d'UI (paleta + fonts). Es carreguen abans que els
|
||||||
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els
|
// overlays perquè tots els reaprofiten sense duplicar la lògica. Els
|
||||||
// noms de font viuen a Defaults::UI::Font (un únic lloc per canviar-los).
|
// noms de font viuen al fitxer data/ui.yaml via UiConfig.
|
||||||
Resource::Cache::get().loadPalette("ui", "data/palettes/sega-cube.pal");
|
Resource::Cache::get().loadPalette("ui", "data/palettes/uzebox.pal");
|
||||||
const std::string HUD_FONT_GIF_KEY = std::string(Defaults::UI::Font::HUD_NAME) + ".gif";
|
const std::string HUD_FONT_GIF_KEY = UiConfig::get().hudFont().name + ".gif";
|
||||||
Resource::Cache::get().loadSurface(HUD_FONT_GIF_KEY, Defaults::UI::Font::HUD_GIF);
|
Resource::Cache::get().loadSurface(HUD_FONT_GIF_KEY, UiConfig::get().hudFont().gif);
|
||||||
Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
||||||
const std::string PLACEHOLDER_FONT_GIF_KEY = std::string(Defaults::UI::Font::PLACEHOLDER_NAME) + ".gif";
|
const std::string PLACEHOLDER_FONT_GIF_KEY = UiConfig::get().placeholderFont().name + ".gif";
|
||||||
Resource::Cache::get().loadSurface(PLACEHOLDER_FONT_GIF_KEY, Defaults::UI::Font::PLACEHOLDER_GIF);
|
Resource::Cache::get().loadSurface(PLACEHOLDER_FONT_GIF_KEY, UiConfig::get().placeholderFont().gif);
|
||||||
Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
||||||
|
|
||||||
// Música de la piràmide 1 (primer i únic track de moment). Carreguem ací
|
// Música de la piràmide 1 (primer i únic track de moment). Carreguem ací
|
||||||
@@ -126,41 +130,33 @@ Director::Director() {
|
|||||||
// i la injectem als consumidors (Notifier, RenderInfo) per a que cap
|
// i la injectem als consumidors (Notifier, RenderInfo) per a que cap
|
||||||
// d'ells hagi de conèixer els noms dels recursos concrets.
|
// d'ells hagi de conèixer els noms dels recursos concrets.
|
||||||
auto hud_font_surface = Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY);
|
auto hud_font_surface = Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY);
|
||||||
auto hud_text = std::make_shared<Text>(hud_font_surface, Defaults::UI::Font::HUD_FNT);
|
auto hud_text = std::make_shared<Text>(hud_font_surface, UiConfig::get().hudFont().fnt);
|
||||||
hud_text->setDefaultKerning(Defaults::UI::Font::HUD_KERNING);
|
hud_text->setDefaultKerning(UiConfig::get().hudFont().kerning);
|
||||||
const auto& ui_palette = Resource::Cache::get().getPalette("ui");
|
const auto& ui_palette = Resource::Cache::get().getPalette("ui");
|
||||||
|
|
||||||
RenderInfo::get().init(Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW), hud_text);
|
RenderInfo::get().init(Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()), hud_text);
|
||||||
Notifier::get().init(hud_text, ui_palette);
|
Notifier::get().init(hud_text, ui_palette);
|
||||||
|
|
||||||
// Font dels placeholders (LOGO / TITLE / INTRO). És una font distinta a
|
// Font dels placeholders (LOGO / TITLE / INTRO). És una font distinta a
|
||||||
// la de HUD perquè els placeholders desapareixeran amb les seues escenes
|
// la de HUD perquè els placeholders desapareixeran amb les seues escenes
|
||||||
// finals; mentrestant tenen estètica pròpia.
|
// finals; mentrestant tenen estètica pròpia.
|
||||||
auto placeholder_font_surface = Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY);
|
auto placeholder_font_surface = Resource::Cache::get().getSurface(PLACEHOLDER_FONT_GIF_KEY);
|
||||||
auto placeholder_text = std::make_shared<Text>(placeholder_font_surface, Defaults::UI::Font::PLACEHOLDER_FNT);
|
auto placeholder_text = std::make_shared<Text>(placeholder_font_surface, UiConfig::get().placeholderFont().fnt);
|
||||||
placeholder_text->setDefaultKerning(Defaults::UI::Font::PLACEHOLDER_KERNING);
|
placeholder_text->setDefaultKerning(UiConfig::get().placeholderFont().kerning);
|
||||||
|
|
||||||
// Font del menú de servei (F9). El títol va amb la variant `display`
|
// Font del menú de servei (F9). El títol va amb la variant `display`
|
||||||
// i els items amb `condensed-display` (més compacte perquè caben més
|
// i els items amb `condensed-display` (més compacte perquè caben més
|
||||||
// files). Els dos noms i kernings viuen a `Defaults::UI::Font`.
|
// files). Els dos noms i kernings viuen a data/ui.yaml via UiConfig.
|
||||||
auto load_menu_font = [&](const char* name, const char* gif, const char* fnt, int kerning) {
|
auto load_menu_font = [&](const UiConfig::Font& font) {
|
||||||
const std::string KEY = std::string(name) + ".gif";
|
const std::string KEY = font.name + ".gif";
|
||||||
Resource::Cache::get().loadSurface(KEY, gif);
|
Resource::Cache::get().loadSurface(KEY, font.gif);
|
||||||
Resource::Cache::get().getSurface(KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
Resource::Cache::get().getSurface(KEY)->setPalette(Resource::Cache::get().getPalette("ui"));
|
||||||
auto text = std::make_shared<Text>(Resource::Cache::get().getSurface(KEY), fnt);
|
auto text = std::make_shared<Text>(Resource::Cache::get().getSurface(KEY), font.fnt);
|
||||||
text->setDefaultKerning(kerning);
|
text->setDefaultKerning(font.kerning);
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
auto menu_title_text = load_menu_font(
|
auto menu_title_text = load_menu_font(UiConfig::get().menuTitleFont());
|
||||||
Defaults::UI::Font::MENU_TITLE_NAME,
|
auto menu_items_text = load_menu_font(UiConfig::get().menuItemsFont());
|
||||||
Defaults::UI::Font::MENU_TITLE_GIF,
|
|
||||||
Defaults::UI::Font::MENU_TITLE_FNT,
|
|
||||||
Defaults::UI::Font::MENU_TITLE_KERNING);
|
|
||||||
auto menu_items_text = load_menu_font(
|
|
||||||
Defaults::UI::Font::MENU_ITEMS_NAME,
|
|
||||||
Defaults::UI::Font::MENU_ITEMS_GIF,
|
|
||||||
Defaults::UI::Font::MENU_ITEMS_FNT,
|
|
||||||
Defaults::UI::Font::MENU_ITEMS_KERNING);
|
|
||||||
ServiceMenu::get().init(menu_title_text, menu_items_text, ui_palette);
|
ServiceMenu::get().init(menu_title_text, menu_items_text, ui_palette);
|
||||||
|
|
||||||
// Overlay hook: Screen::present() cridarà aquesta lambda al principi,
|
// Overlay hook: Screen::present() cridarà aquesta lambda al principi,
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "core/resources/resource_helper.hpp"
|
||||||
|
#include "external/fkyaml_node.hpp"
|
||||||
|
#include "utils/defaults.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Lector de camp amb fallback: si la clau existeix al node, escriu a
|
||||||
|
// `dst` el valor convertit; si no, deixa `dst` com estava (que al
|
||||||
|
// constructor ja hem inicialitzat amb Defaults::UI).
|
||||||
|
template <typename T>
|
||||||
|
void readField(const fkyaml::node& node, const char* key, T& dst) {
|
||||||
|
if (node.contains(key)) { dst = node[key].get_value<T>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant per a Uint8 — fkyaml no serialitza a Uint8 directament, cal
|
||||||
|
// passar per int i encaixar al rang 0..255 (valors fora de rang es
|
||||||
|
// trunquen i s'avisen).
|
||||||
|
void readColorIndex(const fkyaml::node& node, const char* key, Uint8& dst) {
|
||||||
|
if (!node.contains(key)) { return; }
|
||||||
|
const int VALUE = node[key].get_value<int>();
|
||||||
|
if (VALUE < 0 || VALUE > 255) {
|
||||||
|
std::cerr << "UiConfig: índex '" << key << "'=" << VALUE << " fora de rang 0..255 — ignorat\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dst = static_cast<Uint8>(VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void readFont(const fkyaml::node& node, const char* key, UiConfig::Font& dst) {
|
||||||
|
if (!node.contains(key)) { return; }
|
||||||
|
const auto& f = node[key];
|
||||||
|
readField(f, "name", dst.name);
|
||||||
|
readField(f, "gif", dst.gif);
|
||||||
|
readField(f, "fnt", dst.fnt);
|
||||||
|
readField(f, "kerning", dst.kerning);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UiConfig::UiConfig()
|
||||||
|
: render_info_text_(Defaults::UI::RENDER_INFO_TEXT),
|
||||||
|
render_info_shadow_(Defaults::UI::RENDER_INFO_SHADOW),
|
||||||
|
canvas_bg_(Defaults::UI::CANVAS_BG),
|
||||||
|
notification_text_(Defaults::UI::NOTIFICATION_TEXT),
|
||||||
|
notification_bg_(Defaults::UI::NOTIFICATION_BG),
|
||||||
|
exit_text_(Defaults::UI::EXIT_TEXT),
|
||||||
|
exit_bg_(Defaults::UI::EXIT_BG),
|
||||||
|
menu_bg_(Defaults::UI::MENU_BG),
|
||||||
|
menu_border_(Defaults::UI::MENU_BORDER),
|
||||||
|
menu_title_(Defaults::UI::MENU_TITLE),
|
||||||
|
menu_label_(Defaults::UI::MENU_LABEL),
|
||||||
|
menu_value_(Defaults::UI::MENU_VALUE),
|
||||||
|
menu_cursor_(Defaults::UI::MENU_CURSOR),
|
||||||
|
menu_disabled_(Defaults::UI::MENU_DISABLED),
|
||||||
|
hud_bg_(Defaults::UI::HUD_BG),
|
||||||
|
hud_label_(Defaults::UI::HUD_LABEL),
|
||||||
|
hud_value_(Defaults::UI::HUD_VALUE),
|
||||||
|
hud_flash_(Defaults::UI::HUD_FLASH),
|
||||||
|
notification_ms_(Defaults::UI::NOTIFICATION_TIME_MS),
|
||||||
|
exit_ms_(Defaults::UI::EXIT_TIME_MS),
|
||||||
|
hud_entry_ms_(Defaults::UI::HUD_ENTRY_MS),
|
||||||
|
hud_flash_ms_(Defaults::UI::HUD_FLASH_MS),
|
||||||
|
hud_shake_ms_(Defaults::UI::HUD_SHAKE_MS),
|
||||||
|
hud_font_{.name = Defaults::UI::Font::HUD_NAME, .gif = Defaults::UI::Font::HUD_GIF, .fnt = Defaults::UI::Font::HUD_FNT, .kerning = Defaults::UI::Font::HUD_KERNING},
|
||||||
|
placeholder_font_{.name = Defaults::UI::Font::PLACEHOLDER_NAME, .gif = Defaults::UI::Font::PLACEHOLDER_GIF, .fnt = Defaults::UI::Font::PLACEHOLDER_FNT, .kerning = Defaults::UI::Font::PLACEHOLDER_KERNING},
|
||||||
|
menu_title_font_{.name = Defaults::UI::Font::MENU_TITLE_NAME, .gif = Defaults::UI::Font::MENU_TITLE_GIF, .fnt = Defaults::UI::Font::MENU_TITLE_FNT, .kerning = Defaults::UI::Font::MENU_TITLE_KERNING},
|
||||||
|
menu_items_font_{.name = Defaults::UI::Font::MENU_ITEMS_NAME, .gif = Defaults::UI::Font::MENU_ITEMS_GIF, .fnt = Defaults::UI::Font::MENU_ITEMS_FNT, .kerning = Defaults::UI::Font::MENU_ITEMS_KERNING} {
|
||||||
|
}
|
||||||
|
|
||||||
|
auto UiConfig::get() -> UiConfig& {
|
||||||
|
static UiConfig instance_;
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UiConfig::load(const std::string& file_path) {
|
||||||
|
auto bytes = Resource::Helper::loadFile(file_path);
|
||||||
|
if (bytes.empty()) {
|
||||||
|
std::cerr << "UiConfig: no s'ha pogut obrir " << file_path << " — s'usaran els defaults compilats\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
||||||
|
const auto* end = begin + bytes.size();
|
||||||
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
||||||
|
|
||||||
|
if (yaml.contains("colors")) {
|
||||||
|
const auto& c = yaml["colors"];
|
||||||
|
readColorIndex(c, "render_info_text", render_info_text_);
|
||||||
|
readColorIndex(c, "render_info_shadow", render_info_shadow_);
|
||||||
|
readColorIndex(c, "canvas_bg", canvas_bg_);
|
||||||
|
readColorIndex(c, "notification_text", notification_text_);
|
||||||
|
readColorIndex(c, "notification_bg", notification_bg_);
|
||||||
|
readColorIndex(c, "exit_text", exit_text_);
|
||||||
|
readColorIndex(c, "exit_bg", exit_bg_);
|
||||||
|
readColorIndex(c, "menu_bg", menu_bg_);
|
||||||
|
readColorIndex(c, "menu_border", menu_border_);
|
||||||
|
readColorIndex(c, "menu_title", menu_title_);
|
||||||
|
readColorIndex(c, "menu_label", menu_label_);
|
||||||
|
readColorIndex(c, "menu_value", menu_value_);
|
||||||
|
readColorIndex(c, "menu_cursor", menu_cursor_);
|
||||||
|
readColorIndex(c, "menu_disabled", menu_disabled_);
|
||||||
|
readColorIndex(c, "hud_bg", hud_bg_);
|
||||||
|
readColorIndex(c, "hud_label", hud_label_);
|
||||||
|
readColorIndex(c, "hud_value", hud_value_);
|
||||||
|
readColorIndex(c, "hud_flash", hud_flash_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("timings")) {
|
||||||
|
const auto& t = yaml["timings"];
|
||||||
|
readField(t, "notification_ms", notification_ms_);
|
||||||
|
readField(t, "exit_ms", exit_ms_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("fonts")) {
|
||||||
|
const auto& f = yaml["fonts"];
|
||||||
|
readFont(f, "hud", hud_font_);
|
||||||
|
readFont(f, "placeholder", placeholder_font_);
|
||||||
|
readFont(f, "menu_title", menu_title_font_);
|
||||||
|
readFont(f, "menu_items", menu_items_font_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yaml.contains("hud")) {
|
||||||
|
const auto& h = yaml["hud"];
|
||||||
|
readField(h, "bg_enabled", hud_bg_enabled_);
|
||||||
|
readField(h, "entry_ms", hud_entry_ms_);
|
||||||
|
readField(h, "flash_ms", hud_flash_ms_);
|
||||||
|
readField(h, "shake_ms", hud_shake_ms_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "UiConfig: carregat des de " << file_path << '\n';
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "UiConfig: error parsejant " << file_path << ": " << e.what() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Configuració del tema UI (índexs de paleta, timings, fonts i flags)
|
||||||
|
// carregada des de data/ui.yaml. Cada camp s'inicialitza amb el valor
|
||||||
|
// de Defaults::UI com a fallback: si el fitxer falta o li manca una
|
||||||
|
// clau, el joc s'executa amb el tema compilat.
|
||||||
|
//
|
||||||
|
// Patró idèntic a Locale i KeyConfig: Meyers singleton + load(path).
|
||||||
|
// No hi ha hot-reload — els canvis al YAML requereixen reiniciar.
|
||||||
|
class UiConfig {
|
||||||
|
public:
|
||||||
|
struct Font {
|
||||||
|
std::string name;
|
||||||
|
std::string gif;
|
||||||
|
std::string fnt;
|
||||||
|
int kerning;
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto get() -> UiConfig&;
|
||||||
|
|
||||||
|
UiConfig(const UiConfig&) = delete;
|
||||||
|
UiConfig(UiConfig&&) = delete;
|
||||||
|
auto operator=(const UiConfig&) -> UiConfig& = delete;
|
||||||
|
auto operator=(UiConfig&&) -> UiConfig& = delete;
|
||||||
|
|
||||||
|
void load(const std::string& file_path);
|
||||||
|
|
||||||
|
// Colors — índexs sobre la paleta "ui" (Resource::Cache).
|
||||||
|
[[nodiscard]] auto renderInfoText() const -> Uint8 { return render_info_text_; }
|
||||||
|
[[nodiscard]] auto renderInfoShadow() const -> Uint8 { return render_info_shadow_; }
|
||||||
|
[[nodiscard]] auto canvasBg() const -> Uint8 { return canvas_bg_; }
|
||||||
|
[[nodiscard]] auto notificationText() const -> Uint8 { return notification_text_; }
|
||||||
|
[[nodiscard]] auto notificationBg() const -> Uint8 { return notification_bg_; }
|
||||||
|
[[nodiscard]] auto exitText() const -> Uint8 { return exit_text_; }
|
||||||
|
[[nodiscard]] auto exitBg() const -> Uint8 { return exit_bg_; }
|
||||||
|
[[nodiscard]] auto menuBg() const -> Uint8 { return menu_bg_; }
|
||||||
|
[[nodiscard]] auto menuBorder() const -> Uint8 { return menu_border_; }
|
||||||
|
[[nodiscard]] auto menuTitle() const -> Uint8 { return menu_title_; }
|
||||||
|
[[nodiscard]] auto menuLabel() const -> Uint8 { return menu_label_; }
|
||||||
|
[[nodiscard]] auto menuValue() const -> Uint8 { return menu_value_; }
|
||||||
|
[[nodiscard]] auto menuCursor() const -> Uint8 { return menu_cursor_; }
|
||||||
|
[[nodiscard]] auto menuDisabled() const -> Uint8 { return menu_disabled_; }
|
||||||
|
[[nodiscard]] auto hudBg() const -> Uint8 { return hud_bg_; }
|
||||||
|
[[nodiscard]] auto hudLabel() const -> Uint8 { return hud_label_; }
|
||||||
|
[[nodiscard]] auto hudValue() const -> Uint8 { return hud_value_; }
|
||||||
|
[[nodiscard]] auto hudFlash() const -> Uint8 { return hud_flash_; }
|
||||||
|
|
||||||
|
// Timings (ms).
|
||||||
|
[[nodiscard]] auto notificationMs() const -> int { return notification_ms_; }
|
||||||
|
[[nodiscard]] auto exitMs() const -> int { return exit_ms_; }
|
||||||
|
[[nodiscard]] auto hudEntryMs() const -> int { return hud_entry_ms_; }
|
||||||
|
[[nodiscard]] auto hudFlashMs() const -> int { return hud_flash_ms_; }
|
||||||
|
[[nodiscard]] auto hudShakeMs() const -> int { return hud_shake_ms_; }
|
||||||
|
|
||||||
|
// Fonts de UI.
|
||||||
|
[[nodiscard]] auto hudFont() const -> const Font& { return hud_font_; }
|
||||||
|
[[nodiscard]] auto placeholderFont() const -> const Font& { return placeholder_font_; }
|
||||||
|
[[nodiscard]] auto menuTitleFont() const -> const Font& { return menu_title_font_; }
|
||||||
|
[[nodiscard]] auto menuItemsFont() const -> const Font& { return menu_items_font_; }
|
||||||
|
|
||||||
|
// Flags visuals.
|
||||||
|
[[nodiscard]] auto hudBgEnabled() const -> bool { return hud_bg_enabled_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
UiConfig();
|
||||||
|
~UiConfig() = default;
|
||||||
|
|
||||||
|
Uint8 render_info_text_;
|
||||||
|
Uint8 render_info_shadow_;
|
||||||
|
Uint8 canvas_bg_;
|
||||||
|
Uint8 notification_text_;
|
||||||
|
Uint8 notification_bg_;
|
||||||
|
Uint8 exit_text_;
|
||||||
|
Uint8 exit_bg_;
|
||||||
|
Uint8 menu_bg_;
|
||||||
|
Uint8 menu_border_;
|
||||||
|
Uint8 menu_title_;
|
||||||
|
Uint8 menu_label_;
|
||||||
|
Uint8 menu_value_;
|
||||||
|
Uint8 menu_cursor_;
|
||||||
|
Uint8 menu_disabled_;
|
||||||
|
Uint8 hud_bg_;
|
||||||
|
Uint8 hud_label_;
|
||||||
|
Uint8 hud_value_;
|
||||||
|
Uint8 hud_flash_;
|
||||||
|
|
||||||
|
int notification_ms_;
|
||||||
|
int exit_ms_;
|
||||||
|
int hud_entry_ms_;
|
||||||
|
int hud_flash_ms_;
|
||||||
|
int hud_shake_ms_;
|
||||||
|
|
||||||
|
Font hud_font_;
|
||||||
|
Font placeholder_font_;
|
||||||
|
Font menu_title_font_;
|
||||||
|
Font menu_items_font_;
|
||||||
|
|
||||||
|
bool hud_bg_enabled_{false};
|
||||||
|
};
|
||||||
Vendored
+1724
File diff suppressed because it is too large.
Load diff
@@ -23,6 +23,7 @@
|
|||||||
#include "core/resources/resource_cache.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "external/fkyaml_node.hpp"
|
#include "external/fkyaml_node.hpp"
|
||||||
#include "game/entities/entity.hpp"
|
#include "game/entities/entity.hpp"
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
@@ -30,7 +31,6 @@
|
|||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/ui/hud.hpp"
|
#include "game/ui/hud.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
@@ -144,11 +144,11 @@ GameScene::GameScene(Config config)
|
|||||||
// la paleta "ui" ja precarregades pel Director — no toca disc des
|
// la paleta "ui" ja precarregades pel Director — no toca disc des
|
||||||
// d'aquí. Subscrit al bus per reaccionar a `player_hit`,
|
// d'aquí. Subscrit al bus per reaccionar a `player_hit`,
|
||||||
// `level_complete`, etc. amb shake/flash i futurs canvis de mode.
|
// `level_complete`, etc. amb shake/flash i futurs canvis de mode.
|
||||||
const std::string HUD_FONT_GIF_KEY = std::string(Defaults::UI::Font::HUD_NAME) + ".gif";
|
const std::string HUD_FONT_GIF_KEY = UiConfig::get().hudFont().name + ".gif";
|
||||||
auto hud_font_surface = Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY);
|
auto hud_font_surface = Resource::Cache::get().getSurface(HUD_FONT_GIF_KEY);
|
||||||
auto hud_font = std::make_shared<Text>(hud_font_surface, Defaults::UI::Font::HUD_FNT);
|
auto hud_font = std::make_shared<Text>(hud_font_surface, UiConfig::get().hudFont().fnt);
|
||||||
hud_font->setDefaultKerning(Defaults::UI::Font::HUD_KERNING);
|
hud_font->setDefaultKerning(UiConfig::get().hudFont().kerning);
|
||||||
hud_ = std::make_unique<Hud>(entity_manager_.getBus(), hud_font, Resource::Cache::get().getPalette("ui"));
|
hud_ = std::make_unique<Hud>(entity_manager_.getBus(), hud_font);
|
||||||
|
|
||||||
// Música de fons en bucle infinit. El track ja està al Resource::Cache
|
// Música de fons en bucle infinit. El track ja està al Resource::Cache
|
||||||
// (preload al Director), així que playMusic només mou el punter cap a
|
// (preload al Director), així que playMusic només mou el punter cap a
|
||||||
@@ -159,10 +159,10 @@ GameScene::GameScene(Config config)
|
|||||||
// Font de l'overlay de pausa: reutilitzem la Surface ja cachejada al
|
// Font de l'overlay de pausa: reutilitzem la Surface ja cachejada al
|
||||||
// Director (PLACEHOLDER_NAME + ".gif"). Text només parseja el .fnt a
|
// Director (PLACEHOLDER_NAME + ".gif"). Text només parseja el .fnt a
|
||||||
// l'arrencada de l'escena, un sol cop.
|
// l'arrencada de l'escena, un sol cop.
|
||||||
const std::string PAUSE_FONT_KEY = std::string(Defaults::UI::Font::PLACEHOLDER_NAME) + ".gif";
|
const std::string PAUSE_FONT_KEY = UiConfig::get().placeholderFont().name + ".gif";
|
||||||
auto pause_surface = Resource::Cache::get().getSurface(PAUSE_FONT_KEY);
|
auto pause_surface = Resource::Cache::get().getSurface(PAUSE_FONT_KEY);
|
||||||
pause_font_ = std::make_shared<Text>(pause_surface, Defaults::UI::Font::PLACEHOLDER_FNT);
|
pause_font_ = std::make_shared<Text>(pause_surface, UiConfig::get().placeholderFont().fnt);
|
||||||
pause_font_->setDefaultKerning(Defaults::UI::Font::PLACEHOLDER_KERNING);
|
pause_font_->setDefaultKerning(UiConfig::get().placeholderFont().kerning);
|
||||||
}
|
}
|
||||||
|
|
||||||
GameScene::~GameScene() = default;
|
GameScene::~GameScene() = default;
|
||||||
@@ -252,12 +252,12 @@ void GameScene::update(int delta_ms) {
|
|||||||
// sola i quan es despausa ja està al seu lloc, igual que el Notifier.
|
// sola i quan es despausa ja està al seu lloc, igual que el Notifier.
|
||||||
if (hud_) { hud_->update(delta_ms); }
|
if (hud_) { hud_->update(delta_ms); }
|
||||||
|
|
||||||
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW));
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::render() {
|
void GameScene::render() {
|
||||||
const auto BG_ARGB = Resource::Cache::get().getPalette("ui")[UiColor::CANVAS_BG];
|
const auto BG_ARGB = Resource::Cache::get().getPalette("ui")[UiConfig::get().canvasBg()];
|
||||||
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BG_ARGB);
|
Screen::get().fillRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, BG_ARGB);
|
||||||
|
|
||||||
// Mapa visual: textura prerenderitzada del tilemap. Els sarcòfags
|
// Mapa visual: textura prerenderitzada del tilemap. Els sarcòfags
|
||||||
@@ -334,7 +334,7 @@ void GameScene::render() {
|
|||||||
Y,
|
Y,
|
||||||
TEXT,
|
TEXT,
|
||||||
PAUSE_KERNING,
|
PAUSE_KERNING,
|
||||||
Defaults::UI::NOTIFICATION_TEXT,
|
UiConfig::get().notificationText(),
|
||||||
STROKE_DIST,
|
STROKE_DIST,
|
||||||
OUTLINE_WHITE);
|
OUTLINE_WHITE);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/placeholder_input.hpp"
|
#include "game/scenes/placeholder_input.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ void IntroScene::update(int delta_ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW));
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntroScene::render() {
|
void IntroScene::render() {
|
||||||
@@ -70,13 +70,13 @@ void IntroScene::render() {
|
|||||||
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
||||||
SCENE_NAME,
|
SCENE_NAME,
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
GameCanvas::CENTER_X,
|
GameCanvas::CENTER_X,
|
||||||
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
||||||
std::to_string(SECS_LEFT),
|
std::to_string(SECS_LEFT),
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().render();
|
Notifier::get().render();
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/placeholder_input.hpp"
|
#include "game/scenes/placeholder_input.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ void LogoScene::update(int delta_ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW));
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogoScene::render() {
|
void LogoScene::render() {
|
||||||
@@ -71,13 +71,13 @@ void LogoScene::render() {
|
|||||||
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
||||||
SCENE_NAME,
|
SCENE_NAME,
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
GameCanvas::CENTER_X,
|
GameCanvas::CENTER_X,
|
||||||
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
||||||
std::to_string(SECS_LEFT),
|
std::to_string(SECS_LEFT),
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().render();
|
Notifier::get().render();
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/scenes/placeholder_input.hpp"
|
#include "game/scenes/placeholder_input.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ void TitleScene::update(int delta_ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().update(delta_ms);
|
Notifier::get().update(delta_ms);
|
||||||
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW));
|
RenderInfo::get().update(delta_ms, Options::buildRenderInfoFrame(UiConfig::get().renderInfoText(), UiConfig::get().renderInfoShadow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TitleScene::render() {
|
void TitleScene::render() {
|
||||||
@@ -70,13 +70,13 @@ void TitleScene::render() {
|
|||||||
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
GameCanvas::CENTER_Y + NAME_Y_OFFSET,
|
||||||
SCENE_NAME,
|
SCENE_NAME,
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||||
GameCanvas::CENTER_X,
|
GameCanvas::CENTER_X,
|
||||||
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
GameCanvas::CENTER_Y + TIMER_Y_OFFSET,
|
||||||
std::to_string(SECS_LEFT),
|
std::to_string(SECS_LEFT),
|
||||||
1,
|
1,
|
||||||
Defaults::UI::NOTIFICATION_TEXT);
|
UiConfig::get().notificationText());
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifier::get().render();
|
Notifier::get().render();
|
||||||
|
|||||||
+47
-41
@@ -11,22 +11,21 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
#include "core/resources/resource_cache.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
#include "utils/easing.hpp"
|
#include "utils/easing.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Colors HUD reaprofitats de la paleta UI: les etiquetes ("P1", "V",
|
// Els colors del HUD viuen a data/ui.yaml (colors.hud_label/value/flash)
|
||||||
// "T") usen un to apagat per crear jerarquia visual respecte als
|
// via UiConfig. Les etiquetes ("P1", "V", "T") usen un to apagat per
|
||||||
// valors. El flash temporal usa un to càlid (groc del títol del menú).
|
// crear jerarquia visual respecte als valors; el flash temporal usa un
|
||||||
constexpr Uint8 LABEL_COLOR = UiColor::MENU_LABEL; // 47
|
// to càlid que contrasta amb el valor normal.
|
||||||
constexpr Uint8 VALUE_COLOR = UiColor::MENU_VALUE; // 197
|
|
||||||
constexpr Uint8 BG_COLOR = UiColor::MENU_BG; // 40
|
|
||||||
constexpr Uint8 FLASH_COLOR = Defaults::UI::MENU_TITLE; // 213
|
|
||||||
|
|
||||||
// Amplada nominal d'una cel·la de dígit. Imposem monoespaiat als
|
// Amplada nominal d'una cel·la de dígit. Imposem monoespaiat als
|
||||||
// comptadors per evitar jitter visible quan canvien valors (Notifier
|
// comptadors per evitar jitter visible quan canvien valors (Notifier
|
||||||
@@ -66,16 +65,18 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, const Palette& ui_palette)
|
Hud::Hud(EventBus& bus, std::shared_ptr<Text> font)
|
||||||
: bus_(bus),
|
: bus_(bus),
|
||||||
font_(std::move(font)),
|
font_(std::move(font)) {
|
||||||
ui_palette_(ui_palette) {
|
|
||||||
// Geometria fixa dels tres blocs. Sumen exactament GameCanvas::WIDTH.
|
// Geometria fixa dels tres blocs. Sumen exactament GameCanvas::WIDTH.
|
||||||
|
// La durada de l'animació d'entrada ve de data/ui.yaml via UiConfig.
|
||||||
|
const int ENTRY_MS = UiConfig::get().hudEntryMs();
|
||||||
blocks_[0] = Block{
|
blocks_[0] = Block{
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
.w = LEFT_W,
|
.w = LEFT_W,
|
||||||
.h = HEIGHT_PX,
|
.h = HEIGHT_PX,
|
||||||
|
.entry_ms_left = ENTRY_MS,
|
||||||
.entry_offset_x = -LEFT_W,
|
.entry_offset_x = -LEFT_W,
|
||||||
.entry_offset_y = 0,
|
.entry_offset_y = 0,
|
||||||
};
|
};
|
||||||
@@ -84,6 +85,7 @@ Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, const Palette& ui_palette)
|
|||||||
.y = 0,
|
.y = 0,
|
||||||
.w = CENTER_W,
|
.w = CENTER_W,
|
||||||
.h = HEIGHT_PX,
|
.h = HEIGHT_PX,
|
||||||
|
.entry_ms_left = ENTRY_MS,
|
||||||
.entry_offset_x = 0,
|
.entry_offset_x = 0,
|
||||||
.entry_offset_y = -HEIGHT_PX,
|
.entry_offset_y = -HEIGHT_PX,
|
||||||
};
|
};
|
||||||
@@ -92,6 +94,7 @@ Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, const Palette& ui_palette)
|
|||||||
.y = 0,
|
.y = 0,
|
||||||
.w = RIGHT_W,
|
.w = RIGHT_W,
|
||||||
.h = HEIGHT_PX,
|
.h = HEIGHT_PX,
|
||||||
|
.entry_ms_left = ENTRY_MS,
|
||||||
.entry_offset_x = RIGHT_W,
|
.entry_offset_x = RIGHT_W,
|
||||||
.entry_offset_y = 0,
|
.entry_offset_y = 0,
|
||||||
};
|
};
|
||||||
@@ -113,8 +116,8 @@ Hud::Hud(EventBus& bus, std::shared_ptr<Text> font, const Palette& ui_palette)
|
|||||||
constexpr std::size_t IDX = 0;
|
constexpr std::size_t IDX = 0;
|
||||||
if (players_[IDX].lives > 0) { --players_[IDX].lives; }
|
if (players_[IDX].lives > 0) { --players_[IDX].lives; }
|
||||||
auto& blk = blocks_[IDX]; // P1 → bloc esquerre
|
auto& blk = blocks_[IDX]; // P1 → bloc esquerre
|
||||||
blk.flash_ms_left = FLASH_MS;
|
blk.flash_ms_left = UiConfig::get().hudFlashMs();
|
||||||
blk.shake_ms_left = SHAKE_MS;
|
blk.shake_ms_left = UiConfig::get().hudShakeMs();
|
||||||
blk.shake_intensity = 1.5F;
|
blk.shake_intensity = 1.5F;
|
||||||
std::cout << "[HUD] player_hit dmg=" << (dmg != nullptr ? *dmg : 0)
|
std::cout << "[HUD] player_hit dmg=" << (dmg != nullptr ? *dmg : 0)
|
||||||
<< " lives=" << players_[IDX].lives << '\n';
|
<< " lives=" << players_[IDX].lives << '\n';
|
||||||
@@ -165,7 +168,7 @@ auto Hud::computeOffset(const Block& b) -> SDL_FPoint {
|
|||||||
// Animació d'entrada: t va de 0 (recent iniciat) a 1 (acabada). Amb
|
// Animació d'entrada: t va de 0 (recent iniciat) a 1 (acabada). Amb
|
||||||
// outCubic, el bloc arrenca ràpid i s'atura suaument al seu lloc.
|
// outCubic, el bloc arrenca ràpid i s'atura suaument al seu lloc.
|
||||||
if (b.entry_ms_left > 0) {
|
if (b.entry_ms_left > 0) {
|
||||||
const float T = 1.0F - (static_cast<float>(b.entry_ms_left) / static_cast<float>(ENTRY_MS));
|
const float T = 1.0F - (static_cast<float>(b.entry_ms_left) / static_cast<float>(UiConfig::get().hudEntryMs()));
|
||||||
const float E = Easing::outCubic(T);
|
const float E = Easing::outCubic(T);
|
||||||
const float REMAIN = 1.0F - E; // 1 al principi → 0 al final
|
const float REMAIN = 1.0F - E; // 1 al principi → 0 al final
|
||||||
off.x += static_cast<float>(b.entry_offset_x) * REMAIN;
|
off.x += static_cast<float>(b.entry_offset_x) * REMAIN;
|
||||||
@@ -174,7 +177,7 @@ auto Hud::computeOffset(const Block& b) -> SDL_FPoint {
|
|||||||
|
|
||||||
// Shake: amplitud decau linealment amb el temps restant.
|
// Shake: amplitud decau linealment amb el temps restant.
|
||||||
if (b.shake_ms_left > 0) {
|
if (b.shake_ms_left > 0) {
|
||||||
const float DECAY = static_cast<float>(b.shake_ms_left) / static_cast<float>(SHAKE_MS);
|
const float DECAY = static_cast<float>(b.shake_ms_left) / static_cast<float>(UiConfig::get().hudShakeMs());
|
||||||
const auto JITTER = shakeOffset(b.shake_intensity * DECAY, b.shake_ms_left);
|
const auto JITTER = shakeOffset(b.shake_intensity * DECAY, b.shake_ms_left);
|
||||||
off.x += JITTER.x;
|
off.x += JITTER.x;
|
||||||
off.y += JITTER.y;
|
off.y += JITTER.y;
|
||||||
@@ -184,6 +187,14 @@ auto Hud::computeOffset(const Block& b) -> SDL_FPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Hud::render() const {
|
void Hud::render() const {
|
||||||
|
// Backdrop de la franja del HUD. Es pinta abans dels tres blocs perquè,
|
||||||
|
// quan un bloc es mou (entry slide o shake), la zona que deixa al
|
||||||
|
// descobert segueix mostrant el mateix color — en lloc del tilemap de
|
||||||
|
// sota, que causaria un trencament visual.
|
||||||
|
if (UiConfig::get().hudBgEnabled()) {
|
||||||
|
const auto& pal = Resource::Cache::get().getPalette("ui");
|
||||||
|
Screen::get().fillRect(0, 0, LEFT_W + CENTER_W + RIGHT_W, HEIGHT_PX, pal[UiConfig::get().hudBg()]);
|
||||||
|
}
|
||||||
renderBlock(BlockId::LEFT);
|
renderBlock(BlockId::LEFT);
|
||||||
renderBlock(BlockId::CENTER);
|
renderBlock(BlockId::CENTER);
|
||||||
renderBlock(BlockId::RIGHT);
|
renderBlock(BlockId::RIGHT);
|
||||||
@@ -196,16 +207,12 @@ void Hud::renderBlock(BlockId id) const {
|
|||||||
const int X = b.x + static_cast<int>(std::round(OFF.x));
|
const int X = b.x + static_cast<int>(std::round(OFF.x));
|
||||||
const int Y = b.y + static_cast<int>(std::round(OFF.y));
|
const int Y = b.y + static_cast<int>(std::round(OFF.y));
|
||||||
|
|
||||||
// Fons sòlid del bloc (mateixa convenció que `Notifier::render`):
|
|
||||||
// ARGB directe al canvas, sense tocar surfaces paletades.
|
|
||||||
Screen::get().fillRect(X, Y, b.w, b.h, ui_palette_[BG_COLOR]);
|
|
||||||
|
|
||||||
switch (b.mode) {
|
switch (b.mode) {
|
||||||
case Mode::IDLE:
|
case Mode::IDLE:
|
||||||
if (id == BlockId::LEFT) {
|
if (id == BlockId::LEFT) {
|
||||||
renderIdleSide(X, Y, b.w, b, 0, "P1");
|
renderIdleSide(X, Y, b.w, b, 0, Locale::get().text("hud.p1"));
|
||||||
} else if (id == BlockId::RIGHT) {
|
} else if (id == BlockId::RIGHT) {
|
||||||
renderIdleSide(X, Y, b.w, b, 1, "P2");
|
renderIdleSide(X, Y, b.w, b, 1, Locale::get().text("hud.p2"));
|
||||||
} else {
|
} else {
|
||||||
renderIdleCenter(X, Y, b.w);
|
renderIdleCenter(X, Y, b.w);
|
||||||
}
|
}
|
||||||
@@ -218,24 +225,23 @@ void Hud::renderBlock(BlockId id) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hud::renderIdleSide(int x, int y, int w, const Block& blk, int player_index, const char* label) const {
|
void Hud::renderIdleSide(int x, int y, int w, const Block& blk, int player_index, const std::string& label) const {
|
||||||
const auto& p = players_[static_cast<std::size_t>(player_index)];
|
const auto& p = players_[static_cast<std::size_t>(player_index)];
|
||||||
const Uint8 SCORE_C = (blk.flash_ms_left > 0) ? FLASH_COLOR : VALUE_COLOR;
|
const Uint8 SCORE_C = (blk.flash_ms_left > 0) ? UiConfig::get().hudFlash() : UiConfig::get().hudValue();
|
||||||
|
|
||||||
// --- Línia 1: "<label> 000000" centrada ---
|
// --- Línia 1: "<label> 000000" centrada ---
|
||||||
const std::string LBL{label};
|
|
||||||
const std::string SCORE = formatScore(p.score);
|
const std::string SCORE = formatScore(p.score);
|
||||||
const int LBL_W = font_->length(LBL);
|
const int LBL_W = font_->length(label);
|
||||||
const int SC_W = font_->lengthMonoDigits(SCORE, MONO_DIGIT_W);
|
const int SC_W = font_->lengthMonoDigits(SCORE, MONO_DIGIT_W);
|
||||||
const int LINE1_W = LBL_W + LABEL_GAP + SC_W;
|
const int LINE1_W = LBL_W + LABEL_GAP + SC_W;
|
||||||
int xx = x + ((w - LINE1_W) / 2);
|
int xx = x + ((w - LINE1_W) / 2);
|
||||||
font_->writeColored(xx, y + LINE1_Y, LBL, LABEL_COLOR);
|
font_->writeColored(xx, y + LINE1_Y, label, UiConfig::get().hudLabel());
|
||||||
xx += LBL_W + LABEL_GAP;
|
xx += LBL_W + LABEL_GAP;
|
||||||
font_->writeMonoDigits(xx, y + LINE1_Y, SCORE, SCORE_C, MONO_DIGIT_W);
|
font_->writeMonoDigits(xx, y + LINE1_Y, SCORE, SCORE_C, MONO_DIGIT_W);
|
||||||
|
|
||||||
// --- Línia 2: "V <vides> T <tresors>" centrada ---
|
// --- Línia 2: "<vides> <n> <tresors> <n>" centrada ---
|
||||||
const std::string V_LBL{"V"};
|
const std::string V_LBL = Locale::get().text("hud.lives");
|
||||||
const std::string T_LBL{"T"};
|
const std::string T_LBL = Locale::get().text("hud.treasures");
|
||||||
const std::string V_VAL = std::to_string(std::clamp(p.lives, 0, 9));
|
const std::string V_VAL = std::to_string(std::clamp(p.lives, 0, 9));
|
||||||
const std::string T_VAL = formatTwoDigits(p.treasures);
|
const std::string T_VAL = formatTwoDigits(p.treasures);
|
||||||
const int V_LW = font_->length(V_LBL);
|
const int V_LW = font_->length(V_LBL);
|
||||||
@@ -244,35 +250,35 @@ void Hud::renderIdleSide(int x, int y, int w, const Block& blk, int player_index
|
|||||||
const int T_VW = font_->lengthMonoDigits(T_VAL, MONO_DIGIT_W);
|
const int T_VW = font_->lengthMonoDigits(T_VAL, MONO_DIGIT_W);
|
||||||
const int LINE2_W = V_LW + LABEL_GAP + V_VW + FIELD_GAP + T_LW + LABEL_GAP + T_VW;
|
const int LINE2_W = V_LW + LABEL_GAP + V_VW + FIELD_GAP + T_LW + LABEL_GAP + T_VW;
|
||||||
xx = x + ((w - LINE2_W) / 2);
|
xx = x + ((w - LINE2_W) / 2);
|
||||||
font_->writeColored(xx, y + LINE2_Y, V_LBL, LABEL_COLOR);
|
font_->writeColored(xx, y + LINE2_Y, V_LBL, UiConfig::get().hudLabel());
|
||||||
xx += V_LW + LABEL_GAP;
|
xx += V_LW + LABEL_GAP;
|
||||||
font_->writeMonoDigits(xx, y + LINE2_Y, V_VAL, VALUE_COLOR, MONO_DIGIT_W);
|
font_->writeMonoDigits(xx, y + LINE2_Y, V_VAL, UiConfig::get().hudValue(), MONO_DIGIT_W);
|
||||||
xx += V_VW + FIELD_GAP;
|
xx += V_VW + FIELD_GAP;
|
||||||
font_->writeColored(xx, y + LINE2_Y, T_LBL, LABEL_COLOR);
|
font_->writeColored(xx, y + LINE2_Y, T_LBL, UiConfig::get().hudLabel());
|
||||||
xx += T_LW + LABEL_GAP;
|
xx += T_LW + LABEL_GAP;
|
||||||
font_->writeMonoDigits(xx, y + LINE2_Y, T_VAL, VALUE_COLOR, MONO_DIGIT_W);
|
font_->writeMonoDigits(xx, y + LINE2_Y, T_VAL, UiConfig::get().hudValue(), MONO_DIGIT_W);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Hud::renderIdleCenter(int x, int y, int w) const {
|
void Hud::renderIdleCenter(int x, int y, int w) const {
|
||||||
// --- Línia 1: "PIRAMIDE <n>" centrada ---
|
// --- Línia 1: "<piramide> <n>" centrada ---
|
||||||
const std::string PIR_LBL{"PIRAMIDE"};
|
const std::string PIR_LBL = Locale::get().text("hud.pyramid");
|
||||||
const std::string PIR_VAL = std::to_string(pyramid_);
|
const std::string PIR_VAL = std::to_string(pyramid_);
|
||||||
const int PL_W = font_->length(PIR_LBL);
|
const int PL_W = font_->length(PIR_LBL);
|
||||||
const int PV_W = font_->lengthMonoDigits(PIR_VAL, MONO_DIGIT_W);
|
const int PV_W = font_->lengthMonoDigits(PIR_VAL, MONO_DIGIT_W);
|
||||||
const int LINE1_W = PL_W + LABEL_GAP + PV_W;
|
const int LINE1_W = PL_W + LABEL_GAP + PV_W;
|
||||||
int xx = x + ((w - LINE1_W) / 2);
|
int xx = x + ((w - LINE1_W) / 2);
|
||||||
font_->writeColored(xx, y + LINE1_Y, PIR_LBL, LABEL_COLOR);
|
font_->writeColored(xx, y + LINE1_Y, PIR_LBL, UiConfig::get().hudLabel());
|
||||||
xx += PL_W + LABEL_GAP;
|
xx += PL_W + LABEL_GAP;
|
||||||
font_->writeMonoDigits(xx, y + LINE1_Y, PIR_VAL, VALUE_COLOR, MONO_DIGIT_W);
|
font_->writeMonoDigits(xx, y + LINE1_Y, PIR_VAL, UiConfig::get().hudValue(), MONO_DIGIT_W);
|
||||||
|
|
||||||
// --- Línia 2: "SALA <r>" centrada ---
|
// --- Línia 2: "<sala> <r>" centrada ---
|
||||||
const std::string SALA_LBL{"SALA"};
|
const std::string SALA_LBL = Locale::get().text("hud.room");
|
||||||
const std::string SALA_VAL = std::to_string(room_);
|
const std::string SALA_VAL = std::to_string(room_);
|
||||||
const int SL_W = font_->length(SALA_LBL);
|
const int SL_W = font_->length(SALA_LBL);
|
||||||
const int SV_W = font_->lengthMonoDigits(SALA_VAL, MONO_DIGIT_W);
|
const int SV_W = font_->lengthMonoDigits(SALA_VAL, MONO_DIGIT_W);
|
||||||
const int LINE2_W = SL_W + LABEL_GAP + SV_W;
|
const int LINE2_W = SL_W + LABEL_GAP + SV_W;
|
||||||
xx = x + ((w - LINE2_W) / 2);
|
xx = x + ((w - LINE2_W) / 2);
|
||||||
font_->writeColored(xx, y + LINE2_Y, SALA_LBL, LABEL_COLOR);
|
font_->writeColored(xx, y + LINE2_Y, SALA_LBL, UiConfig::get().hudLabel());
|
||||||
xx += SL_W + LABEL_GAP;
|
xx += SL_W + LABEL_GAP;
|
||||||
font_->writeMonoDigits(xx, y + LINE2_Y, SALA_VAL, VALUE_COLOR, MONO_DIGIT_W);
|
font_->writeMonoDigits(xx, y + LINE2_Y, SALA_VAL, UiConfig::get().hudValue(), MONO_DIGIT_W);
|
||||||
}
|
}
|
||||||
+6
-15
@@ -5,9 +5,9 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/rendering/surface.hpp" // Palette
|
|
||||||
#include "game/entities/event_bus.hpp"
|
#include "game/entities/event_bus.hpp"
|
||||||
|
|
||||||
class Text;
|
class Text;
|
||||||
@@ -41,7 +41,7 @@ class Hud {
|
|||||||
WIN,
|
WIN,
|
||||||
EMPTY };
|
EMPTY };
|
||||||
|
|
||||||
Hud(EventBus& bus, std::shared_ptr<Text> font, const Palette& ui_palette);
|
Hud(EventBus& bus, std::shared_ptr<Text> font);
|
||||||
~Hud();
|
~Hud();
|
||||||
|
|
||||||
Hud(const Hud&) = delete;
|
Hud(const Hud&) = delete;
|
||||||
@@ -89,15 +89,6 @@ class Hud {
|
|||||||
static constexpr int LINE1_Y = 4;
|
static constexpr int LINE1_Y = 4;
|
||||||
static constexpr int LINE2_Y = 13;
|
static constexpr int LINE2_Y = 13;
|
||||||
|
|
||||||
// Durada de l'animació d'entrada — els tres blocs alhora, amb easing
|
|
||||||
// outCubic. 400 ms és prou per a percebre el moviment sense fer
|
|
||||||
// esperar massa al jugador.
|
|
||||||
static constexpr int ENTRY_MS = 400;
|
|
||||||
|
|
||||||
// Durades per defecte d'efectes transitoris.
|
|
||||||
static constexpr int FLASH_MS = 250;
|
|
||||||
static constexpr int SHAKE_MS = 300;
|
|
||||||
|
|
||||||
// Vides inicials per jugador (placeholder fins que GameScene ho
|
// Vides inicials per jugador (placeholder fins que GameScene ho
|
||||||
// configure des del Config del nivell).
|
// configure des del Config del nivell).
|
||||||
static constexpr int DEFAULT_LIVES = 3;
|
static constexpr int DEFAULT_LIVES = 3;
|
||||||
@@ -112,8 +103,9 @@ class Hud {
|
|||||||
int h{0};
|
int h{0};
|
||||||
|
|
||||||
// Animació d'entrada: ms restants fins a quedar-se a la posició
|
// Animació d'entrada: ms restants fins a quedar-se a la posició
|
||||||
// final. Quan val 0 el bloc està quiet.
|
// final. Quan val 0 el bloc està quiet. El Hud ctor l'inicialitza
|
||||||
int entry_ms_left{ENTRY_MS};
|
// amb la durada viva a data/ui.yaml (hud.entry_ms).
|
||||||
|
int entry_ms_left{0};
|
||||||
// Vector inicial des d'on entra: {-LEFT_W,0} per al bloc esquerre,
|
// Vector inicial des d'on entra: {-LEFT_W,0} per al bloc esquerre,
|
||||||
// {0,-HEIGHT_PX} per al central i {+RIGHT_W,0} per al dret.
|
// {0,-HEIGHT_PX} per al central i {+RIGHT_W,0} per al dret.
|
||||||
int entry_offset_x{0};
|
int entry_offset_x{0};
|
||||||
@@ -143,7 +135,7 @@ class Hud {
|
|||||||
|
|
||||||
// Subrutines del mode `Idle` per als tres blocs. Reben el rectangle
|
// Subrutines del mode `Idle` per als tres blocs. Reben el rectangle
|
||||||
// ja desplaçat (x, y) on han d'escriure.
|
// ja desplaçat (x, y) on han d'escriure.
|
||||||
void renderIdleSide(int x, int y, int w, const Block& blk, int player_index, const char* label) const;
|
void renderIdleSide(int x, int y, int w, const Block& blk, int player_index, const std::string& label) const;
|
||||||
void renderIdleCenter(int x, int y, int w) const;
|
void renderIdleCenter(int x, int y, int w) const;
|
||||||
|
|
||||||
// Calcula l'offset transitori (entrada + shake) en píxels per a un
|
// Calcula l'offset transitori (entrada + shake) en píxels per a un
|
||||||
@@ -155,7 +147,6 @@ class Hud {
|
|||||||
std::vector<EventBus::SubId> subs_;
|
std::vector<EventBus::SubId> subs_;
|
||||||
|
|
||||||
std::shared_ptr<Text> font_;
|
std::shared_ptr<Text> font_;
|
||||||
Palette ui_palette_{};
|
|
||||||
|
|
||||||
std::array<Block, 3> blocks_;
|
std::array<Block, 3> blocks_;
|
||||||
std::array<PlayerStats, 2> players_;
|
std::array<PlayerStats, 2> players_;
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#include "core/rendering/screen.hpp"
|
#include "core/rendering/screen.hpp"
|
||||||
#include "core/rendering/text.hpp"
|
#include "core/rendering/text.hpp"
|
||||||
#include "core/system/event_buffer.hpp"
|
#include "core/system/event_buffer.hpp"
|
||||||
|
#include "core/ui/ui_config.hpp"
|
||||||
#include "game/options.hpp"
|
#include "game/options.hpp"
|
||||||
#include "game/options_apply.hpp"
|
#include "game/options_apply.hpp"
|
||||||
#include "game/ui/ui_palette.hpp"
|
|
||||||
#include "utils/defaults.hpp"
|
#include "utils/defaults.hpp"
|
||||||
#include "utils/defines.hpp"
|
#include "utils/defines.hpp"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
@@ -844,8 +844,8 @@ auto ServiceMenu::targetBoxHeight(const Page& page) const -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServiceMenu::renderBox(int box_x, int box_y, int box_w, int box_h, float eased) const {
|
void ServiceMenu::renderBox(int box_x, int box_y, int box_w, int box_h, float eased) const {
|
||||||
const std::uint32_t BG_ARGB = ui_palette_[UiColor::MENU_BG];
|
const std::uint32_t BG_ARGB = ui_palette_[UiConfig::get().menuBg()];
|
||||||
const std::uint32_t BORDER_ARGB = ui_palette_[UiColor::MENU_BORDER];
|
const std::uint32_t BORDER_ARGB = ui_palette_[UiConfig::get().menuBorder()];
|
||||||
const auto ALPHA = static_cast<std::uint8_t>(std::lround(BG_ALPHA * eased));
|
const auto ALPHA = static_cast<std::uint8_t>(std::lround(BG_ALPHA * eased));
|
||||||
|
|
||||||
// Fons semi-transparent (el joc queda visible apagat per darrere).
|
// Fons semi-transparent (el joc queda visible apagat per darrere).
|
||||||
@@ -877,7 +877,7 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
|
|||||||
cursor_y,
|
cursor_y,
|
||||||
Locale::get().text(page.title_key),
|
Locale::get().text(page.title_key),
|
||||||
TITLE_KERNING,
|
TITLE_KERNING,
|
||||||
UiColor::MENU_TITLE);
|
UiConfig::get().menuTitle());
|
||||||
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
|
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
|
||||||
|
|
||||||
if (!page.subtitle.empty()) {
|
if (!page.subtitle.empty()) {
|
||||||
@@ -886,11 +886,11 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
|
|||||||
cursor_y,
|
cursor_y,
|
||||||
page.subtitle,
|
page.subtitle,
|
||||||
TITLE_KERNING,
|
TITLE_KERNING,
|
||||||
UiColor::MENU_TITLE);
|
UiConfig::get().menuTitle());
|
||||||
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
|
cursor_y += TITLE_H + GAP_Y - TITLE_DESCENDER_TRIM;
|
||||||
}
|
}
|
||||||
|
|
||||||
Screen::get().fillRect(box_x + PAD_X, cursor_y, box_w - (2 * PAD_X), 1, ui_palette_[UiColor::MENU_BORDER]);
|
Screen::get().fillRect(box_x + PAD_X, cursor_y, box_w - (2 * PAD_X), 1, ui_palette_[UiConfig::get().menuBorder()]);
|
||||||
cursor_y += 1 + GAP_Y;
|
cursor_y += 1 + GAP_Y;
|
||||||
|
|
||||||
bool first_item = true;
|
bool first_item = true;
|
||||||
@@ -907,14 +907,14 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
|
|||||||
: Locale::get().text(item.label_key);
|
: Locale::get().text(item.label_key);
|
||||||
const std::string VALUE = formatValue(item);
|
const std::string VALUE = formatValue(item);
|
||||||
|
|
||||||
Uint8 label_color = UiColor::MENU_LABEL;
|
Uint8 label_color = UiConfig::get().menuLabel();
|
||||||
if (item.kind == Item::Kind::LABEL) {
|
if (item.kind == Item::Kind::LABEL) {
|
||||||
// Mateix to que el títol de la pàgina: les LABELs actuen com
|
// Mateix to que el títol de la pàgina: les LABELs actuen com
|
||||||
// a subtítol-divisor (p.ex. "Jugador 1" a Teclat, "ver.X.Y" a
|
// a subtítol-divisor (p.ex. "Jugador 1" a Teclat, "ver.X.Y" a
|
||||||
// Sistema) i han de tenir la mateixa presència visual.
|
// Sistema) i han de tenir la mateixa presència visual.
|
||||||
label_color = UiColor::MENU_TITLE;
|
label_color = UiConfig::get().menuTitle();
|
||||||
} else if (SELECTED) {
|
} else if (SELECTED) {
|
||||||
label_color = UiColor::MENU_CURSOR;
|
label_color = UiConfig::get().menuCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
int label_x = LABEL_X;
|
int label_x = LABEL_X;
|
||||||
@@ -931,7 +931,7 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
|
|||||||
cursor_y,
|
cursor_y,
|
||||||
"> ",
|
"> ",
|
||||||
ITEM_KERNING,
|
ITEM_KERNING,
|
||||||
UiColor::MENU_CURSOR);
|
UiConfig::get().menuCursor());
|
||||||
}
|
}
|
||||||
|
|
||||||
items_font_->writeDX(Text::COLOR_FLAG,
|
items_font_->writeDX(Text::COLOR_FLAG,
|
||||||
@@ -951,7 +951,7 @@ void ServiceMenu::renderPage(const Page& page, int box_x, int box_y, int box_w)
|
|||||||
cursor_y,
|
cursor_y,
|
||||||
VALUE,
|
VALUE,
|
||||||
ITEM_KERNING,
|
ITEM_KERNING,
|
||||||
SELECTED ? UiColor::MENU_CURSOR : UiColor::MENU_VALUE);
|
SELECTED ? UiConfig::get().menuCursor() : UiConfig::get().menuValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor_y += ITEM_H;
|
cursor_y += ITEM_H;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/defaults.hpp"
|
|
||||||
|
|
||||||
// Alias semàntics als índexs de paleta UI (viuen a `Defaults::UI`). Canviar
|
|
||||||
// l'aparença d'un component només requereix tocar-los allà; ací només
|
|
||||||
// reexportem amb els noms que consumeix el codi del joc.
|
|
||||||
namespace UiColor {
|
|
||||||
|
|
||||||
constexpr auto RENDER_INFO_TEXT = Defaults::UI::RENDER_INFO_TEXT;
|
|
||||||
constexpr auto RENDER_INFO_SHADOW = Defaults::UI::RENDER_INFO_SHADOW;
|
|
||||||
constexpr auto CANVAS_BG = Defaults::UI::CANVAS_BG;
|
|
||||||
|
|
||||||
constexpr auto MENU_BG = Defaults::UI::MENU_BG;
|
|
||||||
constexpr auto MENU_BORDER = Defaults::UI::MENU_BORDER;
|
|
||||||
constexpr auto MENU_TITLE = Defaults::UI::MENU_TITLE;
|
|
||||||
constexpr auto MENU_LABEL = Defaults::UI::MENU_LABEL;
|
|
||||||
constexpr auto MENU_VALUE = Defaults::UI::MENU_VALUE;
|
|
||||||
constexpr auto MENU_CURSOR = Defaults::UI::MENU_CURSOR;
|
|
||||||
constexpr auto MENU_DISABLED = Defaults::UI::MENU_DISABLED;
|
|
||||||
|
|
||||||
} // namespace UiColor
|
|
||||||
+34
-19
@@ -84,11 +84,11 @@ namespace Defaults {
|
|||||||
constexpr float SPEED_PX_PER_S = 60.0F; // Velocitat base del jugador (sense sprint / slow-zones).
|
constexpr float SPEED_PX_PER_S = 60.0F; // Velocitat base del jugador (sense sprint / slow-zones).
|
||||||
} // namespace Player
|
} // namespace Player
|
||||||
|
|
||||||
// Índexs de paleta (sega-cube.pal) i temps en ms per als components
|
// Fallbacks del tema d'UI: índexs de paleta, timings (ms) i fonts.
|
||||||
// de UI. Centralitzats ací perquè un "tema" viu en un únic lloc: tocar
|
// El tema en viu és a data/ui.yaml, carregat per UiConfig a
|
||||||
// els colors o durades de l'overlay/notificador no requereix buscar
|
// l'arrencada; aquests valors només s'apliquen si el fitxer falta o
|
||||||
// pel codi. Si algun dia es volen temes múltiples, es pot moure a YAML
|
// li manca alguna clau. Ací només cal tocar-los si es vol canviar el
|
||||||
// sense més impacte que dos accessors extra.
|
// fallback "de fàbrica".
|
||||||
namespace UI {
|
namespace UI {
|
||||||
// Fonts d'UI centralitzades: un únic lloc per conèixer la carpeta
|
// Fonts d'UI centralitzades: un únic lloc per conèixer la carpeta
|
||||||
// (data/fonts/), el nom-clau al Resource::Cache i els fitxers .gif /
|
// (data/fonts/), el nom-clau al Resource::Cache i els fitxers .gif /
|
||||||
@@ -128,32 +128,47 @@ namespace Defaults {
|
|||||||
} // namespace Font
|
} // namespace Font
|
||||||
|
|
||||||
// Overlay de RenderInfo (F12).
|
// Overlay de RenderInfo (F12).
|
||||||
constexpr Uint8 RENDER_INFO_TEXT = 47;
|
constexpr Uint8 RENDER_INFO_TEXT = 58;
|
||||||
constexpr Uint8 RENDER_INFO_SHADOW = 34;
|
constexpr Uint8 RENDER_INFO_SHADOW = 16;
|
||||||
|
|
||||||
// Fons del canvas (zona fora del tilemap en letterbox/integer).
|
// Fons del canvas (zona fora del tilemap en letterbox/integer).
|
||||||
constexpr Uint8 CANVAS_BG = 65;
|
constexpr Uint8 CANVAS_BG = 72;
|
||||||
|
|
||||||
// Preset per defecte del Notifier.
|
// Preset per defecte del Notifier.
|
||||||
constexpr Uint8 NOTIFICATION_TEXT = 197;
|
constexpr Uint8 NOTIFICATION_TEXT = 232;
|
||||||
constexpr Uint8 NOTIFICATION_BG = 96;
|
constexpr Uint8 NOTIFICATION_BG = 64;
|
||||||
constexpr int NOTIFICATION_TIME_MS = 4000;
|
constexpr int NOTIFICATION_TIME_MS = 4000;
|
||||||
|
|
||||||
// Preset per a la confirmació d'eixida (ESC doble). La durada ha
|
// Preset per a la confirmació d'eixida (ESC doble). La durada ha
|
||||||
// de coincidir amb la del Notifier perquè el missatge i la finestra
|
// de coincidir amb la del Notifier perquè el missatge i la finestra
|
||||||
// de confirmació expiren alhora (veure GlobalInputs::onExit).
|
// de confirmació expiren alhora (veure GlobalInputs::onExit).
|
||||||
constexpr Uint8 EXIT_TEXT = 88;
|
constexpr Uint8 EXIT_TEXT = 71;
|
||||||
constexpr Uint8 EXIT_BG = 40;
|
constexpr Uint8 EXIT_BG = 2;
|
||||||
constexpr int EXIT_TIME_MS = 4000;
|
constexpr int EXIT_TIME_MS = 4000;
|
||||||
|
|
||||||
// Índexs de paleta per al menú de servei (F9).
|
// Índexs de paleta per al menú de servei (F9).
|
||||||
constexpr Uint8 MENU_BG = 40; // Fons de la caixa.
|
constexpr Uint8 MENU_BG = 2; // Fons de la caixa.
|
||||||
constexpr Uint8 MENU_BORDER = 197; // Vora.
|
constexpr Uint8 MENU_BORDER = 232; // Vora.
|
||||||
constexpr Uint8 MENU_TITLE = 213; // Títol i subtítol.
|
constexpr Uint8 MENU_TITLE = 237; // Títol i subtítol.
|
||||||
constexpr Uint8 MENU_LABEL = 47; // Etiquetes dels items.
|
constexpr Uint8 MENU_LABEL = 58; // Etiquetes dels items.
|
||||||
constexpr Uint8 MENU_VALUE = 197; // Valor actual.
|
constexpr Uint8 MENU_VALUE = 232; // Valor actual.
|
||||||
constexpr Uint8 MENU_CURSOR = 88; // Item seleccionat.
|
constexpr Uint8 MENU_CURSOR = 71; // Item seleccionat.
|
||||||
constexpr Uint8 MENU_DISABLED = 34; // Label en gris quan no aplica.
|
constexpr Uint8 MENU_DISABLED = 16; // Label en gris quan no aplica.
|
||||||
|
|
||||||
|
// Índexs de paleta per al marcador de joc (HUD de la GameScene).
|
||||||
|
// Arranca reciclant els colors del menú per coherència visual;
|
||||||
|
// separant els noms a constants pròpies, tocar-los a posteriori
|
||||||
|
// no afecta el menú.
|
||||||
|
constexpr Uint8 HUD_BG = 0; // Fons de cada bloc.
|
||||||
|
constexpr Uint8 HUD_LABEL = 58; // Etiquetes ("P1", "V", "T").
|
||||||
|
constexpr Uint8 HUD_VALUE = 187; // Valors (score, vides, tresors).
|
||||||
|
constexpr Uint8 HUD_FLASH = 237; // Color temporal del valor en flash.
|
||||||
|
|
||||||
|
// Timings d'animació del HUD. L'entrada són els tres blocs lliscant
|
||||||
|
// a la seua posició; flash i shake acompanyen un `player_hit`.
|
||||||
|
constexpr int HUD_ENTRY_MS = 400;
|
||||||
|
constexpr int HUD_FLASH_MS = 250;
|
||||||
|
constexpr int HUD_SHAKE_MS = 300;
|
||||||
} // namespace UI
|
} // namespace UI
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
|
|||||||
Reference in new issue
Block a user