Compare commits
169
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31ee7989de | ||
|
|
3948607b1e | ||
|
|
6c180372fe | ||
|
|
fed04b66b8 | ||
|
|
a4ca718070 | ||
|
|
8dbd3e1dd2 | ||
|
|
7a9a2b9832 | ||
|
|
e8757c33e3 | ||
|
|
b498a12458 | ||
|
|
c9f098693c | ||
|
|
7aeffac9fc | ||
|
|
37e7e95ace | ||
|
|
e3f56c4d12 | ||
|
|
3675ec634f | ||
|
|
d8273a225c | ||
|
|
c1c8174b27 | ||
|
|
5697f5c064 | ||
|
|
3ce16cf7f4 | ||
|
|
2abd5b0c3c | ||
|
|
4b29bf7a30 | ||
|
|
6f1bbe4f2f | ||
|
|
f64364f37d | ||
|
|
d16e0bbfbf | ||
|
|
70ae41bd29 | ||
|
|
abf6863044 | ||
|
|
4279bd9675 | ||
|
|
5910110b95 | ||
|
|
44594f7011 | ||
|
|
dbb5301ade | ||
|
|
74a4290be4 | ||
|
|
3ce892f846 | ||
|
|
68cebce04a | ||
|
|
1646e964b5 | ||
|
|
2ee97ad4ce | ||
|
|
109ddc9e1e | ||
|
|
804f8aa1fa | ||
|
|
49b97ba508 | ||
|
|
6c11d8165d | ||
|
|
93554b39ba | ||
|
|
6af0e9cf28 | ||
|
|
083deb8357 | ||
|
|
ffb375faeb | ||
|
|
b8cbd8ff6a | ||
|
|
c2bd761383 | ||
|
|
878e3438c8 | ||
|
|
3da65370e0 | ||
|
|
83e646cba9 | ||
|
|
0566166af4 | ||
|
|
ed0b8c3911 | ||
|
|
03d753a83f | ||
|
|
cf51ed1988 | ||
|
|
70f46e89d3 | ||
|
|
efd760fb2e | ||
|
|
f303d24f4a | ||
|
|
9688e88a7c | ||
|
|
c8a6325615 | ||
|
|
798db14bb5 | ||
|
|
178d78ef72 | ||
|
|
8139a0ea54 | ||
|
|
327c2bec70 | ||
|
|
fc0195d9f7 | ||
|
|
db42bca44b | ||
|
|
b3623c84a5 | ||
|
|
45c1f9004b | ||
|
|
845a395ff1 | ||
|
|
f63653c307 | ||
|
|
5b523c68a7 | ||
|
|
03efbe0681 | ||
|
|
cebd3d81e7 | ||
|
|
de53bebeb8 | ||
|
|
6ccaedd48f | ||
|
|
707856fcf2 | ||
|
|
3c3dc35e1d | ||
|
|
f9820bd226 | ||
|
|
fe2ef92ef3 | ||
|
|
9364768397 | ||
|
|
9c28d5a3b7 | ||
|
|
d5078b978c | ||
|
|
d49594d3fb | ||
|
|
619ce932f9 | ||
|
|
b564d0b1c1 | ||
|
|
fc1fc9f170 | ||
|
|
fd5bcfae90 | ||
|
|
3ee0a53688 | ||
|
|
62c081ecf2 | ||
|
|
d9beb647c1 | ||
|
|
f8f8a80a21 | ||
|
|
ac8023d50f | ||
|
|
769f2f5702 | ||
|
|
2f6839bcc4 | ||
|
|
d37109f5f7 | ||
|
|
15bd9fc43c | ||
|
|
8049e90853 | ||
|
|
e47a2c3e5c | ||
|
|
9512c22e1f | ||
|
|
8a77001016 | ||
|
|
9b0214ee55 | ||
|
|
7ad735552a | ||
|
|
ea35472ef8 | ||
|
|
50c5621d8a | ||
|
|
da275cfd52 | ||
|
|
0f4ee5d965 | ||
|
|
af8c0f5ccd | ||
|
|
8ea370a1d1 | ||
|
|
fcf0de72af | ||
|
|
320c5e6ce5 | ||
|
|
7a858cc424 | ||
|
|
c0292842a5 | ||
|
|
717902462b | ||
|
|
1d73b9d224 | ||
|
|
51f262efa0 | ||
|
|
a6e4e3f71f | ||
|
|
4387d223b9 | ||
|
|
00bfaf6360 | ||
|
|
709bd7be52 | ||
|
|
80f4348e21 | ||
|
|
e630827b34 | ||
|
|
106d9b0fc0 | ||
|
|
4a26f79945 | ||
|
|
1a199f6ce3 | ||
|
|
ec59e77867 | ||
|
|
cb28d2b611 | ||
|
|
27b6ddf5cb | ||
|
|
475c865e33 | ||
|
|
0b4ba062bb | ||
|
|
28f5e66662 | ||
|
|
e225a076f0 | ||
|
|
774c8ad0f8 | ||
|
|
52d1e7414d | ||
|
|
af4cb53e5f | ||
|
|
c019271e12 | ||
|
|
82ae2bce9d | ||
|
|
092e259a06 | ||
|
|
08b4bbfe91 | ||
|
|
560c51cf27 | ||
|
|
674f86b18a | ||
|
|
e3f10e0926 | ||
|
|
d7223d7387 | ||
|
|
45331c2a60 | ||
|
|
33f50a2c69 | ||
|
|
6c8e4d5cbd | ||
|
|
3b7215876a | ||
|
|
c74f5890bd | ||
|
|
fbd3c71ba7 | ||
|
|
128f9ab9d4 | ||
|
|
36fb359aa2 | ||
|
|
1bd73d69fe | ||
|
|
fb48285143 | ||
|
|
3de50b8370 | ||
|
|
e61cbe25b7 | ||
|
|
8395830be6 | ||
|
|
6e4ec2b9d8 | ||
|
|
f501bb8e28 | ||
|
|
d07cd3397d | ||
|
|
e008fb108a | ||
|
|
9aaffff7a8 | ||
|
|
7357b4a5df | ||
|
|
1ddc298a74 | ||
|
|
e2a9442aa6 | ||
|
|
aa0180571e | ||
|
|
7f7be39230 | ||
|
|
4d95415232 | ||
|
|
2cbd80a004 | ||
|
|
9abf8101da | ||
|
|
24badd06a5 | ||
|
|
7a03ef8eaf | ||
|
|
f3ea507313 | ||
|
|
4b0388a0e1 | ||
|
|
a84137b5ff |
No files matched your search
@@ -32,7 +32,7 @@ jobs:
|
|||||||
git \
|
git \
|
||||||
libssl-dev
|
libssl-dev
|
||||||
- name: Build Linux
|
- name: Build Linux
|
||||||
run: ./scripts/build-linux.sh
|
run: ./scripts/linux/build-linux.sh
|
||||||
- name: Upload Linux binary
|
- name: Upload Linux binary
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
- name: Setup pspdev
|
- name: Setup pspdev
|
||||||
uses: ./.github/actions/setup-pspdev
|
uses: ./.github/actions/setup-pspdev
|
||||||
- name: Build PSP
|
- name: Build PSP
|
||||||
run: ./scripts/build-psp.sh
|
run: ./scripts/psp/build-psp.sh
|
||||||
- name: Move EBOOT.PBP to Dusk subfolder
|
- name: Move EBOOT.PBP to Dusk subfolder
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk
|
mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk
|
||||||
@@ -103,7 +103,7 @@ jobs:
|
|||||||
libgles2:arm64 \
|
libgles2:arm64 \
|
||||||
libgl1-mesa-dev:arm64
|
libgl1-mesa-dev:arm64
|
||||||
- name: Build Knulli
|
- name: Build Knulli
|
||||||
run: ./scripts/build-knulli.sh
|
run: ./scripts/knulli/build-knulli.sh
|
||||||
- name: Move output to Dusk subfolder
|
- name: Move output to Dusk subfolder
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk
|
mkdir -p ./git-artifcats/Dusk
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
||||||
- name: Build GameCube
|
- name: Build GameCube
|
||||||
run: ./scripts/build-gamecube.sh
|
run: ./scripts/gamecube/build-gamecube.sh
|
||||||
- name: Copy output files
|
- name: Copy output files
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk
|
mkdir -p ./git-artifcats/Dusk
|
||||||
@@ -166,7 +166,7 @@ jobs:
|
|||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
||||||
- name: Build GameCube ISO
|
- name: Build GameCube ISO
|
||||||
run: ./scripts/build-gamecube-iso.sh
|
run: ./scripts/gamecube/build-gamecube-iso.sh
|
||||||
- name: Copy output files
|
- name: Copy output files
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk
|
mkdir -p ./git-artifcats/Dusk
|
||||||
@@ -199,7 +199,7 @@ jobs:
|
|||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
||||||
- name: Build Wii
|
- name: Build Wii
|
||||||
run: ./scripts/build-wii.sh
|
run: ./scripts/wii/build-wii.sh
|
||||||
- name: Copy output files
|
- name: Copy output files
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk/apps/Dusk
|
mkdir -p ./git-artifcats/Dusk/apps/Dusk
|
||||||
@@ -232,7 +232,7 @@ jobs:
|
|||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
||||||
- name: Build Wii ISO
|
- name: Build Wii ISO
|
||||||
run: ./scripts/build-wii-iso.sh
|
run: ./scripts/wii/build-wii-iso.sh
|
||||||
- name: Copy output files
|
- name: Copy output files
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ./git-artifcats/Dusk
|
mkdir -p ./git-artifcats/Dusk
|
||||||
|
|||||||
@@ -32,75 +32,4 @@ jobs:
|
|||||||
git \
|
git \
|
||||||
libssl-dev
|
libssl-dev
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: ./scripts/test-linux.sh
|
run: ./scripts/linux/test-linux.sh
|
||||||
|
|
||||||
# Emulator smoke tests: boot the built disc/EBOOT for a fixed window and
|
|
||||||
# confirm the emulator doesn't crash. Not yet verified against a real
|
|
||||||
# runner (no CI run has exercised these) -- continue-on-error so a
|
|
||||||
# flaky/broken emulator step doesn't block the required Linux test job.
|
|
||||||
run-tests-gamecube-dolphin:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
container:
|
|
||||||
image: ghcr.io/extremscorner/libogc2:latest
|
|
||||||
steps:
|
|
||||||
- name: Install Node.js
|
|
||||||
run: apt-get update && apt-get install -y nodejs
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install additional dependencies
|
|
||||||
run: |
|
|
||||||
apt-get install -y \
|
|
||||||
python3-pip python3-polib python3-pil \
|
|
||||||
python3-dotenv python3-pyqt5 python3-opengl xorriso \
|
|
||||||
dolphin-emu xvfb
|
|
||||||
dkp-pacman -Syu --noconfirm
|
|
||||||
dkp-pacman -S --needed --noconfirm \
|
|
||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
|
||||||
- name: Build GameCube ISO and boot it in Dolphin
|
|
||||||
run: ./scripts/test-gamecube-dolphin.sh
|
|
||||||
|
|
||||||
run-tests-wii-dolphin:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
container:
|
|
||||||
image: ghcr.io/extremscorner/libogc2:latest
|
|
||||||
steps:
|
|
||||||
- name: Install Node.js
|
|
||||||
run: apt-get update && apt-get install -y nodejs
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Install additional dependencies
|
|
||||||
run: |
|
|
||||||
apt-get install -y \
|
|
||||||
python3-pip python3-polib python3-pil \
|
|
||||||
python3-dotenv python3-pyqt5 python3-opengl xorriso \
|
|
||||||
dolphin-emu xvfb
|
|
||||||
dkp-pacman -Syu --noconfirm
|
|
||||||
dkp-pacman -S --needed --noconfirm \
|
|
||||||
gamecube-sdl2 ppc-liblzma ppc-libzip \
|
|
||||||
gamecube-tools ppc-libmad ppc-zlib-ng ppc-bzip2 ppc-zstd
|
|
||||||
- name: Build Wii ISO and boot it in Dolphin
|
|
||||||
run: ./scripts/test-wii-dolphin.sh
|
|
||||||
|
|
||||||
run-tests-psp-ppsspp:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup pspdev
|
|
||||||
uses: ./.github/actions/setup-pspdev
|
|
||||||
- name: Install PPSSPPHeadless build dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y git cmake ninja-build libsdl2-dev zlib1g-dev
|
|
||||||
- name: Build PPSSPPHeadless
|
|
||||||
run: |
|
|
||||||
git clone --recursive --depth 1 https://github.com/hrydgard/ppsspp.git /tmp/ppsspp
|
|
||||||
cmake -S /tmp/ppsspp -B /tmp/ppsspp/build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build /tmp/ppsspp/build --target PPSSPPHeadless -- -j$(nproc)
|
|
||||||
echo "PPSSPP_HEADLESS_BIN=/tmp/ppsspp/build/PPSSPPHeadless" >> "$GITHUB_ENV"
|
|
||||||
- name: Build PSP EBOOT and boot it in PPSSPPHeadless
|
|
||||||
run: ./scripts/test-psp-ppsspp.sh
|
|
||||||
+2
-1
@@ -106,4 +106,5 @@ yarn.lock
|
|||||||
/build*
|
/build*
|
||||||
/assets/test
|
/assets/test
|
||||||
/tools_old
|
/tools_old
|
||||||
/assets/test.png
|
/assets/test.png
|
||||||
|
/assets/audio/boa.mp3
|
||||||
+36
-22
@@ -8,17 +8,22 @@ cmake_minimum_required(VERSION 3.13)
|
|||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
set(CMAKE_MODULE_PATH
|
||||||
|
${CMAKE_MODULE_PATH}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules
|
||||||
|
)
|
||||||
cmake_policy(SET CMP0079 NEW)
|
cmake_policy(SET CMP0079 NEW)
|
||||||
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
# set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||||
|
|
||||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||||
option(DUSK_NETWORKING "Enable networking support" OFF)
|
option(DUSK_NETWORK "Enable network support" ON)
|
||||||
|
|
||||||
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
|
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
|
||||||
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
|
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
|
||||||
set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description")
|
set(DUSK_GAME_SHORT_DESCRIPTION "Dusk game" CACHE STRING "One-line description")
|
||||||
set(DUSK_GAME_LONG_DESCRIPTION "No description yet." CACHE STRING "Full description")
|
set(DUSK_GAME_LONG_DESCRIPTION
|
||||||
|
"No description yet." CACHE STRING "Full description"
|
||||||
|
)
|
||||||
|
|
||||||
# Prep cache
|
# Prep cache
|
||||||
set(DUSK_CACHE_TARGET "dusk-target")
|
set(DUSK_CACHE_TARGET "dusk-target")
|
||||||
@@ -32,13 +37,19 @@ set(DUSK_TEMP_DIR "${DUSK_BUILD_DIR}/temp")
|
|||||||
set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools")
|
set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools")
|
||||||
set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data")
|
set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data")
|
||||||
set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
|
set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
|
||||||
set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets"
|
||||||
|
CACHE INTERNAL ${DUSK_CACHE_TARGET}
|
||||||
|
)
|
||||||
set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated")
|
set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated")
|
||||||
set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk
|
||||||
|
CACHE INTERNAL ${DUSK_CACHE_TARGET}
|
||||||
|
)
|
||||||
set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||||
set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||||
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||||
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk"
|
||||||
|
CACHE INTERNAL ${DUSK_CACHE_TARGET}
|
||||||
|
)
|
||||||
|
|
||||||
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
||||||
set(DUSK_TARGET_SYSTEM "linux")
|
set(DUSK_TARGET_SYSTEM "linux")
|
||||||
@@ -73,7 +84,9 @@ if(DUSK_BUILD_TESTS)
|
|||||||
${DUSK_LIBRARY_TARGET_NAME}
|
${DUSK_LIBRARY_TARGET_NAME}
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}"
|
||||||
|
CACHE INTERNAL ${DUSK_CACHE_TARGET}
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT DEFINED DUSK_VERSION)
|
if(NOT DEFINED DUSK_VERSION)
|
||||||
@@ -91,9 +104,9 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
DUSK_VERSION="${DUSK_VERSION}"
|
DUSK_VERSION="${DUSK_VERSION}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(DUSK_NETWORKING)
|
if(DUSK_NETWORK)
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
DUSK_NETWORKING
|
DUSK_NETWORK
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -122,20 +135,21 @@ if(DUSK_BUILD_TESTS)
|
|||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Build assets
|
# Build assets. Runs unconditionally on every build (rather than being
|
||||||
# Deliberately not CONFIGURE_DEPENDS: that reruns the full CMake configure
|
# gated behind a custom_command's file-level DEPENDS list) because
|
||||||
# step (which invalidates generated headers and forces a huge rebuild) on
|
# CONFIGURE_DEPENDS globs only pick up added/removed files on the next
|
||||||
# every single asset edit. Re-run cmake manually when assets are added or
|
# CMake configure, so that approach could miss/lag a rebuild behind an
|
||||||
# removed.
|
# asset edit. tools.asset.pack itself decides whether anything actually
|
||||||
file(GLOB_RECURSE DUSK_ASSET_FILES "${DUSK_ASSETS_DIR}/*")
|
# changed (see its manifest check) and no-ops quickly when nothing did,
|
||||||
add_custom_command(
|
# so this stays cheap on a build where assets are untouched.
|
||||||
OUTPUT "${DUSK_ASSETS_ZIP}"
|
add_custom_target(DUSK_ASSETS_BUILT ALL
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_ASSETS_DIR}"
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_ASSETS_DIR}"
|
||||||
COMMAND ${CMAKE_COMMAND} -E rm -f "${DUSK_ASSETS_ZIP}"
|
COMMAND ${Python3_EXECUTABLE} -m tools.asset.pack
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar "cf" "${DUSK_ASSETS_ZIP}" --format=zip -- .
|
--input "${DUSK_ASSETS_DIR}"
|
||||||
WORKING_DIRECTORY "${DUSK_ASSETS_DIR}"
|
--output "${DUSK_ASSETS_ZIP}"
|
||||||
DEPENDS ${DUSK_ASSET_FILES}
|
WORKING_DIRECTORY "${DUSK_ROOT_DIR}"
|
||||||
|
BYPRODUCTS "${DUSK_ASSETS_ZIP}"
|
||||||
|
COMMENT "Packing assets into dusk.dsk"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
add_custom_target(DUSK_ASSETS_BUILT DEPENDS "${DUSK_ASSETS_ZIP}")
|
|
||||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)
|
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)
|
||||||
@@ -9,15 +9,18 @@ following packages, depending on your system;
|
|||||||
|
|
||||||
Fedora;
|
Fedora;
|
||||||
```
|
```
|
||||||
sudo dnf install git make gcc cmake python python-polib python3-pillow python3-dotenv python3-numpy python-qt5 python3-pyopengl SDL2-devel zlib-devel libzip-devel bzip2-devel openssl-devel lzma-sdk-devel xz xz-devel lua-devel
|
sudo dnf install git make gcc cmake python python-polib python3-pillow python3-dotenv python3-numpy python-qt5 python3-pyopengl SDL2-devel zlib-devel libzip-devel bzip2-devel openssl-devel lzma-sdk-devel xz xz-devel lua-devel libmad-devel
|
||||||
```
|
```
|
||||||
|
`libmad-devel` (used for MP3 decoding) is GPL-licensed and ships in the
|
||||||
|
[RPM Fusion](https://rpmfusion.org/) free repository rather than Fedora's
|
||||||
|
base repos - make sure RPM Fusion is enabled first.
|
||||||
|
|
||||||
Ubuntu;
|
Ubuntu;
|
||||||
```
|
```
|
||||||
sudo apt-get install git build-essential gcc python python-polib python3-pillow python3-dotenv python3-numpy python3-pyqt5 python3-opengl
|
sudo apt-get install git build-essential gcc python python-polib python3-pillow python3-dotenv python3-numpy python3-pyqt5 python3-opengl libmad0-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Arch Linux;
|
Arch Linux;
|
||||||
```
|
```
|
||||||
sudo pacman -S git base-devel gcc python python-polib python-pillow python-dotenv python-numpy python-pyqt5 python-opengl
|
sudo pacman -S git base-devel gcc python python-polib python-pillow python-dotenv python-numpy python-pyqt5 python-opengl libmad
|
||||||
```
|
```
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
add_subdirectory(debug)
|
|
||||||
add_subdirectory(frame)
|
|
||||||
add_subdirectory(focus)
|
|
||||||
add_subdirectory(overlay)
|
|
||||||
add_subdirectory(transition)
|
|
||||||
add_subdirectory(widget)
|
|
||||||
|
|
||||||
# Sources
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
ui.c
|
|
||||||
uielement.c
|
|
||||||
)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uiconsole.c
|
|
||||||
uifps.c
|
|
||||||
)
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiconsole.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "console/console.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "display/mesh/mesh.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
mesh_t mesh;
|
|
||||||
bool_t built;
|
|
||||||
meshvertex_t vertices[UI_CONSOLE_CACHE_VERTEX_MAX];
|
|
||||||
int32_t vertexCount;
|
|
||||||
int32_t cachedScanX;
|
|
||||||
int32_t cachedScanY;
|
|
||||||
} uiconsolecache_t;
|
|
||||||
|
|
||||||
uiconsolecache_t UI_CONSOLE_CACHE;
|
|
||||||
|
|
||||||
errorret_t uiConsoleDraw(void) {
|
|
||||||
if(!CONSOLE.visible) errorOk();
|
|
||||||
|
|
||||||
if(
|
|
||||||
CONSOLE.dirty ||
|
|
||||||
UI_CONSOLE_CACHE.cachedScanX != SCREEN.scanX ||
|
|
||||||
UI_CONSOLE_CACHE.cachedScanY != SCREEN.scanY
|
|
||||||
) {
|
|
||||||
errorChain(uiConsoleRebuild());
|
|
||||||
}
|
|
||||||
|
|
||||||
if(UI_CONSOLE_CACHE.vertexCount == 0) errorOk();
|
|
||||||
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_RED,
|
|
||||||
.texture = FONT_DEFAULT.texture
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
errorChain(shaderBind(&SHADER_UNLIT));
|
|
||||||
errorChain(shaderSetMaterial(&SHADER_UNLIT, &material));
|
|
||||||
return meshDraw(&UI_CONSOLE_CACHE.mesh, 0, UI_CONSOLE_CACHE.vertexCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiConsoleRebuild(void) {
|
|
||||||
int32_t spriteCount = 0;
|
|
||||||
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
|
||||||
char_t c;
|
|
||||||
int32_t j = 0;
|
|
||||||
while((c = CONSOLE.line[i][j++]) != '\0') {
|
|
||||||
if(c != ' ' && c != '\n') spriteCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
spriteCount <= UI_CONSOLE_CACHE_GLYPH_MAX,
|
|
||||||
"Console history exceeds fixed cache capacity"
|
|
||||||
);
|
|
||||||
|
|
||||||
const int32_t vertexCount = spriteCount * QUAD_VERTEX_COUNT;
|
|
||||||
UI_CONSOLE_CACHE.vertexCount = vertexCount;
|
|
||||||
|
|
||||||
if(vertexCount > 0) {
|
|
||||||
const float_t lineH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
const float_t charW = (float_t)FONT_DEFAULT.tileset->tileWidth;
|
|
||||||
|
|
||||||
int32_t index = 0;
|
|
||||||
int32_t row = 0;
|
|
||||||
for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) {
|
|
||||||
float_t posX = (float_t)SCREEN.scanX;
|
|
||||||
float_t posY = (float_t)SCREEN.scanY + lineH * (float_t)row;
|
|
||||||
|
|
||||||
char_t c;
|
|
||||||
int32_t j = 0;
|
|
||||||
while((c = CONSOLE.line[i][j++]) != '\0') {
|
|
||||||
if(c == '\n') {
|
|
||||||
posX = (float_t)SCREEN.scanX;
|
|
||||||
posY += lineH;
|
|
||||||
row++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(c == ' ') {
|
|
||||||
posX += charW;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const spritebatchsprite_t sprite = textGetSprite(
|
|
||||||
(vec2){ posX, posY }, c, &FONT_DEFAULT
|
|
||||||
);
|
|
||||||
spriteBatchBufferToMesh(
|
|
||||||
&sprite, 1,
|
|
||||||
&UI_CONSOLE_CACHE.vertices[index], QUAD_VERTEX_COUNT
|
|
||||||
);
|
|
||||||
|
|
||||||
index += QUAD_VERTEX_COUNT;
|
|
||||||
posX += charW;
|
|
||||||
}
|
|
||||||
|
|
||||||
row++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!UI_CONSOLE_CACHE.built) {
|
|
||||||
errorChain(meshInit(
|
|
||||||
&UI_CONSOLE_CACHE.mesh,
|
|
||||||
QUAD_PRIMITIVE_TYPE,
|
|
||||||
UI_CONSOLE_CACHE_VERTEX_MAX,
|
|
||||||
UI_CONSOLE_CACHE.vertices
|
|
||||||
));
|
|
||||||
UI_CONSOLE_CACHE.built = true;
|
|
||||||
} else {
|
|
||||||
errorChain(meshFlush(&UI_CONSOLE_CACHE.mesh, 0, vertexCount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UI_CONSOLE_CACHE.cachedScanX = SCREEN.scanX;
|
|
||||||
UI_CONSOLE_CACHE.cachedScanY = SCREEN.scanY;
|
|
||||||
CONSOLE.dirty = false;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiConsoleDispose(void) {
|
|
||||||
if(UI_CONSOLE_CACHE.built) {
|
|
||||||
errorChain(meshDispose(&UI_CONSOLE_CACHE.mesh));
|
|
||||||
}
|
|
||||||
|
|
||||||
memoryZero(&UI_CONSOLE_CACHE, sizeof(uiconsolecache_t));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/mesh/quad.h"
|
|
||||||
|
|
||||||
// Fixed capacity for the console's cached glyph mesh, in glyphs -- the
|
|
||||||
// mesh/vertex buffer is sized to this once and never grown/shrunk, so
|
|
||||||
// history producing more non-space characters than this asserts (see
|
|
||||||
// uiConsoleRebuild) instead of reallocating.
|
|
||||||
#define UI_CONSOLE_CACHE_GLYPH_MAX 512
|
|
||||||
#define UI_CONSOLE_CACHE_VERTEX_MAX (\
|
|
||||||
UI_CONSOLE_CACHE_GLYPH_MAX * QUAD_VERTEX_COUNT \
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the console history into the scan-safe area, drawing a mesh
|
|
||||||
* that is only rebuilt when the history changes or the scan-safe origin
|
|
||||||
* moves, rather than rebuffering vertices every frame. No-ops when the
|
|
||||||
* console is not visible.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConsoleDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the cached mesh for the console's current history and
|
|
||||||
* scan-safe origin. Called automatically by uiConsoleDraw() whenever
|
|
||||||
* needed; only needs calling directly to force an immediate rebuild.
|
|
||||||
* The underlying vertex buffer is a fixed-size array (see
|
|
||||||
* UI_CONSOLE_CACHE_VERTEX_MAX) -- asserts if the history produces more
|
|
||||||
* glyphs than that capacity, rather than growing it.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConsoleRebuild(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees the mesh and vertex buffer built up by uiConsoleDraw().
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConsoleDispose(void);
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uifps.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "engine/engine.h"
|
|
||||||
|
|
||||||
uifps_t UIFPS;
|
|
||||||
|
|
||||||
errorret_t uiFPSInit() {
|
|
||||||
uiLabelInit(&UIFPS.fpsLabel, &FONT_DEFAULT);
|
|
||||||
uiLabelInit(&UIFPS.versionLabel, &FONT_DEFAULT);
|
|
||||||
uiLabelSetText(&UIFPS.versionLabel, ENGINE.version);
|
|
||||||
uiLabelSetColor(&UIFPS.versionLabel, color(255, 255, 255, 128));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFPSDraw() {
|
|
||||||
char_t fpsText[32];
|
|
||||||
|
|
||||||
// Get now.
|
|
||||||
dusktimeepoch_t now = timeGetEpoch();
|
|
||||||
double_t delta = now.time - UIFPS.lastTick.time;
|
|
||||||
UIFPS.lastTick = now;
|
|
||||||
|
|
||||||
// Raw current FPS
|
|
||||||
float_t fps = delta > 0 ? 1.0 / delta : 0.0;
|
|
||||||
|
|
||||||
// Average FPS using exponential moving average
|
|
||||||
const float_t alpha = 0.1f; // Smoothing factor
|
|
||||||
if(UIFPS.fpsAverage == 0.0f) {
|
|
||||||
UIFPS.fpsAverage = fps; // Initialize average on first run
|
|
||||||
} else {
|
|
||||||
UIFPS.fpsAverage = alpha * fps + (1.0f - alpha) * UIFPS.fpsAverage;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(
|
|
||||||
fpsText,
|
|
||||||
sizeof(fpsText),
|
|
||||||
"%.1f/%.1fms",
|
|
||||||
UIFPS.fpsAverage,
|
|
||||||
delta * 1000.0f
|
|
||||||
);
|
|
||||||
|
|
||||||
color_t textColor;
|
|
||||||
if(fps >= 55.0f) {
|
|
||||||
textColor = COLOR_GREEN;
|
|
||||||
} else if(fps >= 45.0f) {
|
|
||||||
textColor = COLOR_YELLOW;
|
|
||||||
} else {
|
|
||||||
textColor = COLOR_RED;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiLabelSetColor(&UIFPS.fpsLabel, textColor);
|
|
||||||
if(stringCompare(fpsText, UIFPS.fpsLabel.text) != 0) {
|
|
||||||
uiLabelSetText(&UIFPS.fpsLabel, fpsText);
|
|
||||||
}
|
|
||||||
errorChain(uiLabelDraw(
|
|
||||||
&UIFPS.fpsLabel, (float_t)SCREEN.scanX, (float_t)SCREEN.scanY
|
|
||||||
));
|
|
||||||
|
|
||||||
int32_t versionWidth, versionHeight;
|
|
||||||
uiLabelGetSize(&UIFPS.versionLabel, &versionWidth, &versionHeight);
|
|
||||||
errorChain(uiLabelDraw(
|
|
||||||
&UIFPS.versionLabel,
|
|
||||||
(float_t)(SCREEN.scanX + SCREEN.scanWidth - versionWidth),
|
|
||||||
(float_t)(SCREEN.scanY + SCREEN.scanHeight - versionHeight)
|
|
||||||
));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "time/timeepoch.h"
|
|
||||||
#include "ui/widget/uilabel.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
dusktimeepoch_t lastTick;
|
|
||||||
float_t fpsAverage;
|
|
||||||
uilabel_t fpsLabel;
|
|
||||||
uilabel_t versionLabel;
|
|
||||||
} uifps_t;
|
|
||||||
|
|
||||||
extern uifps_t UIFPS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the FPS counter, caching the (static) version label.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFPSInit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the FPS counter on the screen, and also does the update (for now).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFPSDraw();
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uifocus.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "input/input.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
|
|
||||||
const uifocusdirmap_t UI_FOCUS_DIR_MAP[] = {
|
|
||||||
{ INPUT_BIND_UP, UI_FOCUS_DIRECTION_UP, 0, -1 },
|
|
||||||
{ INPUT_BIND_DOWN, UI_FOCUS_DIRECTION_DOWN, 0, 1 },
|
|
||||||
{ INPUT_BIND_LEFT, UI_FOCUS_DIRECTION_LEFT, -1, 0 },
|
|
||||||
{ INPUT_BIND_RIGHT, UI_FOCUS_DIRECTION_RIGHT, 1, 0 },
|
|
||||||
{ INPUT_BIND_NULL, UI_FOCUS_DIRECTION_NONE, 0, 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
uifocus_t UI_FOCUS;
|
|
||||||
|
|
||||||
void uiFocusInit(void) {
|
|
||||||
memoryZero(&UI_FOCUS, sizeof(uifocus_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
uifocusitem_t * uiFocusPush(
|
|
||||||
const uint8_t cols,
|
|
||||||
const uint8_t rows,
|
|
||||||
uifocusitemcallback_t selected,
|
|
||||||
uifocusitemcallback_t changed,
|
|
||||||
uifocusitemcallback_t closed,
|
|
||||||
uifocusitemdirectioncallback_t direction,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
assertTrue(
|
|
||||||
UI_FOCUS.count < UI_FOCUS_STACK_MAX,
|
|
||||||
"UI focus stack overflow"
|
|
||||||
);
|
|
||||||
assertTrue(cols > 0, "Focus item cols must be > 0");
|
|
||||||
assertTrue(rows > 0, "Focus item rows must be > 0");
|
|
||||||
|
|
||||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count];
|
|
||||||
memoryZero(item, sizeof(uifocusitem_t));
|
|
||||||
item->cols = cols;
|
|
||||||
item->rows = rows;
|
|
||||||
item->selected = selected;
|
|
||||||
item->changed = changed;
|
|
||||||
item->closed = closed;
|
|
||||||
item->direction = direction;
|
|
||||||
item->user = user;
|
|
||||||
UI_FOCUS.count++;
|
|
||||||
UI_FOCUS.pushedThisTick = true;
|
|
||||||
if(item->changed != NULL) item->changed(item);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusPop(void) {
|
|
||||||
assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow");
|
|
||||||
|
|
||||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
|
|
||||||
if(item->closed != NULL) item->closed(item);
|
|
||||||
UI_FOCUS.count--;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusPopItem(uifocusitem_t *item) {
|
|
||||||
assertTrue(UI_FOCUS.count > 0, "UI focus stack underflow");
|
|
||||||
assertTrue(item >= UI_FOCUS.items, "Item is not on the focus stack");
|
|
||||||
assertTrue(
|
|
||||||
item < UI_FOCUS.items + UI_FOCUS.count,
|
|
||||||
"Item is not on the focus stack"
|
|
||||||
);
|
|
||||||
|
|
||||||
while(&UI_FOCUS.items[UI_FOCUS.count - 1] != item) {
|
|
||||||
uiFocusPop();
|
|
||||||
}
|
|
||||||
uiFocusPop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y) {
|
|
||||||
assertTrue(UI_FOCUS.count > 0, "No active focus item");
|
|
||||||
assertTrue(item->cols > 0, "Focus item cols must be > 0");
|
|
||||||
assertTrue(item->rows > 0, "Focus item rows must be > 0");
|
|
||||||
|
|
||||||
uint8_t newX = x % item->cols;
|
|
||||||
uint8_t newY = y % item->rows;
|
|
||||||
|
|
||||||
if(item->changed != NULL) {
|
|
||||||
uint8_t oldX = item->x;
|
|
||||||
uint8_t oldY = item->y;
|
|
||||||
item->x = newX;
|
|
||||||
item->y = newY;
|
|
||||||
if(!item->changed(item)) {
|
|
||||||
item->x = oldX;
|
|
||||||
item->y = oldY;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item->x = newX;
|
|
||||||
item->y = newY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusMoveDirection(
|
|
||||||
uifocusitem_t *item,
|
|
||||||
const uifocusdirection_t dir
|
|
||||||
) {
|
|
||||||
for(
|
|
||||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
|
||||||
m->action != INPUT_BIND_NULL;
|
|
||||||
m++
|
|
||||||
) {
|
|
||||||
if(m->direction != dir) continue;
|
|
||||||
|
|
||||||
uint8_t x = (uint8_t)(item->x + m->dx);
|
|
||||||
uint8_t y = (uint8_t)(item->y + m->dy);
|
|
||||||
uiFocusSetPosition(item, x, y);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusHandleDirection(
|
|
||||||
uifocusitem_t *item,
|
|
||||||
const uifocusdirection_t dir
|
|
||||||
) {
|
|
||||||
if(item->direction != NULL && item->direction(item, dir)) return;
|
|
||||||
uiFocusMoveDirection(item, dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFocusUpdate(void) {
|
|
||||||
if(UI_FOCUS.count == 0) return;
|
|
||||||
|
|
||||||
#ifdef DUSK_TIME_DYNAMIC
|
|
||||||
if(TIME.dynamicUpdate) return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(UI_FOCUS.pushedThisTick) {
|
|
||||||
UI_FOCUS.pushedThisTick = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uifocusitem_t *item = &UI_FOCUS.items[UI_FOCUS.count - 1];
|
|
||||||
|
|
||||||
if(inputPressed(INPUT_BIND_ACCEPT)) {
|
|
||||||
if(item->selected != NULL) item->selected(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(inputPressed(INPUT_BIND_CANCEL)) {
|
|
||||||
uiFocusPop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(
|
|
||||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
|
||||||
m->action != INPUT_BIND_NULL;
|
|
||||||
m++
|
|
||||||
) {
|
|
||||||
if(!inputPressed(m->action)) continue;
|
|
||||||
UI_FOCUS.direction = m->direction;
|
|
||||||
UI_FOCUS.timeHeld = 0.0f;
|
|
||||||
uiFocusHandleDirection(item, m->direction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t held = false;
|
|
||||||
for(
|
|
||||||
const uifocusdirmap_t *m = UI_FOCUS_DIR_MAP;
|
|
||||||
m->action != INPUT_BIND_NULL;
|
|
||||||
m++
|
|
||||||
) {
|
|
||||||
if(m->direction != UI_FOCUS.direction) continue;
|
|
||||||
held = inputIsDown(m->action);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!held) {
|
|
||||||
UI_FOCUS.direction = UI_FOCUS_DIRECTION_NONE;
|
|
||||||
UI_FOCUS.timeHeld = 0.0f;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UI_FOCUS.timeHeld += TIME.delta;
|
|
||||||
|
|
||||||
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY) return;
|
|
||||||
if(UI_FOCUS.timeHeld < UI_FOCUS_HOLD_DELAY + UI_FOCUS_HOLD_REPEAT) return;
|
|
||||||
UI_FOCUS.timeHeld = UI_FOCUS_HOLD_DELAY;
|
|
||||||
uiFocusHandleDirection(item, UI_FOCUS.direction);
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "uifocusitem.h"
|
|
||||||
#include "input/inputaction.h"
|
|
||||||
|
|
||||||
/** Maximum depth of the focus stack. */
|
|
||||||
#define UI_FOCUS_STACK_MAX 8
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long a direction must be held before repeating begins, in seconds.
|
|
||||||
*/
|
|
||||||
#define UI_FOCUS_HOLD_DELAY 0.5f
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interval between repeated moves while a direction is held, in seconds.
|
|
||||||
*/
|
|
||||||
#define UI_FOCUS_HOLD_REPEAT 0.1f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
inputbind_t action;
|
|
||||||
uifocusdirection_t direction;
|
|
||||||
int8_t dx;
|
|
||||||
int8_t dy;
|
|
||||||
} uifocusdirmap_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapping of input actions to focus directions, terminated by an
|
|
||||||
* entry with action == INPUT_BIND_NULL.
|
|
||||||
*/
|
|
||||||
extern const uifocusdirmap_t UI_FOCUS_DIR_MAP[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A stack of focused UI items. Push an item when a widget captures
|
|
||||||
* focus; pop it when focus is released. The topmost item is always
|
|
||||||
* the active focus context.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
uifocusitem_t items[UI_FOCUS_STACK_MAX];
|
|
||||||
uint8_t count;
|
|
||||||
uifocusdirection_t direction;
|
|
||||||
float_t timeHeld;
|
|
||||||
bool_t pushedThisTick;
|
|
||||||
} uifocus_t;
|
|
||||||
|
|
||||||
extern uifocus_t UI_FOCUS;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the focus system, zeroing all state.
|
|
||||||
*/
|
|
||||||
void uiFocusInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a new focus item onto the stack with the given grid dimensions
|
|
||||||
* and callbacks. x and y are initialized to 0.
|
|
||||||
*
|
|
||||||
* @param cols Number of columns in the focus grid.
|
|
||||||
* @param rows Number of rows in the focus grid.
|
|
||||||
* @param selected Called when the user selects the focused cell.
|
|
||||||
* @param changed Called when the focused cell position changes.
|
|
||||||
* @param closed Called when this focus item is popped.
|
|
||||||
* @param direction Called on a direction press/hold before the default
|
|
||||||
* cell-to-cell movement is applied; may be NULL.
|
|
||||||
* @param user Arbitrary pointer stored on the item before changed fires.
|
|
||||||
* @returns Pointer to the newly pushed focus item.
|
|
||||||
*/
|
|
||||||
uifocusitem_t * uiFocusPush(
|
|
||||||
const uint8_t cols,
|
|
||||||
const uint8_t rows,
|
|
||||||
uifocusitemcallback_t selected,
|
|
||||||
uifocusitemcallback_t changed,
|
|
||||||
uifocusitemcallback_t closed,
|
|
||||||
uifocusitemdirectioncallback_t direction,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pops the topmost focus item from the stack, invoking its closed
|
|
||||||
* callback if one is set.
|
|
||||||
*/
|
|
||||||
void uiFocusPop(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pops an item and anything that was pushed after it from the focus stack.
|
|
||||||
*
|
|
||||||
* @param item The focus item to pop to. Must be on the stack.
|
|
||||||
*/
|
|
||||||
void uiFocusPopItem(uifocusitem_t *item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manually sets the cursor position of the topmost focus item and
|
|
||||||
* fires its changed callback.
|
|
||||||
*
|
|
||||||
* @param x Column to move to.
|
|
||||||
* @param y Row to move to.
|
|
||||||
*/
|
|
||||||
void uiFocusSetPosition(uifocusitem_t *item, const uint8_t x, const uint8_t y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the topmost focus item one step in the given direction,
|
|
||||||
* wrapping at the grid edges, and fires its changed callback.
|
|
||||||
*
|
|
||||||
* @param item The focus item to move.
|
|
||||||
* @param dir Direction to move.
|
|
||||||
*/
|
|
||||||
void uiFocusMoveDirection(
|
|
||||||
uifocusitem_t *item,
|
|
||||||
const uifocusdirection_t dir
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles a direction press/hold for the given item: gives the item's
|
|
||||||
* direction callback (if any) first refusal, falling back to the
|
|
||||||
* default cell-to-cell movement if it's unset or returns false.
|
|
||||||
*
|
|
||||||
* @param item The focus item to move.
|
|
||||||
* @param dir Direction that was pressed or held.
|
|
||||||
*/
|
|
||||||
void uiFocusHandleDirection(
|
|
||||||
uifocusitem_t *item,
|
|
||||||
const uifocusdirection_t dir
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the focus system. Handles first-press movement and
|
|
||||||
* held-direction repeating. Called once per game tick.
|
|
||||||
*/
|
|
||||||
void uiFocusUpdate(void);
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
|
|
||||||
typedef struct uifocusitem_s uifocusitem_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UI_FOCUS_DIRECTION_NONE,
|
|
||||||
UI_FOCUS_DIRECTION_UP,
|
|
||||||
UI_FOCUS_DIRECTION_DOWN,
|
|
||||||
UI_FOCUS_DIRECTION_LEFT,
|
|
||||||
UI_FOCUS_DIRECTION_RIGHT
|
|
||||||
} uifocusdirection_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked when a focus item's selected cell changes.
|
|
||||||
*
|
|
||||||
* @param item The focus item that changed.
|
|
||||||
*/
|
|
||||||
typedef bool_t (*uifocusitemcallback_t)(const uifocusitem_t *item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked when a direction is pressed or held-repeated while
|
|
||||||
* this item is focused, before the default cell-to-cell movement is
|
|
||||||
* applied.
|
|
||||||
*
|
|
||||||
* @param item The focus item that is currently focused.
|
|
||||||
* @param direction The direction that was pressed.
|
|
||||||
* @returns True if the direction was fully handled and the default
|
|
||||||
* grid movement should be skipped; false to fall through to it.
|
|
||||||
*/
|
|
||||||
typedef bool_t (*uifocusitemdirectioncallback_t)(
|
|
||||||
const uifocusitem_t *item,
|
|
||||||
const uifocusdirection_t direction
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A single entry on the UI focus stack. Tracks the focused cell
|
|
||||||
* within a grid of cols x rows, and the current x/y position.
|
|
||||||
*/
|
|
||||||
struct uifocusitem_s {
|
|
||||||
uint8_t cols;
|
|
||||||
uint8_t rows;
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uifocusitemcallback_t selected;
|
|
||||||
uifocusitemcallback_t changed;
|
|
||||||
uifocusitemcallback_t closed;
|
|
||||||
uifocusitemdirectioncallback_t direction;
|
|
||||||
void *user;
|
|
||||||
};
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uiframe.c
|
|
||||||
uiconfirm.c
|
|
||||||
)
|
|
||||||
|
|
||||||
add_subdirectory(settings)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uisettings.c
|
|
||||||
uisettingsgeneral.c
|
|
||||||
uisettingsinput.c
|
|
||||||
uisettingsdisplay.c
|
|
||||||
uisettingsaudio.c
|
|
||||||
)
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uisettings.h"
|
|
||||||
#include "uisettingsgeneral.h"
|
|
||||||
#include "uisettingsinput.h"
|
|
||||||
#include "uisettingsdisplay.h"
|
|
||||||
#include "uisettingsaudio.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
#include "ui/frame/uiconfirm.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "locale/localemanager.h"
|
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
|
||||||
|
|
||||||
uisettings_t UI_SETTINGS;
|
|
||||||
|
|
||||||
uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT] = {
|
|
||||||
{
|
|
||||||
.localeId = "ui.settings.tabs.general",
|
|
||||||
.init = uiSettingsGeneralInit,
|
|
||||||
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
|
||||||
.load = uiSettingsGeneralLoad,
|
|
||||||
.apply = uiSettingsGeneralApply,
|
|
||||||
.hasChanges = uiSettingsGeneralHasChanges
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
.localeId = "ui.settings.tabs.input",
|
|
||||||
.init = uiSettingsInputInit,
|
|
||||||
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
|
||||||
.load = uiSettingsInputLoad,
|
|
||||||
.apply = uiSettingsInputApply,
|
|
||||||
.hasChanges = uiSettingsInputHasChanges
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
.localeId = "ui.settings.tabs.display",
|
|
||||||
.init = uiSettingsDisplayInit,
|
|
||||||
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
|
||||||
.load = uiSettingsDisplayLoad,
|
|
||||||
.apply = uiSettingsDisplayApply,
|
|
||||||
.hasChanges = uiSettingsDisplayHasChanges
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
.localeId = "ui.settings.tabs.audio",
|
|
||||||
.init = uiSettingsAudioInit,
|
|
||||||
.menu = (uimenu_t *)&UI_SETTINGS.data,
|
|
||||||
.load = uiSettingsAudioLoad,
|
|
||||||
.apply = uiSettingsAudioApply,
|
|
||||||
.hasChanges = uiSettingsAudioHasChanges
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static uisettingstabdef_t UI_SETTINGS_TOP_DEF = {
|
|
||||||
.menu = &UI_SETTINGS.tabsMenu,
|
|
||||||
.load = uiSettingsLoad,
|
|
||||||
.hasChanges = uiSettingsHasChanges
|
|
||||||
};
|
|
||||||
|
|
||||||
static uisettingstabdef_t *UI_SETTINGS_PENDING_DISCARD = NULL;
|
|
||||||
static uisettingstabdef_t *UI_SETTINGS_ACTIVE_TAB = NULL;
|
|
||||||
|
|
||||||
errorret_t uiSettingsActivateTab(uisettingstabdef_t *def) {
|
|
||||||
if(def == UI_SETTINGS_ACTIVE_TAB) errorOk();
|
|
||||||
|
|
||||||
errorChain(def->init(&UI_SETTINGS.data));
|
|
||||||
def->menu->user = def;
|
|
||||||
def->load();
|
|
||||||
UI_SETTINGS_ACTIVE_TAB = def;
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsTabChanged(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
if(index >= UI_SETTINGS_TAB_COUNT) return;
|
|
||||||
errorCatch(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsTabSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
if(index >= UI_SETTINGS_TAB_COUNT) return;
|
|
||||||
|
|
||||||
uisettingstabdef_t *def = &UI_SETTINGS_TAB_DEFS[index];
|
|
||||||
errorCatch(uiSettingsActivateTab(def));
|
|
||||||
uiMenuOpen(def->menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsInit(void) {
|
|
||||||
memoryZero(&UI_SETTINGS, sizeof(uisettings_t));
|
|
||||||
UI_SETTINGS_ACTIVE_TAB = NULL;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) {
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
UI_SETTINGS_TAB_DEFS[i].localeId,
|
|
||||||
0,
|
|
||||||
UI_SETTINGS.tabLabels[i],
|
|
||||||
UI_SETTINGS_TAB_LABEL_MAX
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&UI_SETTINGS.tabsMenu, UI_SETTINGS.tabs, uiSettingsTabSelected,
|
|
||||||
uiSettingsDiscardMenuClosed, uiSettingsTabChanged
|
|
||||||
);
|
|
||||||
for(uint8_t i = 0; i < UI_SETTINGS_TAB_COUNT; i++) {
|
|
||||||
MENU_TAB(UI_SETTINGS.tabLabels[i]);
|
|
||||||
}
|
|
||||||
MENU_END(UI_SETTINGS.tabs, menuIndex);
|
|
||||||
|
|
||||||
UI_SETTINGS.tabsMenu.user = &UI_SETTINGS_TOP_DEF;
|
|
||||||
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
"ui.settings.apply",
|
|
||||||
0,
|
|
||||||
UI_SETTINGS.applyLabel,
|
|
||||||
UI_SETTINGS_APPLY_LABEL_MAX
|
|
||||||
));
|
|
||||||
|
|
||||||
errorChain(uiSettingsActivateTab(&UI_SETTINGS_TAB_DEFS[0]));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsDraw(void) {
|
|
||||||
if(!uiMenuIsActive(&UI_SETTINGS.tabsMenu)) errorOk();
|
|
||||||
|
|
||||||
const float_t width = SCREEN.width;
|
|
||||||
const float_t height = SCREEN.height;
|
|
||||||
const float_t x = (float_t)SCREEN.scanX +
|
|
||||||
((float_t)SCREEN.scanWidth - width) * 0.5f;
|
|
||||||
const float_t y = (float_t)SCREEN.scanY +
|
|
||||||
((float_t)SCREEN.scanHeight - height) * 0.5f;
|
|
||||||
|
|
||||||
errorChain(uiFrameDrawCached(&UI_SETTINGS.frameCache, x, y, width, height));
|
|
||||||
|
|
||||||
const float_t contentX = x + UI_FRAME_START_X;
|
|
||||||
const float_t contentY = y + UI_FRAME_START_Y;
|
|
||||||
const float_t contentWidth = width - (UI_FRAME_START_X * 2);
|
|
||||||
const float_t contentHeight = height - (UI_FRAME_START_Y * 2);
|
|
||||||
|
|
||||||
errorChain(uiMenuDraw(
|
|
||||||
&UI_SETTINGS.tabsMenu,
|
|
||||||
contentX,
|
|
||||||
contentY,
|
|
||||||
contentWidth,
|
|
||||||
contentHeight
|
|
||||||
));
|
|
||||||
|
|
||||||
const float_t tabsRowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
const float_t pageY = contentY + tabsRowHeight + UI_FRAME_PADDING_Y;
|
|
||||||
const float_t pageHeight = contentHeight - tabsRowHeight - UI_FRAME_PADDING_Y;
|
|
||||||
|
|
||||||
if(UI_SETTINGS_ACTIVE_TAB != NULL) {
|
|
||||||
errorChain(uiMenuDraw(
|
|
||||||
UI_SETTINGS_ACTIVE_TAB->menu, contentX, pageY, contentWidth, pageHeight
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsUpdate(void) {
|
|
||||||
if(!UI_SETTINGS_PENDING_DISCARD) errorOk();
|
|
||||||
|
|
||||||
uisettingstabdef_t *def = UI_SETTINGS_PENDING_DISCARD;
|
|
||||||
UI_SETTINGS_PENDING_DISCARD = NULL;
|
|
||||||
|
|
||||||
return uiSettingsDiscardUpdate(def);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsIsOpen(void) {
|
|
||||||
return uiMenuIsActive(&UI_SETTINGS.tabsMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsOpen() {
|
|
||||||
uiSettingsLoad();
|
|
||||||
uiMenuOpen(&UI_SETTINGS.tabsMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsClose() {
|
|
||||||
uiMenuClose(&UI_SETTINGS.tabsMenu);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsDispose(void) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsLoad(void) {
|
|
||||||
if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsApply(void) {
|
|
||||||
if(UI_SETTINGS_ACTIVE_TAB != NULL) UI_SETTINGS_ACTIVE_TAB->apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsHasChanges(void) {
|
|
||||||
return UI_SETTINGS_ACTIVE_TAB != NULL && UI_SETTINGS_ACTIVE_TAB->hasChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsDiscardMenuClosed(const uimenu_t *menu) {
|
|
||||||
uisettingstabdef_t *def = (uisettingstabdef_t *)menu->user;
|
|
||||||
if(def->hasChanges()) UI_SETTINGS_PENDING_DISCARD = def;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def) {
|
|
||||||
#define UI_SETTINGS_DISCARD_TEXT_MAX 128
|
|
||||||
char_t text[UI_SETTINGS_DISCARD_TEXT_MAX];
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
"ui.confirm.discard_changes",
|
|
||||||
0,
|
|
||||||
text,
|
|
||||||
UI_SETTINGS_DISCARD_TEXT_MAX
|
|
||||||
));
|
|
||||||
#undef UI_SETTINGS_DISCARD_TEXT_MAX
|
|
||||||
|
|
||||||
uiConfirmOpen(text, uiSettingsDiscardConfirmResult, def);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsDiscardConfirmResult(const bool_t result, void *user) {
|
|
||||||
uisettingstabdef_t *def = (uisettingstabdef_t *)user;
|
|
||||||
|
|
||||||
if(!result) {
|
|
||||||
uiMenuOpen(def->menu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
def->load();
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uimenu.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
#include "uisettingsdata.h"
|
|
||||||
|
|
||||||
#define UI_SETTINGS_TAB_COUNT 4
|
|
||||||
#define UI_SETTINGS_TAB_LABEL_MAX 32
|
|
||||||
#define UI_SETTINGS_APPLY_LABEL_MAX 32
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const char_t *localeId;
|
|
||||||
errorret_t (*init)(uisettingsdata_t *data);
|
|
||||||
uimenu_t *menu;
|
|
||||||
void (*load)(void);
|
|
||||||
void (*apply)(void);
|
|
||||||
bool_t (*hasChanges)(void);
|
|
||||||
} uisettingstabdef_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t tabsMenu;
|
|
||||||
uimenuitem_t tabs[UI_SETTINGS_TAB_COUNT];
|
|
||||||
char_t tabLabels[UI_SETTINGS_TAB_COUNT][UI_SETTINGS_TAB_LABEL_MAX];
|
|
||||||
char_t applyLabel[UI_SETTINGS_APPLY_LABEL_MAX];
|
|
||||||
uisettingsdata_t data;
|
|
||||||
uiframecache_t frameCache;
|
|
||||||
} uisettings_t;
|
|
||||||
|
|
||||||
extern uisettings_t UI_SETTINGS;
|
|
||||||
extern uisettingstabdef_t UI_SETTINGS_TAB_DEFS[UI_SETTINGS_TAB_COUNT];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu closed callback that flags a pending discard-changes
|
|
||||||
* confirmation if the menu's settings page/panel reports unsaved
|
|
||||||
* changes. Pass this directly as a settings page menu's closed
|
|
||||||
* callback. The pending def is picked up by uiSettingsUpdate on the
|
|
||||||
* next tick.
|
|
||||||
*
|
|
||||||
* @param menu The menu that was just closed.
|
|
||||||
*/
|
|
||||||
void uiSettingsDiscardMenuClosed(const uimenu_t *menu);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the discard-changes confirmation dialog for def. Called once
|
|
||||||
* by uiSettingsUpdate when uiSettingsDiscardMenuClosed has flagged a
|
|
||||||
* pending def.
|
|
||||||
*
|
|
||||||
* @param def The tab/panel def to show the confirmation for.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsDiscardUpdate(uisettingstabdef_t *def);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback passed to uiConfirmOpen by uiSettingsDiscardUpdate. Reopens
|
|
||||||
* the menu if the user chose to keep editing, otherwise reloads the
|
|
||||||
* widgets back to the actual engine values.
|
|
||||||
*
|
|
||||||
* @param result True if the user confirmed discarding changes, false
|
|
||||||
* to keep editing.
|
|
||||||
* @param user The uisettingstabdef_t* this confirmation belongs to.
|
|
||||||
*/
|
|
||||||
void uiSettingsDiscardConfirmResult(const bool_t result, void *user);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the settings panel and all of its category pages.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the settings panel. No-op when not visible.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the settings panel. Shows a discard-changes confirmation if
|
|
||||||
* the panel was just closed with unsaved changes.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the settings panel is currently open.
|
|
||||||
*
|
|
||||||
* @returns True if open.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsIsOpen(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the settings panel. No-op when already open.
|
|
||||||
*/
|
|
||||||
void uiSettingsOpen();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the settings panel. No-op when already closed.
|
|
||||||
*/
|
|
||||||
void uiSettingsClose();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the settings panel.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsDispose(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads every category page's widgets from the current engine settings.
|
|
||||||
* Called automatically by uiSettingsOpen.
|
|
||||||
*/
|
|
||||||
void uiSettingsLoad(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies every category page's widget values to the engine settings.
|
|
||||||
* Called automatically when a page's own Apply button is pressed.
|
|
||||||
*/
|
|
||||||
void uiSettingsApply(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether any category page has a widget value that differs
|
|
||||||
* from what was loaded.
|
|
||||||
*
|
|
||||||
* @returns True if there are unsaved changes.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsHasChanges(void);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uisettingsaudio.h"
|
|
||||||
#include "uisettings.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
void uiSettingsAudioSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
if(index != UI_SETTINGS_AUDIO_INDEX_APPLY) return;
|
|
||||||
uiSettingsAudioApply();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsAudioInit(uisettingsdata_t *data) {
|
|
||||||
uisettingsaudio_t *audio = &data->audio;
|
|
||||||
memoryZero(audio, sizeof(uisettingsaudio_t));
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&audio->menu, audio->items,
|
|
||||||
uiSettingsAudioSelected, uiSettingsDiscardMenuClosed, NULL
|
|
||||||
);
|
|
||||||
MENU_LABEL("No audio settings yet");
|
|
||||||
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
|
||||||
|
|
||||||
MENU_END(audio->items, 1);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsAudioLoad(void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsAudioApply(void) {
|
|
||||||
uiMenuClose(&UI_SETTINGS.data.audio.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsAudioHasChanges(void) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "uisettingsdata.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the audio settings page into the shared settings data
|
|
||||||
* union.
|
|
||||||
*
|
|
||||||
* @param data The shared settings data union to initialize into.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsAudioInit(uisettingsdata_t *data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the page's widgets from the current engine audio settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsAudioLoad(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the page's widget values to the engine audio settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsAudioApply(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether any widget's value differs from what was loaded.
|
|
||||||
*
|
|
||||||
* @returns True if there are unsaved changes.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsAudioHasChanges(void);
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uimenu.h"
|
|
||||||
|
|
||||||
#define UI_SETTINGS_GENERAL_ITEM_COUNT 3
|
|
||||||
#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE 0
|
|
||||||
#define UI_SETTINGS_GENERAL_INDEX_LANGUAGE_DETAIL 1
|
|
||||||
#define UI_SETTINGS_GENERAL_INDEX_APPLY 2
|
|
||||||
#define UI_SETTINGS_GENERAL_LOCALE_COUNT 3
|
|
||||||
#define UI_SETTINGS_GENERAL_LABEL_MAX 32
|
|
||||||
#define UI_SETTINGS_GENERAL_INFO_MAX 64
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t menu;
|
|
||||||
uimenuitem_t items[UI_SETTINGS_GENERAL_ITEM_COUNT];
|
|
||||||
const char_t *localeNames[UI_SETTINGS_GENERAL_LOCALE_COUNT];
|
|
||||||
char_t languageLabel[UI_SETTINGS_GENERAL_LABEL_MAX];
|
|
||||||
char_t labelInfo[UI_SETTINGS_GENERAL_INFO_MAX];
|
|
||||||
} uisettingsgeneral_t;
|
|
||||||
|
|
||||||
#define UI_SETTINGS_INPUT_ITEM_COUNT 2
|
|
||||||
#define UI_SETTINGS_INPUT_LABEL_MAX 32
|
|
||||||
#define UI_SETTINGS_INPUT_INDEX_DEADZONE 0
|
|
||||||
#define UI_SETTINGS_INPUT_INDEX_APPLY 1
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t menu;
|
|
||||||
uimenuitem_t items[UI_SETTINGS_INPUT_ITEM_COUNT];
|
|
||||||
char_t deadzoneLabel[UI_SETTINGS_INPUT_LABEL_MAX];
|
|
||||||
} uisettingsinput_t;
|
|
||||||
|
|
||||||
#define UI_SETTINGS_DISPLAY_ITEM_COUNT 2
|
|
||||||
#define UI_SETTINGS_DISPLAY_INDEX_APPLY 1
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t menu;
|
|
||||||
uimenuitem_t items[UI_SETTINGS_DISPLAY_ITEM_COUNT];
|
|
||||||
} uisettingsdisplay_t;
|
|
||||||
|
|
||||||
#define UI_SETTINGS_AUDIO_ITEM_COUNT 2
|
|
||||||
#define UI_SETTINGS_AUDIO_INDEX_APPLY 1
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenu_t menu;
|
|
||||||
uimenuitem_t items[UI_SETTINGS_AUDIO_ITEM_COUNT];
|
|
||||||
} uisettingsaudio_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared, overlapping storage for every settings tab's page data. Only
|
|
||||||
* one member is ever live at a time -- the active tab's Init writes
|
|
||||||
* into it, overwriting whatever the previously active tab left there.
|
|
||||||
* This trades simultaneous access to every tab for a memory footprint
|
|
||||||
* of only the single largest page.
|
|
||||||
*/
|
|
||||||
typedef union {
|
|
||||||
uisettingsgeneral_t general;
|
|
||||||
uisettingsinput_t input;
|
|
||||||
uisettingsdisplay_t display;
|
|
||||||
uisettingsaudio_t audio;
|
|
||||||
} uisettingsdata_t;
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uisettingsdisplay.h"
|
|
||||||
#include "uisettings.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
void uiSettingsDisplaySelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
if(index != UI_SETTINGS_DISPLAY_INDEX_APPLY) return;
|
|
||||||
uiSettingsDisplayApply();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsDisplayInit(uisettingsdata_t *data) {
|
|
||||||
uisettingsdisplay_t *display = &data->display;
|
|
||||||
memoryZero(display, sizeof(uisettingsdisplay_t));
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&display->menu, display->items,
|
|
||||||
uiSettingsDisplaySelected, uiSettingsDiscardMenuClosed, NULL
|
|
||||||
);
|
|
||||||
MENU_LABEL("No display settings yet");
|
|
||||||
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
|
||||||
|
|
||||||
MENU_END(display->items, 1);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsDisplayLoad(void) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsDisplayApply(void) {
|
|
||||||
uiMenuClose(&UI_SETTINGS.data.display.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsDisplayHasChanges(void) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "uisettingsdata.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the display settings page into the shared settings data
|
|
||||||
* union.
|
|
||||||
*
|
|
||||||
* @param data The shared settings data union to initialize into.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsDisplayInit(uisettingsdata_t *data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the page's widgets from the current engine display settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsDisplayLoad(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the page's widget values to the engine display settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsDisplayApply(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether any widget's value differs from what was loaded.
|
|
||||||
*
|
|
||||||
* @returns True if there are unsaved changes.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsDisplayHasChanges(void);
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uisettingsgeneral.h"
|
|
||||||
#include "uisettings.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "locale/localemanager.h"
|
|
||||||
#include "locale/localeinfo.h"
|
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
|
||||||
|
|
||||||
static const localeinfo_t *const UI_SETTINGS_GENERAL_LOCALES[
|
|
||||||
UI_SETTINGS_GENERAL_LOCALE_COUNT
|
|
||||||
] = {
|
|
||||||
&LOCALE_EN_US,
|
|
||||||
&LOCALE_JP_JP,
|
|
||||||
&LOCALE_ES_MX
|
|
||||||
};
|
|
||||||
|
|
||||||
void uiSettingsGeneralSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
switch(index) {
|
|
||||||
case UI_SETTINGS_GENERAL_INDEX_APPLY:
|
|
||||||
uiSettingsGeneralApply();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsGeneralInit(uisettingsdata_t *data) {
|
|
||||||
uisettingsgeneral_t *general = &data->general;
|
|
||||||
memoryZero(general, sizeof(uisettingsgeneral_t));
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) {
|
|
||||||
general->localeNames[i] = UI_SETTINGS_GENERAL_LOCALES[i]->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
"ui.settings.general.language",
|
|
||||||
0,
|
|
||||||
general->languageLabel,
|
|
||||||
UI_SETTINGS_GENERAL_LABEL_MAX
|
|
||||||
));
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
"ui.settings.general.language_detail",
|
|
||||||
0,
|
|
||||||
general->labelInfo,
|
|
||||||
UI_SETTINGS_GENERAL_INFO_MAX
|
|
||||||
));
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&general->menu, general->items,
|
|
||||||
uiSettingsGeneralSelected, uiSettingsDiscardMenuClosed, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
MENU_DROPDOWN(
|
|
||||||
general->languageLabel, general->localeNames,
|
|
||||||
UI_SETTINGS_GENERAL_LOCALE_COUNT, 0
|
|
||||||
);
|
|
||||||
MENU_LABEL(general->labelInfo);
|
|
||||||
|
|
||||||
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
|
||||||
MENU_END(general->items, 1);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsGeneralLoad(void) {
|
|
||||||
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
|
||||||
|
|
||||||
uint8_t index = 0;
|
|
||||||
for(uint8_t i = 0; i < UI_SETTINGS_GENERAL_LOCALE_COUNT; i++) {
|
|
||||||
if(stringCompare(LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[i]->file) != 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiDropdownSetSelectedIndex(
|
|
||||||
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown, index
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsGeneralApply(void) {
|
|
||||||
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
|
||||||
|
|
||||||
uint8_t index = uiDropdownGetSelectedIndex(
|
|
||||||
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown
|
|
||||||
);
|
|
||||||
errorCatch(localeManagerSetLocale(UI_SETTINGS_GENERAL_LOCALES[index]));
|
|
||||||
|
|
||||||
uiMenuClose(&general->menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsGeneralHasChanges(void) {
|
|
||||||
uisettingsgeneral_t *general = &UI_SETTINGS.data.general;
|
|
||||||
|
|
||||||
uint8_t index = uiDropdownGetSelectedIndex(
|
|
||||||
&general->items[UI_SETTINGS_GENERAL_INDEX_LANGUAGE].dropdown
|
|
||||||
);
|
|
||||||
return stringCompare(
|
|
||||||
LOCALE.locale->file, UI_SETTINGS_GENERAL_LOCALES[index]->file
|
|
||||||
) != 0;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "uisettingsdata.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the general settings page into the shared settings data
|
|
||||||
* union.
|
|
||||||
*
|
|
||||||
* @param data The shared settings data union to initialize into.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsGeneralInit(uisettingsdata_t *data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the page's widgets from the current engine general settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsGeneralLoad(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the page's widget values to the engine general settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsGeneralApply(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether any widget's value differs from what was loaded.
|
|
||||||
*
|
|
||||||
* @returns True if there are unsaved changes.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsGeneralHasChanges(void);
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uisettingsinput.h"
|
|
||||||
#include "uisettings.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "locale/localemanager.h"
|
|
||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
|
||||||
#include "input/input.h"
|
|
||||||
#include "save/savesettings.h"
|
|
||||||
|
|
||||||
void uiSettingsInputSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
if(index != UI_SETTINGS_INPUT_INDEX_APPLY) return;
|
|
||||||
uiSettingsInputApply();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSettingsInputInit(uisettingsdata_t *data) {
|
|
||||||
uisettingsinput_t *input = &data->input;
|
|
||||||
memoryZero(input, sizeof(uisettingsinput_t));
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&input->menu, input->items,
|
|
||||||
uiSettingsInputSelected, uiSettingsDiscardMenuClosed, NULL
|
|
||||||
);
|
|
||||||
#ifdef DUSK_INPUT_GAMEPAD
|
|
||||||
errorChain(assetLocaleGetString(
|
|
||||||
&LOCALE.entry->data.locale,
|
|
||||||
"ui.settings.input.deadzone",
|
|
||||||
0,
|
|
||||||
input->deadzoneLabel,
|
|
||||||
UI_SETTINGS_INPUT_LABEL_MAX
|
|
||||||
));
|
|
||||||
MENU_SLIDER_FLOAT(
|
|
||||||
input->deadzoneLabel, INPUT_DEADZONE_DEFAULT, 0.0f, 1.0f, 0.05f
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
MENU_LABEL("No input settings yet");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MENU_BUTTON(UI_SETTINGS.applyLabel);
|
|
||||||
MENU_END(input->items, 1);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsInputLoad(void) {
|
|
||||||
#ifdef DUSK_INPUT_GAMEPAD
|
|
||||||
uiSliderSetFloat(
|
|
||||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider,
|
|
||||||
INPUT.deadzone
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSettingsInputApply(void) {
|
|
||||||
#ifdef DUSK_INPUT_GAMEPAD
|
|
||||||
INPUT.deadzone = uiSliderGetFloat(
|
|
||||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
errorCatch(errorPrint(saveSettingsWrite()));
|
|
||||||
uiMenuClose(&UI_SETTINGS.data.input.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSettingsInputHasChanges(void) {
|
|
||||||
#ifdef DUSK_INPUT_GAMEPAD
|
|
||||||
if(uiSliderGetFloat(
|
|
||||||
&UI_SETTINGS.data.input.items[UI_SETTINGS_INPUT_INDEX_DEADZONE].slider
|
|
||||||
) != INPUT.deadzone) return true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "uisettingsdata.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the input settings page into the shared settings data
|
|
||||||
* union.
|
|
||||||
*
|
|
||||||
* @param data The shared settings data union to initialize into.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSettingsInputInit(uisettingsdata_t *data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the page's widgets from the current engine input settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsInputLoad(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the page's widget values to the engine input settings.
|
|
||||||
*/
|
|
||||||
void uiSettingsInputApply(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether any widget's value differs from what was loaded.
|
|
||||||
*
|
|
||||||
* @returns True if there are unsaved changes.
|
|
||||||
*/
|
|
||||||
bool_t uiSettingsInputHasChanges(void);
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiconfirm.h"
|
|
||||||
#include "uiframe.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/math.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
|
|
||||||
#define UI_CONFIRM_BACKDROP_COLOR color4b(0, 0, 0, 160)
|
|
||||||
|
|
||||||
uiconfirm_t UI_CONFIRM;
|
|
||||||
|
|
||||||
void uiConfirmSelected(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
) {
|
|
||||||
UI_CONFIRM.result = index == UI_CONFIRM_INDEX_CONFIRM;
|
|
||||||
uiConfirmClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiConfirmClosed(const uimenu_t *menu) {
|
|
||||||
if(UI_CONFIRM.callback != NULL) {
|
|
||||||
UI_CONFIRM.callback(UI_CONFIRM.result, UI_CONFIRM.user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiConfirmInit(void) {
|
|
||||||
memoryZero(&UI_CONFIRM, sizeof(uiconfirm_t));
|
|
||||||
uiLabelInit(&UI_CONFIRM.textLabel, &FONT_DEFAULT);
|
|
||||||
|
|
||||||
MENU_BEGIN(
|
|
||||||
&UI_CONFIRM.menu, UI_CONFIRM.items, uiConfirmSelected, uiConfirmClosed, NULL
|
|
||||||
);
|
|
||||||
MENU_BUTTON("Confirm");
|
|
||||||
MENU_BUTTON("Cancel");
|
|
||||||
|
|
||||||
MENU_END(UI_CONFIRM.items, menuIndex);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiConfirmDraw(void) {
|
|
||||||
if(!uiMenuIsActive(&UI_CONFIRM.menu)) errorOk();
|
|
||||||
|
|
||||||
spritebatchsprite_t backdropSprite = {
|
|
||||||
.min = { 0.0f, 0.0f, 0.0f },
|
|
||||||
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
shadermaterial_t backdropMaterial = {
|
|
||||||
.unlit = {
|
|
||||||
.color = UI_CONFIRM_BACKDROP_COLOR,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(
|
|
||||||
spriteBatchBuffer(&backdropSprite, 1, &SHADER_UNLIT, backdropMaterial)
|
|
||||||
);
|
|
||||||
|
|
||||||
int32_t textW, textH;
|
|
||||||
uiLabelGetSize(&UI_CONFIRM.textLabel, &textW, &textH);
|
|
||||||
|
|
||||||
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
float_t width = mathMax(
|
|
||||||
(float_t)textW + (UI_FRAME_START_X * 2), UI_CONFIRM_MIN_WIDTH
|
|
||||||
);
|
|
||||||
float_t height = (UI_FRAME_START_Y * 2) + rowHeight + UI_FRAME_PADDING_Y +
|
|
||||||
rowHeight;
|
|
||||||
float_t x = (float_t)SCREEN.scanX + ((float_t)SCREEN.scanWidth - width) * 0.5f;
|
|
||||||
float_t y = (float_t)SCREEN.scanY + ((float_t)SCREEN.scanHeight - height) * 0.5f;
|
|
||||||
|
|
||||||
errorChain(uiFrameDrawCached(&UI_CONFIRM.frameCache, x, y, width, height));
|
|
||||||
|
|
||||||
float_t contentX = x + UI_FRAME_START_X;
|
|
||||||
float_t contentY = y + UI_FRAME_START_Y;
|
|
||||||
float_t contentWidth = width - (UI_FRAME_START_X * 2);
|
|
||||||
|
|
||||||
errorChain(uiLabelDraw(&UI_CONFIRM.textLabel, contentX, contentY));
|
|
||||||
|
|
||||||
float_t buttonsY = contentY + rowHeight + UI_FRAME_PADDING_Y;
|
|
||||||
errorChain(uiMenuDraw(&UI_CONFIRM.menu, contentX, buttonsY, contentWidth, rowHeight));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiConfirmIsOpen(void) {
|
|
||||||
return uiMenuIsActive(&UI_CONFIRM.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiConfirmGetResult(void) {
|
|
||||||
return UI_CONFIRM.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiConfirmOpen(
|
|
||||||
const char_t *question,
|
|
||||||
uiconfirmcallback_t callback,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
assertNotNull(question, "Question cannot be NULL");
|
|
||||||
uiLabelSetText(&UI_CONFIRM.textLabel, question);
|
|
||||||
UI_CONFIRM.callback = callback;
|
|
||||||
UI_CONFIRM.user = user;
|
|
||||||
UI_CONFIRM.result = false;
|
|
||||||
uiMenuOpen(&UI_CONFIRM.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiConfirmClose(void) {
|
|
||||||
uiMenuClose(&UI_CONFIRM.menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiConfirmDispose(void) {
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uimenu.h"
|
|
||||||
#include "ui/widget/uilabel.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
|
|
||||||
#define UI_CONFIRM_MIN_WIDTH 160.0f
|
|
||||||
#define UI_CONFIRM_INDEX_CONFIRM 0
|
|
||||||
#define UI_CONFIRM_INDEX_CANCEL 1
|
|
||||||
#define UI_CONFIRM_ITEM_COUNT 2
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback invoked once a confirm dialog is dismissed.
|
|
||||||
*
|
|
||||||
* @param result True if Confirm was selected, false if Cancel was
|
|
||||||
* selected or the dialog was backed out of.
|
|
||||||
* @param user Arbitrary pointer passed to uiConfirmOpen.
|
|
||||||
*/
|
|
||||||
typedef void (*uiconfirmcallback_t)(const bool_t result, void *user);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uilabel_t textLabel;
|
|
||||||
uimenu_t menu;
|
|
||||||
uimenuitem_t items[UI_CONFIRM_ITEM_COUNT];
|
|
||||||
uiconfirmcallback_t callback;
|
|
||||||
void *user;
|
|
||||||
bool_t result;
|
|
||||||
uiframecache_t frameCache;
|
|
||||||
} uiconfirm_t;
|
|
||||||
|
|
||||||
extern uiconfirm_t UI_CONFIRM;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the confirm dialog.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConfirmInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the confirm dialog: a semi-transparent black backdrop covering
|
|
||||||
* the whole screen, then its own centered frame with the question text
|
|
||||||
* and Confirm/Cancel buttons. No-op when not open.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConfirmDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the confirm dialog is currently open.
|
|
||||||
*
|
|
||||||
* @returns True if open.
|
|
||||||
*/
|
|
||||||
bool_t uiConfirmIsOpen(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the result of the most recently dismissed confirm dialog.
|
|
||||||
*
|
|
||||||
* @returns True if Confirm was selected, false otherwise.
|
|
||||||
*/
|
|
||||||
bool_t uiConfirmGetResult(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the confirm dialog with the given question text. callback is
|
|
||||||
* invoked exactly once with the result, whether the dialog was
|
|
||||||
* dismissed by selecting a button or by pressing cancel/back.
|
|
||||||
*
|
|
||||||
* @param question Display text; copied internally, safe to be transient.
|
|
||||||
* @param callback Called with the result once the dialog closes. May be
|
|
||||||
* NULL.
|
|
||||||
* @param user Arbitrary pointer passed through to callback.
|
|
||||||
*/
|
|
||||||
void uiConfirmOpen(
|
|
||||||
const char_t *question,
|
|
||||||
uiconfirmcallback_t callback,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the confirm dialog. No-op when already closed.
|
|
||||||
*/
|
|
||||||
void uiConfirmClose(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the confirm dialog.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiConfirmDispose(void);
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiframe.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
uiframe_t UI_FRAME;
|
|
||||||
|
|
||||||
errorret_t uiFrameInit(void) {
|
|
||||||
memoryZero(&UI_FRAME, sizeof(uiframe_t));
|
|
||||||
|
|
||||||
// Init texture.
|
|
||||||
color_t border = color4b(0, 50, 255, 255);
|
|
||||||
color_t center = color4b(0, 100, 220, 240);
|
|
||||||
|
|
||||||
for(uint8_t y = 0; y < UI_FRAME_TEXTURE_HEIGHT_POW2; y++) {
|
|
||||||
for(uint8_t x = 0; x < UI_FRAME_TEXTURE_WIDTH_POW2; x++) {
|
|
||||||
color_t c;
|
|
||||||
if(x >= UI_FRAME_TEXTURE_WIDTH || y >= UI_FRAME_TEXTURE_HEIGHT) {
|
|
||||||
c = COLOR_TRANSPARENT;
|
|
||||||
} else if(
|
|
||||||
y < UI_FRAME_TILE_HEIGHT || y >= UI_FRAME_TILE_HEIGHT * 2 ||
|
|
||||||
x < UI_FRAME_TILE_WIDTH || x >= UI_FRAME_TILE_WIDTH * 2
|
|
||||||
) {
|
|
||||||
c = border;
|
|
||||||
} else {
|
|
||||||
c = center;
|
|
||||||
}
|
|
||||||
UI_FRAME.pixels[y * UI_FRAME_TEXTURE_WIDTH_POW2 + x] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(textureInit(
|
|
||||||
&UI_FRAME.texture,
|
|
||||||
UI_FRAME_TEXTURE_WIDTH_POW2, UI_FRAME_TEXTURE_HEIGHT_POW2,
|
|
||||||
TEXTURE_FORMAT_RGBA,
|
|
||||||
(texturedata_t){ .rgbaColors = UI_FRAME.pixels }
|
|
||||||
));
|
|
||||||
|
|
||||||
UI_FRAME.tileset.tileWidth = UI_FRAME_TILE_WIDTH;
|
|
||||||
UI_FRAME.tileset.tileHeight = UI_FRAME_TILE_HEIGHT;
|
|
||||||
UI_FRAME.tileset.columns = 3;
|
|
||||||
UI_FRAME.tileset.rows = 3;
|
|
||||||
UI_FRAME.tileset.tileCount = 9;
|
|
||||||
UI_FRAME.tileset.uv[0] =
|
|
||||||
(float_t)UI_FRAME_TILE_WIDTH / UI_FRAME_TEXTURE_WIDTH_POW2;
|
|
||||||
UI_FRAME.tileset.uv[1] =
|
|
||||||
(float_t)UI_FRAME_TILE_HEIGHT / UI_FRAME_TEXTURE_HEIGHT_POW2;
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFrameDraw(
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_WHITE,
|
|
||||||
.texture = &UI_FRAME.texture
|
|
||||||
}
|
|
||||||
};
|
|
||||||
spritebatchsprite_t sprites[9];
|
|
||||||
uiFrameBuildSprites(sprites, x, y, width, height);
|
|
||||||
return spriteBatchBuffer(sprites, 9, &SHADER_UNLIT, material);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFrameBuildSprites(
|
|
||||||
spritebatchsprite_t sprites[9],
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
float_t tileW = (float_t)UI_FRAME_BORDER_WIDTH;
|
|
||||||
float_t tileH = (float_t)UI_FRAME_BORDER_HEIGHT;
|
|
||||||
|
|
||||||
sprites[0] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 0, 0,
|
|
||||||
x, y, tileW, tileH
|
|
||||||
);
|
|
||||||
sprites[1] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 1, 0,
|
|
||||||
x + tileW, y, width - (tileW * 2.0f), tileH
|
|
||||||
);
|
|
||||||
sprites[2] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 2, 0,
|
|
||||||
x + width - tileW, y, tileW, tileH
|
|
||||||
);
|
|
||||||
sprites[3] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 0, 1,
|
|
||||||
x, y + tileH, tileW, height - (tileH * 2.0f)
|
|
||||||
);
|
|
||||||
sprites[4] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 1, 1,
|
|
||||||
x + tileW, y + tileH,
|
|
||||||
width - (tileW * 2.0f), height - (tileH * 2.0f)
|
|
||||||
);
|
|
||||||
sprites[5] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 2, 1,
|
|
||||||
x + width - tileW, y + tileH, tileW, height - (tileH * 2.0f)
|
|
||||||
);
|
|
||||||
sprites[6] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 0, 2,
|
|
||||||
x, y + height - tileH, tileW, tileH
|
|
||||||
);
|
|
||||||
sprites[7] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 1, 2,
|
|
||||||
x + tileW, y + height - tileH, width - (tileW * 2.0f), tileH
|
|
||||||
);
|
|
||||||
sprites[8] = spriteBatchSpriteTilesetPosition(
|
|
||||||
&UI_FRAME.tileset, 2, 2,
|
|
||||||
x + width - tileW, y + height - tileH, tileW, tileH
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFrameDrawCached(
|
|
||||||
uiframecache_t *cache,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
assertNotNull(cache, "Frame cache cannot be NULL");
|
|
||||||
|
|
||||||
if(
|
|
||||||
!cache->built ||
|
|
||||||
cache->lastX != x || cache->lastY != y ||
|
|
||||||
cache->lastWidth != width || cache->lastHeight != height
|
|
||||||
) {
|
|
||||||
uiFrameBuildSprites(cache->sprites, x, y, width, height);
|
|
||||||
cache->lastX = x;
|
|
||||||
cache->lastY = y;
|
|
||||||
cache->lastWidth = width;
|
|
||||||
cache->lastHeight = height;
|
|
||||||
cache->built = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_WHITE,
|
|
||||||
.texture = &UI_FRAME.texture
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return spriteBatchBuffer(cache->sprites, 9, &SHADER_UNLIT, material);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFrameDispose(void) {
|
|
||||||
return textureDispose(&UI_FRAME.texture);
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/texture/tileset.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
|
|
||||||
#define UI_FRAME_BORDER_WIDTH 6
|
|
||||||
#define UI_FRAME_BORDER_HEIGHT 6
|
|
||||||
#define UI_FRAME_PADDING_X 2
|
|
||||||
#define UI_FRAME_PADDING_Y 2
|
|
||||||
#define UI_FRAME_START_X (UI_FRAME_BORDER_WIDTH + UI_FRAME_PADDING_X)
|
|
||||||
#define UI_FRAME_START_Y (UI_FRAME_BORDER_HEIGHT + UI_FRAME_PADDING_Y)
|
|
||||||
#define UI_FRAME_TILE_WIDTH 1
|
|
||||||
#define UI_FRAME_TILE_HEIGHT 1
|
|
||||||
#define UI_FRAME_TEXTURE_WIDTH (UI_FRAME_TILE_WIDTH * 3)
|
|
||||||
#define UI_FRAME_TEXTURE_HEIGHT (UI_FRAME_TILE_HEIGHT * 3)
|
|
||||||
#define UI_FRAME_TEXTURE_WIDTH_POW2 4
|
|
||||||
#define UI_FRAME_TEXTURE_HEIGHT_POW2 4
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
tileset_t tileset;
|
|
||||||
texture_t texture;
|
|
||||||
color_t pixels[UI_FRAME_BORDER_WIDTH * UI_FRAME_BORDER_HEIGHT];
|
|
||||||
} uiframe_t;
|
|
||||||
|
|
||||||
extern uiframe_t UI_FRAME;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the global UI_FRAME: builds the dummy texture and
|
|
||||||
* configures the tileset. Call once after displayInit().
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFrameInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws UI_FRAME using 9-slice rendering from its 3x3 tileset.
|
|
||||||
* Pushes quads to the sprite batch without flushing.
|
|
||||||
*
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @param width Total width of the frame.
|
|
||||||
* @param height Total height of the frame.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFrameDraw(
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the 9 sprites for a 9-slice frame at the given rect. Used
|
|
||||||
* internally by uiFrameDraw and uiFrameDrawCached -- call directly only
|
|
||||||
* if you need the raw sprites instead of buffering them.
|
|
||||||
*
|
|
||||||
* @param sprites Destination array of exactly 9 sprites.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @param width Total width of the frame.
|
|
||||||
* @param height Total height of the frame.
|
|
||||||
*/
|
|
||||||
void uiFrameBuildSprites(
|
|
||||||
spritebatchsprite_t sprites[9],
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A frame's cached 9-slice sprites, plus the rect they were built for.
|
|
||||||
* Owned by whichever widget/screen draws a frame repeatedly (dialogs,
|
|
||||||
* textboxes, settings panels) so uiFrameDrawCached can skip rebuilding
|
|
||||||
* the sprites when the rect hasn't moved since the last draw.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
spritebatchsprite_t sprites[9];
|
|
||||||
bool_t built;
|
|
||||||
float_t lastX;
|
|
||||||
float_t lastY;
|
|
||||||
float_t lastWidth;
|
|
||||||
float_t lastHeight;
|
|
||||||
} uiframecache_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a 9-slice frame using a caller-owned cache, only rebuilding the
|
|
||||||
* sprites (via uiFrameBuildSprites) when x/y/width/height differ from
|
|
||||||
* the last call -- unlike uiFrameDraw, which rebuilds unconditionally
|
|
||||||
* every time. Prefer this for frames redrawn every frame at a fixed or
|
|
||||||
* rarely-changing rect (dialogs, panels); use plain uiFrameDraw for
|
|
||||||
* genuinely one-off or per-frame-varying rects.
|
|
||||||
*
|
|
||||||
* @param cache Caller-owned cache, zero-initialized before first use.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @param width Total width of the frame.
|
|
||||||
* @param height Total height of the frame.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFrameDrawCached(
|
|
||||||
uiframecache_t *cache,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the global UI_FRAME, releasing its GPU texture.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFrameDispose(void);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uicrop.c
|
|
||||||
uifullbox.c
|
|
||||||
uiloading.c
|
|
||||||
)
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uicrop.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
|
|
||||||
uicrop_t UI_CROP;
|
|
||||||
|
|
||||||
errorret_t uiCropInit(void) {
|
|
||||||
UI_CROP.color = COLOR_BLACK;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiCropDraw(void) {
|
|
||||||
#ifndef DUSK_DISPLAY_SIZE_DYNAMIC
|
|
||||||
errorOk();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(
|
|
||||||
SCREEN.scanX == 0 &&
|
|
||||||
SCREEN.scanY == 0 &&
|
|
||||||
SCREEN.scanWidth == SCREEN.width &&
|
|
||||||
SCREEN.scanHeight == SCREEN.height
|
|
||||||
) errorOk();
|
|
||||||
|
|
||||||
float_t x0 = (float_t)SCREEN.scanX;
|
|
||||||
float_t y0 = (float_t)SCREEN.scanY;
|
|
||||||
float_t x1 = (float_t)(SCREEN.scanX + SCREEN.scanWidth);
|
|
||||||
float_t y1 = (float_t)(SCREEN.scanY + SCREEN.scanHeight);
|
|
||||||
float_t w = (float_t)SCREEN.width;
|
|
||||||
float_t h = (float_t)SCREEN.height;
|
|
||||||
|
|
||||||
spritebatchsprite_t sprites[4];
|
|
||||||
int32_t count = 0;
|
|
||||||
|
|
||||||
if(SCREEN.scanX > 0) {
|
|
||||||
sprites[count++] = (spritebatchsprite_t){
|
|
||||||
.min = { 0.0f, 0.0f, 0.0f },
|
|
||||||
.max = { x0, h, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SCREEN.scanX + SCREEN.scanWidth < SCREEN.width) {
|
|
||||||
sprites[count++] = (spritebatchsprite_t){
|
|
||||||
.min = { x1, 0.0f, 0.0f },
|
|
||||||
.max = { w, h, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SCREEN.scanY > 0) {
|
|
||||||
sprites[count++] = (spritebatchsprite_t){
|
|
||||||
.min = { x0, 0.0f, 0.0f },
|
|
||||||
.max = { x1, y0, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(SCREEN.scanY + SCREEN.scanHeight < SCREEN.height) {
|
|
||||||
sprites[count++] = (spritebatchsprite_t){
|
|
||||||
.min = { x0, y1, 0.0f },
|
|
||||||
.max = { x1, h, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(count == 0) errorOk();
|
|
||||||
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = UI_CROP.color,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(sprites, count, &SHADER_UNLIT, material));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
color_t color;
|
|
||||||
} uicrop_t;
|
|
||||||
|
|
||||||
extern uicrop_t UI_CROP;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the crop bars to opaque black.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiCropInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders solid-color bars covering every area outside the
|
|
||||||
* current scan-safe region. No-ops when the scan area equals
|
|
||||||
* the full viewport.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiCropDraw(void);
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uifullbox.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
|
|
||||||
uifullbox_t UI_FULLBOX_UNDER;
|
|
||||||
uifullbox_t UI_FULLBOX_OVER;
|
|
||||||
|
|
||||||
void uiFullboxInit(uifullbox_t *fullbox) {
|
|
||||||
assertNotNull(fullbox, "fullbox must not be NULL");
|
|
||||||
memoryZero(fullbox, sizeof(uifullbox_t));
|
|
||||||
eventInit(
|
|
||||||
&fullbox->onTransitionEnd,
|
|
||||||
fullbox->onTransitionEndCallbacks,
|
|
||||||
fullbox->onTransitionEndUsers,
|
|
||||||
4
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) {
|
|
||||||
assertNotNull(fullbox, "fullbox must not be NULL");
|
|
||||||
if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) return;
|
|
||||||
|
|
||||||
fullbox->time += delta;
|
|
||||||
if(fullbox->time >= fullbox->duration) {
|
|
||||||
fullbox->time = fullbox->duration;
|
|
||||||
eventInvoke(&fullbox->onTransitionEnd, fullbox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static color_t uiFullboxGetColor(const uifullbox_t *fullbox) {
|
|
||||||
if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) {
|
|
||||||
return fullbox->toColor;
|
|
||||||
}
|
|
||||||
float_t t = easingApply(fullbox->easing, fullbox->time / fullbox->duration);
|
|
||||||
return color4b(
|
|
||||||
(uint8_t)((float_t)fullbox->fromColor.r +
|
|
||||||
((float_t)fullbox->toColor.r - (float_t)fullbox->fromColor.r) * t),
|
|
||||||
(uint8_t)((float_t)fullbox->fromColor.g +
|
|
||||||
((float_t)fullbox->toColor.g - (float_t)fullbox->fromColor.g) * t),
|
|
||||||
(uint8_t)((float_t)fullbox->fromColor.b +
|
|
||||||
((float_t)fullbox->toColor.b - (float_t)fullbox->fromColor.b) * t),
|
|
||||||
(uint8_t)((float_t)fullbox->fromColor.a +
|
|
||||||
((float_t)fullbox->toColor.a - (float_t)fullbox->fromColor.a) * t)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxDraw(uifullbox_t *fullbox) {
|
|
||||||
assertNotNull(fullbox, "fullbox must not be NULL");
|
|
||||||
|
|
||||||
color_t color = uiFullboxGetColor(fullbox);
|
|
||||||
if(color.a == 0) errorOk();
|
|
||||||
|
|
||||||
spritebatchsprite_t sprite = {
|
|
||||||
.min = { 0.0f, 0.0f, 0.0f },
|
|
||||||
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = color,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiFullboxTransition(
|
|
||||||
uifullbox_t *fullbox,
|
|
||||||
color_t from,
|
|
||||||
color_t to,
|
|
||||||
float_t duration,
|
|
||||||
easingtype_t easing
|
|
||||||
) {
|
|
||||||
assertNotNull(fullbox, "fullbox must not be NULL");
|
|
||||||
fullbox->fromColor = from;
|
|
||||||
fullbox->toColor = to;
|
|
||||||
fullbox->duration = duration;
|
|
||||||
fullbox->time = 0.0f;
|
|
||||||
fullbox->easing = easing;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxUnderInit(void) {
|
|
||||||
uiFullboxInit(&UI_FULLBOX_UNDER);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxUnderUpdate(void) {
|
|
||||||
uiFullboxUpdate(&UI_FULLBOX_UNDER, TIME.delta);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxUnderDraw(void) {
|
|
||||||
return uiFullboxDraw(&UI_FULLBOX_UNDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxOverInit(void) {
|
|
||||||
uiFullboxInit(&UI_FULLBOX_OVER);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxOverUpdate(void) {
|
|
||||||
uiFullboxUpdate(&UI_FULLBOX_OVER, TIME.delta);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiFullboxOverDraw(void) {
|
|
||||||
return uiFullboxDraw(&UI_FULLBOX_OVER);
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "animation/easing.h"
|
|
||||||
#include "event/event.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
color_t fromColor;
|
|
||||||
color_t toColor;
|
|
||||||
float_t duration;
|
|
||||||
float_t time;
|
|
||||||
easingtype_t easing;
|
|
||||||
eventcallback_t onTransitionEndCallbacks[4];
|
|
||||||
void *onTransitionEndUsers[4];
|
|
||||||
event_t onTransitionEnd;
|
|
||||||
} uifullbox_t;
|
|
||||||
|
|
||||||
extern uifullbox_t UI_FULLBOX_UNDER;
|
|
||||||
extern uifullbox_t UI_FULLBOX_OVER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a fullbox, zeroing all fields.
|
|
||||||
*
|
|
||||||
* @param fullbox The fullbox to initialize.
|
|
||||||
*/
|
|
||||||
void uiFullboxInit(uifullbox_t *fullbox);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances the fullbox transition. Fires onTransitionEnd once when the
|
|
||||||
* transition completes. Safe to call when no transition is running.
|
|
||||||
*
|
|
||||||
* @param fullbox The fullbox to update.
|
|
||||||
* @param delta Seconds elapsed since last update.
|
|
||||||
*/
|
|
||||||
void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the fullbox. Skipped entirely when the current alpha is zero.
|
|
||||||
*
|
|
||||||
* @param fullbox The fullbox to draw.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxDraw(uifullbox_t *fullbox);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins a color transition.
|
|
||||||
*
|
|
||||||
* @param fullbox The fullbox to transition.
|
|
||||||
* @param from Start color.
|
|
||||||
* @param to End color.
|
|
||||||
* @param duration Transition duration in seconds.
|
|
||||||
* @param easing Easing function to apply.
|
|
||||||
*/
|
|
||||||
void uiFullboxTransition(
|
|
||||||
uifullbox_t *fullbox,
|
|
||||||
color_t from,
|
|
||||||
color_t to,
|
|
||||||
float_t duration,
|
|
||||||
easingtype_t easing
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxUnderInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxUnderUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw function for UI_FULLBOX_UNDER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxUnderDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxOverInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxOverUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw function for UI_FULLBOX_OVER (registered in UI_ELEMENTS).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiFullboxOverDraw(void);
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiloading.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "uifullbox.h"
|
|
||||||
|
|
||||||
#define UI_LOADING_TEXT "loading"
|
|
||||||
|
|
||||||
uiloading_t UI_LOADING;
|
|
||||||
|
|
||||||
errorret_t uiLoadingInit(void) {
|
|
||||||
memoryZero(&UI_LOADING, sizeof(uiloading_t));
|
|
||||||
uiLabelInit(&UI_LOADING.textLabel, &FONT_DEFAULT);
|
|
||||||
uiLabelSetText(&UI_LOADING.textLabel, UI_LOADING_TEXT);
|
|
||||||
eventInit(
|
|
||||||
&UI_LOADING.onTransitionEnd,
|
|
||||||
UI_LOADING.onTransitionEndCallbacks,
|
|
||||||
UI_LOADING.onTransitionEndUsers,
|
|
||||||
4
|
|
||||||
);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiLoadingUpdate(void) {
|
|
||||||
if(UI_LOADING.duration > 0.0f && UI_LOADING.time < UI_LOADING.duration) {
|
|
||||||
UI_LOADING.time += TIME.delta;
|
|
||||||
if(UI_LOADING.time >= UI_LOADING.duration) {
|
|
||||||
UI_LOADING.time = UI_LOADING.duration;
|
|
||||||
eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiLoadingDraw(void) {
|
|
||||||
float_t alpha;
|
|
||||||
if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration) {
|
|
||||||
alpha = UI_LOADING.toAlpha;
|
|
||||||
} else {
|
|
||||||
float_t t = UI_LOADING.time / UI_LOADING.duration;
|
|
||||||
alpha = UI_LOADING.fromAlpha +
|
|
||||||
(UI_LOADING.toAlpha - UI_LOADING.fromAlpha) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(alpha <= 0.0f) errorOk();
|
|
||||||
|
|
||||||
int32_t textW, textH;
|
|
||||||
uiLabelGetSize(&UI_LOADING.textLabel, &textW, &textH);
|
|
||||||
|
|
||||||
float_t x = (float_t)(SCREEN.scanX + SCREEN.scanWidth) -
|
|
||||||
(float_t)textW - UI_LOADING_MARGIN;
|
|
||||||
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) -
|
|
||||||
(float_t)textH - UI_LOADING_MARGIN;
|
|
||||||
|
|
||||||
color_t color = COLOR_WHITE;
|
|
||||||
color.a = (uint8_t)(alpha * 255.0f);
|
|
||||||
uiLabelSetColor(&UI_LOADING.textLabel, color);
|
|
||||||
|
|
||||||
errorChain(uiLabelDraw(&UI_LOADING.textLabel, x, y));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void uiLoadingTransition(
|
|
||||||
const float_t from,
|
|
||||||
const float_t to,
|
|
||||||
const eventcallback_t callback,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
UI_LOADING.fromAlpha = from;
|
|
||||||
UI_LOADING.toAlpha = to;
|
|
||||||
UI_LOADING.duration = UI_LOADING_FADE_DURATION;
|
|
||||||
UI_LOADING.time = 0.0f;
|
|
||||||
eventInit(
|
|
||||||
&UI_LOADING.onTransitionEnd,
|
|
||||||
UI_LOADING.onTransitionEndCallbacks,
|
|
||||||
UI_LOADING.onTransitionEndUsers,
|
|
||||||
4
|
|
||||||
);
|
|
||||||
if(callback) eventSubscribe(&UI_LOADING.onTransitionEnd, callback, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiLoadingShow(eventcallback_t callback, void *user) {
|
|
||||||
uiLoadingTransition(0.0f, 1.0f, callback, user);
|
|
||||||
uiFullboxTransition(
|
|
||||||
&UI_FULLBOX_OVER,
|
|
||||||
COLOR_TRANSPARENT_BLACK,
|
|
||||||
COLOR_BLACK,
|
|
||||||
UI_LOADING_FADE_DURATION,
|
|
||||||
EASING_LINEAR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiLoadingHide(eventcallback_t callback, void *user) {
|
|
||||||
uiLoadingTransition(1.0f, 0.0f, callback, user);
|
|
||||||
uiFullboxTransition(
|
|
||||||
&UI_FULLBOX_OVER,
|
|
||||||
COLOR_BLACK,
|
|
||||||
COLOR_TRANSPARENT_BLACK,
|
|
||||||
UI_LOADING_FADE_DURATION,
|
|
||||||
EASING_LINEAR
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "event/event.h"
|
|
||||||
#include "ui/widget/uilabel.h"
|
|
||||||
|
|
||||||
#define UI_LOADING_FADE_DURATION 0.5f
|
|
||||||
#define UI_LOADING_MARGIN 8.0f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
float_t fromAlpha;
|
|
||||||
float_t toAlpha;
|
|
||||||
float_t duration;
|
|
||||||
float_t time;
|
|
||||||
eventcallback_t onTransitionEndCallbacks[4];
|
|
||||||
void *onTransitionEndUsers[4];
|
|
||||||
event_t onTransitionEnd;
|
|
||||||
uilabel_t textLabel;
|
|
||||||
} uiloading_t;
|
|
||||||
|
|
||||||
extern uiloading_t UI_LOADING;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the loading indicator.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiLoadingInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances the loading indicator fade transition. Fires onTransitionEnd once
|
|
||||||
* when the transition completes.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiLoadingUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the loading indicator. No-op when fully transparent.
|
|
||||||
*
|
|
||||||
* @return Error state.
|
|
||||||
*/
|
|
||||||
errorret_t uiLoadingDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fades the loading indicator in. Invokes callback when fully visible.
|
|
||||||
*
|
|
||||||
* @param callback Called when the fade-in completes. May be NULL.
|
|
||||||
* @param user Forwarded to the callback unchanged.
|
|
||||||
*/
|
|
||||||
void uiLoadingShow(eventcallback_t callback, void *user);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fades the loading indicator out. Invokes callback when fully hidden.
|
|
||||||
*
|
|
||||||
* @param callback Called when the fade-out completes. May be NULL.
|
|
||||||
* @param user Forwarded to the callback unchanged.
|
|
||||||
*/
|
|
||||||
void uiLoadingHide(eventcallback_t callback, void *user);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uitransition.c
|
|
||||||
uitransitionfade.c
|
|
||||||
)
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitransition.h"
|
|
||||||
#include "uitransitionfade.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "util/math.h"
|
|
||||||
|
|
||||||
uitransition_t UI_TRANSITION;
|
|
||||||
|
|
||||||
uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT] = {
|
|
||||||
[UI_TRANSITION_TYPE_FADE] = uiTransitionFadeDraw
|
|
||||||
};
|
|
||||||
|
|
||||||
errorret_t uiTransitionInit(void) {
|
|
||||||
memoryZero(&UI_TRANSITION, sizeof(uitransition_t));
|
|
||||||
|
|
||||||
// uiTransitionStart((uitransitionstartparams_t){
|
|
||||||
// .duration = 5.0f,
|
|
||||||
// .type = UI_TRANSITION_TYPE_FADE,
|
|
||||||
// .params = {
|
|
||||||
// .fade = {
|
|
||||||
// .easing = EASING_OUT_QUART,
|
|
||||||
// .fromColor = COLOR_TRANSPARENT_BLACK,
|
|
||||||
// .toColor = COLOR_BLACK
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// .finished = NULL,
|
|
||||||
// .user = NULL
|
|
||||||
// });
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTransitionUpdate(void) {
|
|
||||||
// Update time.
|
|
||||||
float_t newTime = UI_TRANSITION.data.time + TIME.delta;
|
|
||||||
|
|
||||||
UI_TRANSITION.data.time = mathClamp(
|
|
||||||
newTime,
|
|
||||||
0.0f,
|
|
||||||
UI_TRANSITION.data.duration
|
|
||||||
);
|
|
||||||
UI_TRANSITION.data.t = (
|
|
||||||
(UI_TRANSITION.data.duration <= 0.0f) ?
|
|
||||||
1.0f :
|
|
||||||
(UI_TRANSITION.data.time / UI_TRANSITION.data.duration)
|
|
||||||
);
|
|
||||||
|
|
||||||
if(UI_TRANSITION.data.t >= 1.0f && UI_TRANSITION.finished) {
|
|
||||||
UI_TRANSITION.finished(UI_TRANSITION.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTransitionDraw(void) {
|
|
||||||
uitransitiondrawfunc_t draw = UI_TRANSITION_TYPE_DRAW[UI_TRANSITION.type];
|
|
||||||
if(!draw) errorOk();
|
|
||||||
return draw(&UI_TRANSITION.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTransitionStart(const uitransitionstartparams_t info) {
|
|
||||||
assertTrue(info.type > UI_TRANSITION_TYPE_NULL, "Invalid type");
|
|
||||||
assertTrue(info.type < UI_TRANSITION_TYPE_COUNT, "Invalid type");
|
|
||||||
assertTrue(info.duration >= 0.0f, "Duration must be non-negative");
|
|
||||||
|
|
||||||
memoryZero(&UI_TRANSITION, sizeof(uitransition_t));
|
|
||||||
|
|
||||||
UI_TRANSITION.data.duration = info.duration;
|
|
||||||
UI_TRANSITION.type = info.type;
|
|
||||||
UI_TRANSITION.data.params = info.params;
|
|
||||||
UI_TRANSITION.finished = info.finished;
|
|
||||||
UI_TRANSITION.user = info.user;
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "animation/easing.h"
|
|
||||||
#include "uitransitiondata.h"
|
|
||||||
|
|
||||||
typedef void (*uitransitioncallback_t)(void *user);
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UI_TRANSITION_TYPE_NULL,
|
|
||||||
|
|
||||||
UI_TRANSITION_TYPE_FADE,
|
|
||||||
|
|
||||||
UI_TRANSITION_TYPE_COUNT
|
|
||||||
} uitransitiontype_t;
|
|
||||||
|
|
||||||
extern uitransitiondrawfunc_t UI_TRANSITION_TYPE_DRAW[UI_TRANSITION_TYPE_COUNT];
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uitransitiondata_t data;
|
|
||||||
uitransitiontype_t type;
|
|
||||||
uitransitioncallback_t finished;
|
|
||||||
void *user;
|
|
||||||
} uitransition_t;
|
|
||||||
|
|
||||||
extern uitransition_t UI_TRANSITION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the transition overlay, zeroing all fields and wiring the
|
|
||||||
* onTransitionEnd event.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTransitionInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances the transition. Fires onTransitionEnd once when it completes.
|
|
||||||
* Safe to call when no transition is running.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTransitionUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the transition overlay. Skipped entirely when the current alpha
|
|
||||||
* is zero.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTransitionDraw(void);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
float_t duration;
|
|
||||||
uitransitiontype_t type;
|
|
||||||
uitransitionparams_t params;
|
|
||||||
uitransitioncallback_t finished;
|
|
||||||
void *user;
|
|
||||||
} uitransitionstartparams_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a transition described by an info struct.
|
|
||||||
*
|
|
||||||
* @param info Transition parameters.
|
|
||||||
*/
|
|
||||||
void uiTransitionStart(const uitransitionstartparams_t info);
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "uitransitionfade.h"
|
|
||||||
|
|
||||||
typedef union uitransitionparams_u {
|
|
||||||
uitransitionfade_t fade;
|
|
||||||
} uitransitionparams_t;
|
|
||||||
|
|
||||||
typedef struct uitransitiondata_s {
|
|
||||||
float_t time;
|
|
||||||
float_t duration;
|
|
||||||
float_t t;
|
|
||||||
uitransitionparams_t params;
|
|
||||||
} uitransitiondata_t;
|
|
||||||
|
|
||||||
typedef errorret_t (*uitransitiondrawfunc_t)(const uitransitiondata_t *data);
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitransition.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
|
|
||||||
errorret_t uiTransitionFadeDraw(const uitransitiondata_t *data) {
|
|
||||||
assertNotNull(data, "data must not be NULL");
|
|
||||||
|
|
||||||
float_t e = easingApply(data->params.fade.easing, data->t);
|
|
||||||
|
|
||||||
float_t r = (float_t)(
|
|
||||||
data->params.fade.toColor.r - data->params.fade.fromColor.r
|
|
||||||
) * e;
|
|
||||||
float_t g = (float_t)(
|
|
||||||
data->params.fade.toColor.g - data->params.fade.fromColor.g
|
|
||||||
) * e;
|
|
||||||
float_t b = (float_t)(
|
|
||||||
data->params.fade.toColor.b - data->params.fade.fromColor.b
|
|
||||||
) * e;
|
|
||||||
float_t a = (float_t)(
|
|
||||||
data->params.fade.toColor.a - data->params.fade.fromColor.a
|
|
||||||
) * e;
|
|
||||||
|
|
||||||
color_t color = color4b(
|
|
||||||
data->params.fade.fromColor.r + (uint8_t)r,
|
|
||||||
data->params.fade.fromColor.g + (uint8_t)g,
|
|
||||||
data->params.fade.fromColor.b + (uint8_t)b,
|
|
||||||
data->params.fade.fromColor.a + (uint8_t)a
|
|
||||||
);
|
|
||||||
|
|
||||||
if(color.a == 0) errorOk();
|
|
||||||
|
|
||||||
spritebatchsprite_t sprite = {
|
|
||||||
.min = { 0.0f, 0.0f, 0.0f },
|
|
||||||
.max = { (float_t)SCREEN.width, (float_t)SCREEN.height, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = color,
|
|
||||||
.texture = NULL
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "animation/easing.h"
|
|
||||||
|
|
||||||
typedef struct uitransitiondata_s uitransitiondata_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
color_t fromColor;
|
|
||||||
color_t toColor;
|
|
||||||
easingtype_t easing;
|
|
||||||
} uitransitionfade_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a full-screen color overlay interpolated between fade->fromColor
|
|
||||||
* and fade->toColor using fade->easing. t is the raw normalized progress
|
|
||||||
* in [0, 1] before easing is applied. No-op when the resulting alpha is zero.
|
|
||||||
*
|
|
||||||
* @param fade The fade parameters.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTransitionFadeDraw(const uitransitiondata_t *fade);
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "ui.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "ui/uielement.h"
|
|
||||||
#include "ui/focus/uifocus.h"
|
|
||||||
|
|
||||||
ui_t UI;
|
|
||||||
|
|
||||||
errorret_t uiInit(void) {
|
|
||||||
memoryZero(&UI, sizeof(ui_t));
|
|
||||||
uiFocusInit();
|
|
||||||
uiElementsSort();
|
|
||||||
|
|
||||||
uielement_t *element = &UI_ELEMENTS[0];
|
|
||||||
while(!uiElementIsNull(element)) {
|
|
||||||
errorChain(uiElementInit(element));
|
|
||||||
element++;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiUpdate(void) {
|
|
||||||
uiFocusUpdate();
|
|
||||||
|
|
||||||
uielement_t *element = &UI_ELEMENTS[0];
|
|
||||||
while(!uiElementIsNull(element)) {
|
|
||||||
errorChain(uiElementUpdate(element));
|
|
||||||
element++;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiRender(void) {
|
|
||||||
const uielement_t *element = &UI_ELEMENTS[0];
|
|
||||||
while(!uiElementIsNull(element)) {
|
|
||||||
errorChain(uiElementDraw(element));
|
|
||||||
element++;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(spriteBatchFlush());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiDispose(void) {
|
|
||||||
uielement_t *element = &UI_ELEMENTS[0];
|
|
||||||
while(!uiElementIsNull(element)) {
|
|
||||||
errorChain(uiElementDispose(element));
|
|
||||||
element++;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
void *nothing;
|
|
||||||
} ui_t;
|
|
||||||
|
|
||||||
extern ui_t UI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the UI system.
|
|
||||||
*/
|
|
||||||
errorret_t uiInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the UI system.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the UI system.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiRender(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of the UI system.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiDispose(void);
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uielement.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/sort.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
#include "ui/debug/uifps.h"
|
|
||||||
#include "engine/engine.h"
|
|
||||||
#include "ui/overlay/uifullbox.h"
|
|
||||||
#include "ui/overlay/uiloading.h"
|
|
||||||
#include "ui/overlay/uicrop.h"
|
|
||||||
#include "ui/transition/uitransition.h"
|
|
||||||
#include "ui/debug/uiconsole.h"
|
|
||||||
#include "ui/frame/uiconfirm.h"
|
|
||||||
|
|
||||||
// Priming pass: X does nothing here, so this only exists to process any
|
|
||||||
// #include directives nested in uielementlist.h/ui/uielementgame.h at
|
|
||||||
// file scope (each such header's own #pragma once makes it a no-op on
|
|
||||||
// the real pass below, which happens inside UI_ELEMENTS[]'s braces,
|
|
||||||
// where a raw #include of a declaration would be invalid).
|
|
||||||
#define X(initFn, updateFn, drawFn, disposeFn, order) // do nothing
|
|
||||||
#include "uielementlist.h"
|
|
||||||
#undef X
|
|
||||||
|
|
||||||
uielement_t UI_ELEMENTS[] = {
|
|
||||||
#define X(initFn, updateFn, drawFn, disposeFn, elementOrder) \
|
|
||||||
{ \
|
|
||||||
.init = initFn, .update = updateFn, .draw = drawFn, \
|
|
||||||
.dispose = disposeFn, .order = elementOrder \
|
|
||||||
},
|
|
||||||
#include "uielementlist.h"
|
|
||||||
#undef X
|
|
||||||
|
|
||||||
{ 0 } // Null terminator
|
|
||||||
};
|
|
||||||
|
|
||||||
bool_t uiElementIsNull(const uielement_t *element) {
|
|
||||||
return element->init == NULL &&
|
|
||||||
element->update == NULL &&
|
|
||||||
element->draw == NULL &&
|
|
||||||
element->dispose == NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int_t uiElementCompareOrder(const void *a, const void *b) {
|
|
||||||
const uielement_t *elementA = (const uielement_t *)a;
|
|
||||||
const uielement_t *elementB = (const uielement_t *)b;
|
|
||||||
if(elementA->order < elementB->order) return -1;
|
|
||||||
if(elementA->order > elementB->order) return 1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiElementsSort(void) {
|
|
||||||
// The trailing null terminator is always the last static entry -- sort
|
|
||||||
// everything before it, and leave it in place.
|
|
||||||
const size_t count = (sizeof(UI_ELEMENTS) / sizeof(UI_ELEMENTS[0])) - 1;
|
|
||||||
sortBubble(UI_ELEMENTS, count, sizeof(uielement_t), uiElementCompareOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiElementInit(uielement_t *element) {
|
|
||||||
assertNotNull(element, "element must not be NULL");
|
|
||||||
if(element->init != NULL) errorChain(element->init());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiElementUpdate(uielement_t *element) {
|
|
||||||
assertNotNull(element, "element must not be NULL");
|
|
||||||
if(element->update != NULL) errorChain(element->update());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiElementDraw(const uielement_t *element) {
|
|
||||||
assertNotNull(element, "element must not be NULL");
|
|
||||||
if(element->draw != NULL) errorChain(element->draw());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiElementDispose(uielement_t *element) {
|
|
||||||
assertNotNull(element, "element must not be NULL");
|
|
||||||
if(element->dispose != NULL) errorChain(element->dispose());
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
|
|
||||||
// Built-in order tiers. Lower values update/render first; ties preserve
|
|
||||||
// their relative UI_ELEMENTS declaration order (uiElementsSort uses a
|
|
||||||
// stable sort). Game-specific elements can use any int32_t value -- these
|
|
||||||
// are just the ones the engine itself relies on.
|
|
||||||
#define UI_ELEMENT_ORDER_DEFAULT 0
|
|
||||||
#define UI_ELEMENT_ORDER_DEBUG 1000
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
errorret_t (*init)();
|
|
||||||
errorret_t (*update)();
|
|
||||||
errorret_t (*draw)();
|
|
||||||
errorret_t (*dispose)();
|
|
||||||
int32_t order;
|
|
||||||
} uielement_t;
|
|
||||||
|
|
||||||
extern uielement_t UI_ELEMENTS[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when all four callbacks on the element are NULL,
|
|
||||||
* which marks the end of the UI_ELEMENTS array.
|
|
||||||
*
|
|
||||||
* @param element The element to test.
|
|
||||||
* @returns True if the element is the null terminator.
|
|
||||||
*/
|
|
||||||
bool_t uiElementIsNull(const uielement_t *element);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two elements by their .order field, ascending. Matches
|
|
||||||
* sortcompare_t, for use with the project's sort utilities.
|
|
||||||
*
|
|
||||||
* @param a First uielement_t to compare.
|
|
||||||
* @param b Second uielement_t to compare.
|
|
||||||
* @return Negative if a < b, zero if a == b, positive if a > b.
|
|
||||||
*/
|
|
||||||
int_t uiElementCompareOrder(const void *a, const void *b);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stably sorts UI_ELEMENTS in place by .order, ascending. The trailing
|
|
||||||
* null terminator is never moved. Called once by uiInit -- element order
|
|
||||||
* is static after that, so there's no need to re-sort every frame.
|
|
||||||
*/
|
|
||||||
void uiElementsSort(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a UI element, invoking its init callback if set.
|
|
||||||
*
|
|
||||||
* @param element The element to initialize.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiElementInit(uielement_t *element);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a UI element, calling its update callback if set.
|
|
||||||
*
|
|
||||||
* @param element The element to update.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiElementUpdate(uielement_t *element);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a UI element, calling its draw callback if set.
|
|
||||||
*
|
|
||||||
* @param element The element to render.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiElementDraw(const uielement_t *element);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes of a UI element, invoking its dispose callback if set.
|
|
||||||
*
|
|
||||||
* @param element The element to dispose.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiElementDispose(uielement_t *element);
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
// X(init, update, draw, dispose, order) -- pass NULL for any callback the
|
|
||||||
// element doesn't need. order controls update/draw sequence (lower first,
|
|
||||||
// see UI_ELEMENT_ORDER_* in uielement.h); ties preserve declaration order.
|
|
||||||
// See uielement.c for how this expands.
|
|
||||||
|
|
||||||
X(uiFrameInit, NULL, NULL, uiFrameDispose, UI_ELEMENT_ORDER_DEFAULT)
|
|
||||||
|
|
||||||
// Fullbox under: above scene, below system UI.
|
|
||||||
X(
|
|
||||||
uiFullboxUnderInit, uiFullboxUnderUpdate, uiFullboxUnderDraw, NULL,
|
|
||||||
UI_ELEMENT_ORDER_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
// Text stuffs
|
|
||||||
X(uiConfirmInit, NULL, uiConfirmDraw, uiConfirmDispose, UI_ELEMENT_ORDER_DEFAULT)
|
|
||||||
|
|
||||||
X(
|
|
||||||
uiTransitionInit, uiTransitionUpdate, uiTransitionDraw, NULL,
|
|
||||||
UI_ELEMENT_ORDER_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fullbox over: above absolutely everything (except debug).
|
|
||||||
X(
|
|
||||||
uiFullboxOverInit, uiFullboxOverUpdate, uiFullboxOverDraw, NULL,
|
|
||||||
UI_ELEMENT_ORDER_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
X(
|
|
||||||
uiLoadingInit, uiLoadingUpdate, uiLoadingDraw, NULL,
|
|
||||||
UI_ELEMENT_ORDER_DEFAULT
|
|
||||||
)
|
|
||||||
|
|
||||||
X(uiCropInit, NULL, uiCropDraw, NULL, UI_ELEMENT_ORDER_DEFAULT)
|
|
||||||
|
|
||||||
// Debug items -- always last.
|
|
||||||
X(NULL, NULL, uiConsoleDraw, uiConsoleDispose, UI_ELEMENT_ORDER_DEBUG)
|
|
||||||
X(uiFPSInit, NULL, uiFPSDraw, NULL, UI_ELEMENT_ORDER_DEBUG)
|
|
||||||
|
|
||||||
// Game-specific UI elements (duskrpg)
|
|
||||||
#include "ui/uielementgame.h"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uibutton.c
|
|
||||||
uicheckbox.c
|
|
||||||
uitab.c
|
|
||||||
uislider.c
|
|
||||||
uidropdown.c
|
|
||||||
uiscrolling.c
|
|
||||||
uimenu.c
|
|
||||||
uilabel.c
|
|
||||||
uiwidgetlabel.c
|
|
||||||
)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uibutton.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
void uiButtonInit(uibutton_t *button, const char_t *label) {
|
|
||||||
assertNotNull(button, "Button cannot be NULL");
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
uiWidgetLabelInit(&button->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&button->label, label);
|
|
||||||
button->highlighted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiButtonIsHighlighted(const uibutton_t *button) {
|
|
||||||
assertNotNull(button, "Button cannot be NULL");
|
|
||||||
return button->highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted) {
|
|
||||||
assertNotNull(button, "Button cannot be NULL");
|
|
||||||
button->highlighted = highlighted;
|
|
||||||
uiWidgetLabelSetColor(
|
|
||||||
&button->label, highlighted ? COLOR_RED : COLOR_WHITE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiButtonDraw(
|
|
||||||
const uibutton_t *button,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(button, "Button cannot be NULL");
|
|
||||||
errorChain(uiWidgetLabelDraw(&button->label, x, y));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
bool_t highlighted;
|
|
||||||
} uibutton_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a button.
|
|
||||||
*
|
|
||||||
* @param button The button to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
*/
|
|
||||||
void uiButtonInit(uibutton_t *button, const char_t *label);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the button is highlighted.
|
|
||||||
*
|
|
||||||
* @param button The button to query.
|
|
||||||
* @returns True if highlighted.
|
|
||||||
*/
|
|
||||||
bool_t uiButtonIsHighlighted(const uibutton_t *button);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the highlighted state of the button.
|
|
||||||
*
|
|
||||||
* @param button The button to update.
|
|
||||||
* @param highlighted The new highlighted state.
|
|
||||||
*/
|
|
||||||
void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the button at the given screen position.
|
|
||||||
*
|
|
||||||
* @param button The button to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiButtonDraw(
|
|
||||||
const uibutton_t *button,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uicheckbox.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
void uiCheckboxInit(
|
|
||||||
uicheckbox_t *checkbox,
|
|
||||||
const char_t *label
|
|
||||||
) {
|
|
||||||
memoryZero(checkbox, sizeof(uicheckbox_t));
|
|
||||||
checkbox->rawLabel = label;
|
|
||||||
uiWidgetLabelInit(&checkbox->label, &FONT_DEFAULT);
|
|
||||||
uiCheckboxRebuildLabel(checkbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox) {
|
|
||||||
return checkbox->checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked) {
|
|
||||||
checkbox->checked = checked;
|
|
||||||
uiCheckboxRebuildLabel(checkbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiCheckboxToggle(uicheckbox_t *checkbox) {
|
|
||||||
uiCheckboxSetChecked(checkbox, !uiCheckboxIsChecked(checkbox));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox) {
|
|
||||||
return checkbox->highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, const bool_t highlighted) {
|
|
||||||
checkbox->highlighted = highlighted;
|
|
||||||
uiWidgetLabelSetColor(
|
|
||||||
&checkbox->label, highlighted ? COLOR_RED : COLOR_WHITE
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiCheckboxDraw(
|
|
||||||
const uicheckbox_t *checkbox,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
errorChain(uiWidgetLabelDraw(&checkbox->label, x, y));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiCheckboxRebuildLabel(uicheckbox_t *checkbox) {
|
|
||||||
char_t combined[UI_WIDGET_LABEL_TEXT_MAX];
|
|
||||||
stringFormat(
|
|
||||||
combined, UI_WIDGET_LABEL_TEXT_MAX - 1, "%s %s",
|
|
||||||
checkbox->checked ? "Y" : "N", checkbox->rawLabel
|
|
||||||
);
|
|
||||||
uiWidgetLabelSetText(&checkbox->label, combined);
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
|
|
||||||
typedef struct uicheckbox_s {
|
|
||||||
const char_t *rawLabel;
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
bool_t checked;
|
|
||||||
bool_t highlighted;
|
|
||||||
} uicheckbox_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a checkbox.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
*/
|
|
||||||
void uiCheckboxInit(
|
|
||||||
uicheckbox_t *checkbox,
|
|
||||||
const char_t *label
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the checkbox is checked.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to query.
|
|
||||||
* @returns True if checked.
|
|
||||||
*/
|
|
||||||
bool_t uiCheckboxIsChecked(const uicheckbox_t *checkbox);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the checked state of the checkbox.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to update.
|
|
||||||
* @param checked The new checked state.
|
|
||||||
*/
|
|
||||||
void uiCheckboxSetChecked(uicheckbox_t *checkbox, const bool_t checked);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the checked state of the checkbox.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to toggle.
|
|
||||||
*/
|
|
||||||
void uiCheckboxToggle(uicheckbox_t *checkbox);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the checkbox is highlighted.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to query.
|
|
||||||
* @returns True if highlighted.
|
|
||||||
*/
|
|
||||||
bool_t uiCheckboxIsHighlighted(const uicheckbox_t *checkbox);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the highlighted state of the checkbox.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to update.
|
|
||||||
* @param highlighted The new highlighted state.
|
|
||||||
*/
|
|
||||||
void uiCheckboxSetHighlighted(uicheckbox_t *checkbox, const bool_t highlighted);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the checkbox at the given screen position.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiCheckboxDraw(
|
|
||||||
const uicheckbox_t *checkbox,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the checkbox's cached label ("Y "/"N " mark plus rawLabel)
|
|
||||||
* from its current checked state. Called internally whenever checked
|
|
||||||
* changes.
|
|
||||||
*
|
|
||||||
* @param checkbox The checkbox to update.
|
|
||||||
*/
|
|
||||||
void uiCheckboxRebuildLabel(uicheckbox_t *checkbox);
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uidropdown.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
void uiDropdownInit(
|
|
||||||
uidropdown_t *dropdown,
|
|
||||||
const char_t *label,
|
|
||||||
const char_t *const *options,
|
|
||||||
const uint8_t optionCount,
|
|
||||||
const uint8_t selectedIndex
|
|
||||||
) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(options, "Options cannot be NULL");
|
|
||||||
assertTrue(optionCount > 0, "Dropdown must have at least one option");
|
|
||||||
|
|
||||||
memoryZero(dropdown, sizeof(uidropdown_t));
|
|
||||||
uiWidgetLabelInit(&dropdown->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&dropdown->label, label);
|
|
||||||
uiWidgetLabelInit(&dropdown->value, &FONT_DEFAULT);
|
|
||||||
dropdown->options = options;
|
|
||||||
dropdown->optionCount = optionCount;
|
|
||||||
dropdown->selectedIndex = selectedIndex < optionCount ? selectedIndex : 0;
|
|
||||||
uiDropdownRebuildValue(dropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
return dropdown->selectedIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char_t *uiDropdownGetSelectedOption(const uidropdown_t *dropdown) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
return dropdown->options[dropdown->selectedIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiDropdownSetSelectedIndex(uidropdown_t *dropdown, const uint8_t index) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
assertTrue(index < dropdown->optionCount, "Dropdown index out of range");
|
|
||||||
dropdown->selectedIndex = index;
|
|
||||||
uiDropdownRebuildValue(dropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiDropdownStepNext(uidropdown_t *dropdown) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
dropdown->selectedIndex =
|
|
||||||
(dropdown->selectedIndex + 1) % dropdown->optionCount;
|
|
||||||
uiDropdownRebuildValue(dropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiDropdownStepPrev(uidropdown_t *dropdown) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
dropdown->selectedIndex = dropdown->selectedIndex == 0 ?
|
|
||||||
dropdown->optionCount - 1 : dropdown->selectedIndex - 1;
|
|
||||||
uiDropdownRebuildValue(dropdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiDropdownIsHighlighted(const uidropdown_t *dropdown) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
return dropdown->highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiDropdownSetHighlighted(
|
|
||||||
uidropdown_t *dropdown,
|
|
||||||
const bool_t highlighted
|
|
||||||
) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
dropdown->highlighted = highlighted;
|
|
||||||
color_t color = highlighted ? COLOR_RED : COLOR_WHITE;
|
|
||||||
uiWidgetLabelSetColor(&dropdown->label, color);
|
|
||||||
uiWidgetLabelSetColor(&dropdown->value, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiDropdownDraw(
|
|
||||||
const uidropdown_t *dropdown,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(dropdown, "Dropdown cannot be NULL");
|
|
||||||
|
|
||||||
errorChain(uiWidgetLabelDraw(&dropdown->label, x, y));
|
|
||||||
|
|
||||||
int32_t labelW, labelH;
|
|
||||||
uiWidgetLabelGetSize(&dropdown->label, &labelW, &labelH);
|
|
||||||
|
|
||||||
errorChain(uiWidgetLabelDraw(
|
|
||||||
&dropdown->value, x + (float_t)labelW + UI_DROPDOWN_GAP, y
|
|
||||||
));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiDropdownRebuildValue(uidropdown_t *dropdown) {
|
|
||||||
char_t valueText[UI_WIDGET_LABEL_TEXT_MAX];
|
|
||||||
stringFormat(
|
|
||||||
valueText, UI_WIDGET_LABEL_TEXT_MAX - 1, "< %s >",
|
|
||||||
uiDropdownGetSelectedOption(dropdown)
|
|
||||||
);
|
|
||||||
uiWidgetLabelSetText(&dropdown->value, valueText);
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
|
|
||||||
#define UI_DROPDOWN_GAP 4.0f
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
uiwidgetlabel_t value;
|
|
||||||
const char_t *const *options;
|
|
||||||
uint8_t optionCount;
|
|
||||||
uint8_t selectedIndex;
|
|
||||||
bool_t highlighted;
|
|
||||||
} uidropdown_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a dropdown.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
* @param options Array of option display strings; caller-owned, must
|
|
||||||
* outlive the dropdown.
|
|
||||||
* @param optionCount Number of entries in options. Must be > 0.
|
|
||||||
* @param selectedIndex Initial selected option index, clamped to
|
|
||||||
* [0, optionCount - 1].
|
|
||||||
*/
|
|
||||||
void uiDropdownInit(
|
|
||||||
uidropdown_t *dropdown,
|
|
||||||
const char_t *label,
|
|
||||||
const char_t *const *options,
|
|
||||||
const uint8_t optionCount,
|
|
||||||
const uint8_t selectedIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the currently selected option.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to query.
|
|
||||||
* @returns The selected option index.
|
|
||||||
*/
|
|
||||||
uint8_t uiDropdownGetSelectedIndex(const uidropdown_t *dropdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently selected option's display string.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to query.
|
|
||||||
* @returns The selected option string.
|
|
||||||
*/
|
|
||||||
const char_t *uiDropdownGetSelectedOption(const uidropdown_t *dropdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the selected option by index.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to update.
|
|
||||||
* @param index The new selected option index. Must be < optionCount.
|
|
||||||
*/
|
|
||||||
void uiDropdownSetSelectedIndex(uidropdown_t *dropdown, const uint8_t index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects the next option, wrapping around to the first option past
|
|
||||||
* the last.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to update.
|
|
||||||
*/
|
|
||||||
void uiDropdownStepNext(uidropdown_t *dropdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects the previous option, wrapping around to the last option
|
|
||||||
* before the first.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to update.
|
|
||||||
*/
|
|
||||||
void uiDropdownStepPrev(uidropdown_t *dropdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the dropdown is highlighted.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to query.
|
|
||||||
* @returns True if highlighted.
|
|
||||||
*/
|
|
||||||
bool_t uiDropdownIsHighlighted(const uidropdown_t *dropdown);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the highlighted state of the dropdown.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to update.
|
|
||||||
* @param highlighted The new highlighted state.
|
|
||||||
*/
|
|
||||||
void uiDropdownSetHighlighted(
|
|
||||||
uidropdown_t *dropdown,
|
|
||||||
const bool_t highlighted
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the dropdown at the given screen position: label, then the
|
|
||||||
* currently selected option surrounded by cycle arrows.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiDropdownDraw(
|
|
||||||
const uidropdown_t *dropdown,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the dropdown's cached value label ("< option >") from its
|
|
||||||
* current selectedIndex. Called internally whenever selectedIndex
|
|
||||||
* changes.
|
|
||||||
*
|
|
||||||
* @param dropdown The dropdown to update.
|
|
||||||
*/
|
|
||||||
void uiDropdownRebuildValue(uidropdown_t *dropdown);
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uilabel.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
|
|
||||||
void uiLabelInit(uilabel_t *label, font_t *font) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(font, "Font cannot be NULL");
|
|
||||||
|
|
||||||
memoryZero(label, sizeof(uilabel_t));
|
|
||||||
label->font = font;
|
|
||||||
label->color = COLOR_WHITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiLabelSetText(uilabel_t *label, const char_t *text) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(text, "Text cannot be NULL");
|
|
||||||
assertStrLenMax(text, UI_LABEL_TEXT_MAX, "Label text too long");
|
|
||||||
|
|
||||||
stringCopy(label->text, text, UI_LABEL_TEXT_MAX);
|
|
||||||
label->spriteCount = textBuildSpriteCache(
|
|
||||||
label->text, label->font, label->sprites, UI_LABEL_SPRITE_COUNT_MAX,
|
|
||||||
&label->width, &label->height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiLabelSetColor(uilabel_t *label, const color_t color) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
label->color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiLabelGetSize(
|
|
||||||
const uilabel_t *label,
|
|
||||||
int32_t *outWidth,
|
|
||||||
int32_t *outHeight
|
|
||||||
) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(outWidth, "Output width cannot be NULL");
|
|
||||||
assertNotNull(outHeight, "Output height cannot be NULL");
|
|
||||||
*outWidth = label->width;
|
|
||||||
*outHeight = label->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiLabelDraw(
|
|
||||||
const uilabel_t *label,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
if(label->spriteCount == 0) errorOk();
|
|
||||||
|
|
||||||
// Cached sprites are relative to (0,0); textDrawSpriteCache translates
|
|
||||||
// into the requested screen position here instead of in
|
|
||||||
// uiLabelSetText, so a label can be repositioned every frame without
|
|
||||||
// rebuilding the (much more expensive) glyph/UV cache.
|
|
||||||
spritebatchsprite_t scratch[UI_LABEL_SPRITE_COUNT_MAX];
|
|
||||||
errorChain(textDrawSpriteCache(
|
|
||||||
label->sprites, label->spriteCount, scratch, x, y, label->color,
|
|
||||||
label->font->texture
|
|
||||||
));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/text/font.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
#define UI_LABEL_TEXT_MAX 256
|
|
||||||
#define UI_LABEL_SPRITE_COUNT_MAX UI_LABEL_TEXT_MAX
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char_t text[UI_LABEL_TEXT_MAX];
|
|
||||||
color_t color;
|
|
||||||
font_t *font;
|
|
||||||
|
|
||||||
spritebatchsprite_t sprites[UI_LABEL_SPRITE_COUNT_MAX];
|
|
||||||
uint32_t spriteCount;
|
|
||||||
int32_t width;
|
|
||||||
int32_t height;
|
|
||||||
} uilabel_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a label, defaulting to white text and no text set.
|
|
||||||
*
|
|
||||||
* @param label The label to initialize.
|
|
||||||
* @param font Font to use for rendering. Must outlive the label.
|
|
||||||
*/
|
|
||||||
void uiLabelInit(uilabel_t *label, font_t *font);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the label's text, rebuilding its cached sprites (glyph lookup, UV,
|
|
||||||
* and layout) immediately. This is the only way the sprite cache gets
|
|
||||||
* rebuilt -- uiLabelDraw never recomputes it, so call this whenever the
|
|
||||||
* text changes rather than once up front and expecting it to stay in sync.
|
|
||||||
*
|
|
||||||
* @param label The label to update.
|
|
||||||
* @param text Null-terminated string to display. Must be shorter than
|
|
||||||
* UI_LABEL_TEXT_MAX.
|
|
||||||
*/
|
|
||||||
void uiLabelSetText(uilabel_t *label, const char_t *text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the label's tint color. Cheap -- doesn't touch the sprite cache,
|
|
||||||
* since color is applied via the draw material, not baked into sprites.
|
|
||||||
*
|
|
||||||
* @param label The label to update.
|
|
||||||
* @param color The new tint color.
|
|
||||||
*/
|
|
||||||
void uiLabelSetColor(uilabel_t *label, const color_t color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the measured size (in pixels) of the label's current text, cached
|
|
||||||
* from the last uiLabelSetText call.
|
|
||||||
*
|
|
||||||
* @param label The label to query.
|
|
||||||
* @param outWidth Pointer to store the width.
|
|
||||||
* @param outHeight Pointer to store the height.
|
|
||||||
*/
|
|
||||||
void uiLabelGetSize(
|
|
||||||
const uilabel_t *label,
|
|
||||||
int32_t *outWidth,
|
|
||||||
int32_t *outHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the label's cached sprites at the given screen position in a
|
|
||||||
* single batched buffer call. Does not recompute glyph layout -- call
|
|
||||||
* uiLabelSetText first whenever the text changes.
|
|
||||||
*
|
|
||||||
* @param label The label to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiLabelDraw(
|
|
||||||
const uilabel_t *label,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uimenu.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
#include "ui/widget/uibutton.h"
|
|
||||||
#include "ui/widget/uitab.h"
|
|
||||||
#include "ui/widget/uislider.h"
|
|
||||||
#include "ui/widget/uidropdown.h"
|
|
||||||
|
|
||||||
void uiMenuInit(
|
|
||||||
uimenu_t *menu,
|
|
||||||
uimenuselectedcallback_t selected,
|
|
||||||
uimenuclosedcallback_t closed,
|
|
||||||
uimenuchangedcallback_t changed
|
|
||||||
) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
memoryZero(menu, sizeof(uimenu_t));
|
|
||||||
menu->selected = selected;
|
|
||||||
menu->closed = closed;
|
|
||||||
menu->changed = changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuSetItems(
|
|
||||||
uimenu_t *menu,
|
|
||||||
const uimenuitem_t *items,
|
|
||||||
const uint8_t itemCount,
|
|
||||||
const uint8_t columns
|
|
||||||
) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
assertNotNull(items, "Items cannot be NULL");
|
|
||||||
assertTrue(itemCount > 0, "Item count must be > 0");
|
|
||||||
assertTrue(columns > 0, "Columns must be > 0");
|
|
||||||
menu->items = (uimenuitem_t *)items;
|
|
||||||
menu->itemCount = itemCount;
|
|
||||||
menu->columns = columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
if(menu->focusItem == NULL) return;
|
|
||||||
uiFocusSetPosition(menu->focusItem, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuOpen(uimenu_t *menu) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
assertNotNull(menu->items, "Menu items cannot be NULL");
|
|
||||||
assertTrue(menu->itemCount > 0, "Menu item count must be > 0");
|
|
||||||
assertTrue(menu->columns > 0, "Menu columns must be > 0");
|
|
||||||
if(menu->focusItem != NULL) return;
|
|
||||||
|
|
||||||
uint8_t focusable = uiMenuFocusableCount(menu);
|
|
||||||
uint8_t rows = focusable > 0 ?
|
|
||||||
(focusable + menu->columns - 1) / menu->columns : 1;
|
|
||||||
menu->focusItem = uiFocusPush(
|
|
||||||
menu->columns, rows,
|
|
||||||
uiMenuFocusSelected,
|
|
||||||
uiMenuFocusChanged,
|
|
||||||
uiMenuFocusClosed,
|
|
||||||
uiMenuFocusDirection,
|
|
||||||
menu
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuClose(uimenu_t *menu) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
if(menu->focusItem == NULL) return;
|
|
||||||
uiFocusPopItem(menu->focusItem);
|
|
||||||
menu->focusItem = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiMenuIsActive(const uimenu_t *menu) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
return menu->focusItem != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiMenuDraw(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
if(menu->itemCount == 0) errorOk();
|
|
||||||
|
|
||||||
float_t colStep = width / (float_t)menu->columns;
|
|
||||||
float_t rowHeight = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
|
|
||||||
uint8_t col = 0;
|
|
||||||
uint8_t row = 0;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < menu->itemCount; i++) {
|
|
||||||
const uimenuitem_t *item = &menu->items[i];
|
|
||||||
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_LABEL) {
|
|
||||||
if(col > 0) { row++; col = 0; }
|
|
||||||
errorChain(uiWidgetLabelDraw(
|
|
||||||
&item->label, x, y + (float_t)row * rowHeight
|
|
||||||
));
|
|
||||||
row++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_SPACER) {
|
|
||||||
if(col > 0) { row++; col = 0; }
|
|
||||||
row++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t ix = x + (float_t)col * colStep;
|
|
||||||
float_t iy = y + (float_t)row * rowHeight;
|
|
||||||
|
|
||||||
switch(item->type) {
|
|
||||||
case UI_MENU_WIDGET_TYPE_CHECKBOX:
|
|
||||||
errorChain(uiCheckboxDraw(&item->checkbox, ix, iy));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UI_MENU_WIDGET_TYPE_BUTTON:
|
|
||||||
errorChain(uiButtonDraw(&item->button, ix, iy));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UI_MENU_WIDGET_TYPE_TAB:
|
|
||||||
errorChain(uiTabDraw(&item->tab, ix, iy));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UI_MENU_WIDGET_TYPE_SLIDER:
|
|
||||||
errorChain(uiSliderDraw(&item->slider, ix, iy));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case UI_MENU_WIDGET_TYPE_DROPDOWN:
|
|
||||||
errorChain(uiDropdownDraw(&item->dropdown, ix, iy));
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
col++;
|
|
||||||
if(col >= menu->columns) { col = 0; row++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem) {
|
|
||||||
assertNotNull(focusItem, "Focus item cannot be NULL");
|
|
||||||
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
|
|
||||||
uimenu_t *menu = (uimenu_t *)focusItem->user;
|
|
||||||
if(menu->selected == NULL) return true;
|
|
||||||
|
|
||||||
uint8_t slot = focusItem->y * menu->columns + focusItem->x;
|
|
||||||
uint8_t index = uiMenuFocusSlotToIndex(menu, slot);
|
|
||||||
if(index == 0xFF) return true;
|
|
||||||
|
|
||||||
menu->selected(menu, index, &menu->items[index]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem) {
|
|
||||||
assertNotNull(focusItem, "Focus item cannot be NULL");
|
|
||||||
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
|
|
||||||
uimenu_t *menu = (uimenu_t *)focusItem->user;
|
|
||||||
|
|
||||||
uint8_t focusSlot = focusItem->y * menu->columns + focusItem->x;
|
|
||||||
uint8_t slot = 0;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < menu->itemCount; i++) {
|
|
||||||
uimenuitem_t *item = &menu->items[i];
|
|
||||||
if(
|
|
||||||
item->type == UI_MENU_WIDGET_TYPE_LABEL ||
|
|
||||||
item->type == UI_MENU_WIDGET_TYPE_SPACER
|
|
||||||
) continue;
|
|
||||||
uiMenuItemSetHighlighted(item, slot == focusSlot);
|
|
||||||
slot++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(menu->changed == NULL) return true;
|
|
||||||
uint8_t index = uiMenuFocusSlotToIndex(menu, focusSlot);
|
|
||||||
if(index == 0xFF) return true;
|
|
||||||
|
|
||||||
menu->changed(menu, index, &menu->items[index]);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem) {
|
|
||||||
assertNotNull(focusItem, "Focus item cannot be NULL");
|
|
||||||
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
|
|
||||||
uimenu_t *menu = (uimenu_t *)focusItem->user;
|
|
||||||
menu->focusItem = NULL;
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < menu->itemCount; i++) {
|
|
||||||
uiMenuItemSetHighlighted(&menu->items[i], false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(menu->closed != NULL) menu->closed(menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiMenuFocusDirection(
|
|
||||||
const uifocusitem_t *focusItem,
|
|
||||||
const uifocusdirection_t direction
|
|
||||||
) {
|
|
||||||
assertNotNull(focusItem, "Focus item cannot be NULL");
|
|
||||||
assertNotNull(focusItem->user, "Focus item user cannot be NULL");
|
|
||||||
if(direction != UI_FOCUS_DIRECTION_LEFT && direction != UI_FOCUS_DIRECTION_RIGHT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uimenu_t *menu = (uimenu_t *)focusItem->user;
|
|
||||||
uint8_t slot = focusItem->y * menu->columns + focusItem->x;
|
|
||||||
uint8_t index = uiMenuFocusSlotToIndex(menu, slot);
|
|
||||||
if(index == 0xFF) return false;
|
|
||||||
|
|
||||||
uimenuitem_t *item = &menu->items[index];
|
|
||||||
bool_t right = direction == UI_FOCUS_DIRECTION_RIGHT;
|
|
||||||
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) {
|
|
||||||
if(right) {
|
|
||||||
uiSliderStepUp(&item->slider);
|
|
||||||
} else {
|
|
||||||
uiSliderStepDown(&item->slider);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
|
|
||||||
uiCheckboxToggle(&item->checkbox);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_DROPDOWN) {
|
|
||||||
if(right) {
|
|
||||||
uiDropdownStepNext(&item->dropdown);
|
|
||||||
} else {
|
|
||||||
uiDropdownStepPrev(&item->dropdown);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t uiMenuFocusableCount(const uimenu_t *menu) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
uint8_t count = 0;
|
|
||||||
for(uint8_t i = 0; i < menu->itemCount; i++) {
|
|
||||||
uimenuwidgettype_t t = menu->items[i].type;
|
|
||||||
if(t != UI_MENU_WIDGET_TYPE_LABEL && t != UI_MENU_WIDGET_TYPE_SPACER) count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot) {
|
|
||||||
assertNotNull(menu, "Menu cannot be NULL");
|
|
||||||
uint8_t focusable = 0;
|
|
||||||
for(uint8_t i = 0; i < menu->itemCount; i++) {
|
|
||||||
uimenuwidgettype_t t = menu->items[i].type;
|
|
||||||
if(t == UI_MENU_WIDGET_TYPE_LABEL || t == UI_MENU_WIDGET_TYPE_SPACER) continue;
|
|
||||||
if(focusable == slot) return i;
|
|
||||||
focusable++;
|
|
||||||
}
|
|
||||||
return 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuLabelInit(uimenuitem_t *item, const char_t *text) {
|
|
||||||
assertNotNull(item, "Item cannot be NULL");
|
|
||||||
assertNotNull(text, "Text cannot be NULL");
|
|
||||||
uiWidgetLabelInit(&item->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&item->label, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted) {
|
|
||||||
if(item->type == UI_MENU_WIDGET_TYPE_CHECKBOX) {
|
|
||||||
uiCheckboxSetHighlighted(&item->checkbox, highlighted);
|
|
||||||
} else if(item->type == UI_MENU_WIDGET_TYPE_BUTTON) {
|
|
||||||
uiButtonSetHighlighted(&item->button, highlighted);
|
|
||||||
} else if(item->type == UI_MENU_WIDGET_TYPE_TAB) {
|
|
||||||
uiTabSetActive(&item->tab, highlighted);
|
|
||||||
} else if(item->type == UI_MENU_WIDGET_TYPE_SLIDER) {
|
|
||||||
uiSliderSetHighlighted(&item->slider, highlighted);
|
|
||||||
} else if(item->type == UI_MENU_WIDGET_TYPE_DROPDOWN) {
|
|
||||||
uiDropdownSetHighlighted(&item->dropdown, highlighted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/focus/uifocus.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
#include "ui/widget/uibutton.h"
|
|
||||||
#include "ui/widget/uicheckbox.h"
|
|
||||||
#include "ui/widget/uitab.h"
|
|
||||||
#include "ui/widget/uislider.h"
|
|
||||||
#include "ui/widget/uidropdown.h"
|
|
||||||
|
|
||||||
typedef struct uimenu_s uimenu_t;
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UI_MENU_WIDGET_TYPE_NONE,
|
|
||||||
UI_MENU_WIDGET_TYPE_LABEL,
|
|
||||||
UI_MENU_WIDGET_TYPE_SPACER,
|
|
||||||
UI_MENU_WIDGET_TYPE_CHECKBOX,
|
|
||||||
UI_MENU_WIDGET_TYPE_BUTTON,
|
|
||||||
UI_MENU_WIDGET_TYPE_TAB,
|
|
||||||
UI_MENU_WIDGET_TYPE_SLIDER,
|
|
||||||
UI_MENU_WIDGET_TYPE_DROPDOWN,
|
|
||||||
} uimenuwidgettype_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uimenuwidgettype_t type;
|
|
||||||
union {
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
uicheckbox_t checkbox;
|
|
||||||
uibutton_t button;
|
|
||||||
uitab_t tab;
|
|
||||||
uislider_t slider;
|
|
||||||
uidropdown_t dropdown;
|
|
||||||
};
|
|
||||||
} uimenuitem_t;
|
|
||||||
|
|
||||||
typedef void (*uimenuselectedcallback_t)(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef void (*uimenuchangedcallback_t)(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const uint8_t index,
|
|
||||||
const uimenuitem_t *item
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef void (*uimenuclosedcallback_t)(const uimenu_t *menu);
|
|
||||||
|
|
||||||
typedef struct uimenu_s {
|
|
||||||
uimenuitem_t *items;
|
|
||||||
uint8_t itemCount;
|
|
||||||
uint8_t columns;
|
|
||||||
uifocusitem_t *focusItem;
|
|
||||||
|
|
||||||
uimenuselectedcallback_t selected;
|
|
||||||
uimenuclosedcallback_t closed;
|
|
||||||
uimenuchangedcallback_t changed;
|
|
||||||
|
|
||||||
void *user;
|
|
||||||
} uimenu_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a menu, clearing all items and focus state.
|
|
||||||
*
|
|
||||||
* @param menu The menu to initialize.
|
|
||||||
* @param items The list of items to display in the menu.
|
|
||||||
* @param itemCount The number of items in the list.
|
|
||||||
* @param columns The number of columns to display the items in.
|
|
||||||
* @param selected The callback to invoke when an item is selected.
|
|
||||||
* @param closed The callback to invoke when the menu is closed.
|
|
||||||
* @param changed The callback to invoke when the menu changes.
|
|
||||||
*/
|
|
||||||
void uiMenuInit(
|
|
||||||
uimenu_t *menu,
|
|
||||||
uimenuselectedcallback_t selected,
|
|
||||||
uimenuclosedcallback_t closed,
|
|
||||||
uimenuchangedcallback_t changed
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the items to display in the menu.
|
|
||||||
*
|
|
||||||
* @param menu The menu to update.
|
|
||||||
* @param items The list of items to display in the menu.
|
|
||||||
* @param itemCount The number of items in the list.
|
|
||||||
* @param columns The number of columns to display the items in.
|
|
||||||
*/
|
|
||||||
void uiMenuSetItems(
|
|
||||||
uimenu_t *menu,
|
|
||||||
const uimenuitem_t *items,
|
|
||||||
const uint8_t itemCount,
|
|
||||||
const uint8_t columns
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the position of the menu on the screen.
|
|
||||||
*
|
|
||||||
* @param menu The menu to position.
|
|
||||||
* @param x The x-coordinate to position the menu at.
|
|
||||||
* @param y The y-coordinate to position the menu at.
|
|
||||||
*/
|
|
||||||
void uiMenuSetPosition(uimenu_t *menu, const uint8_t x, const uint8_t y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a menu onto the UI focus stack, making it the active menu.
|
|
||||||
*
|
|
||||||
* @param menu The menu to push.
|
|
||||||
*/
|
|
||||||
void uiMenuOpen(uimenu_t *menu);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pops a menu from the UI focus stack, removing it from the active menu.
|
|
||||||
*
|
|
||||||
* @param menu The menu to pop.
|
|
||||||
*/
|
|
||||||
void uiMenuClose(uimenu_t *menu);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the menu is currently active (on the UI focus stack).
|
|
||||||
*
|
|
||||||
* @param menu The menu to query.
|
|
||||||
* @returns True if the menu is active.
|
|
||||||
*/
|
|
||||||
bool_t uiMenuIsActive(const uimenu_t *menu);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the menu at the specified position and size.
|
|
||||||
*
|
|
||||||
* @param menu The menu to draw.
|
|
||||||
* @param x The x-coordinate to draw the menu at.
|
|
||||||
* @param y The y-coordinate to draw the menu at.
|
|
||||||
* @param width The width of the menu.
|
|
||||||
* @param height The height of the menu.
|
|
||||||
* @returns An error code indicating success or failure.
|
|
||||||
*/
|
|
||||||
errorret_t uiMenuDraw(
|
|
||||||
const uimenu_t *menu,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of focusable (non-label) items in the menu.
|
|
||||||
*
|
|
||||||
* @param menu The menu to query.
|
|
||||||
* @returns Count of non-label items.
|
|
||||||
*/
|
|
||||||
uint8_t uiMenuFocusableCount(const uimenu_t *menu);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a flat focus slot index to the corresponding item array index,
|
|
||||||
* skipping over label items which are not focusable.
|
|
||||||
*
|
|
||||||
* @param menu The menu to query.
|
|
||||||
* @param slot The focus slot index (y * columns + x).
|
|
||||||
* @returns The item array index, or 0xFF if out of range.
|
|
||||||
*/
|
|
||||||
uint8_t uiMenuFocusSlotToIndex(const uimenu_t *menu, const uint8_t slot);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the highlighted/active state on a menu item, dispatching to the
|
|
||||||
* matching widget's setter based on its type. No-op for label/spacer
|
|
||||||
* items and any other type with no highlight concept.
|
|
||||||
*
|
|
||||||
* @param item The item to update.
|
|
||||||
* @param highlighted The new highlighted/active state.
|
|
||||||
*/
|
|
||||||
void uiMenuItemSetHighlighted(uimenuitem_t *item, const bool_t highlighted);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a menu item as a standalone label, caching its text.
|
|
||||||
* Used internally by the MENU_LABEL helper macro.
|
|
||||||
*
|
|
||||||
* @param item The item to initialize.
|
|
||||||
* @param text Display text.
|
|
||||||
*/
|
|
||||||
void uiMenuLabelInit(uimenuitem_t *item, const char_t *text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - forwards selection to the menu's selected handler.
|
|
||||||
*
|
|
||||||
* @param focusItem The active focus item; user field must point to uimenu_t.
|
|
||||||
* @returns True.
|
|
||||||
*/
|
|
||||||
bool_t uiMenuFocusSelected(const uifocusitem_t *focusItem);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - updates widget highlights and fires changed.
|
|
||||||
*
|
|
||||||
* @param focusItem The active focus item; user field must point to uimenu_t.
|
|
||||||
* @returns True.
|
|
||||||
*/
|
|
||||||
bool_t uiMenuFocusChanged(const uifocusitem_t *focusItem);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - clears focusItem and fires the closed handler.
|
|
||||||
*
|
|
||||||
* @param focusItem The active focus item; user field must point to uimenu_t.
|
|
||||||
* @returns True.
|
|
||||||
*/
|
|
||||||
bool_t uiMenuFocusClosed(const uifocusitem_t *focusItem);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - gives the focused item's widget a chance to
|
|
||||||
* handle LEFT/RIGHT itself (e.g. a slider adjusting its value) before
|
|
||||||
* falling back to the default cell-to-cell movement.
|
|
||||||
*
|
|
||||||
* @param focusItem The active focus item; user field must point to uimenu_t.
|
|
||||||
* @param direction The direction that was pressed or held.
|
|
||||||
* @returns True if the focused widget handled the direction.
|
|
||||||
*/
|
|
||||||
bool_t uiMenuFocusDirection(
|
|
||||||
const uifocusitem_t *focusItem,
|
|
||||||
const uifocusdirection_t direction
|
|
||||||
);
|
|
||||||
|
|
||||||
// Helper macros
|
|
||||||
#define MENU_BEGIN(menuPtr, itemsArray, selected, closed, changed) \
|
|
||||||
uimenu_t *menu = (menuPtr); \
|
|
||||||
uiMenuInit(menu, selected, closed, changed); \
|
|
||||||
menu->items = (itemsArray); \
|
|
||||||
uint8_t menuIndex = 0; \
|
|
||||||
uint8_t menuCapacity = sizeof(itemsArray) / sizeof((itemsArray)[0])
|
|
||||||
|
|
||||||
#define MENU_LABEL(text) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_LABEL; \
|
|
||||||
uiMenuLabelInit(&menu->items[menuIndex], text); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_SPACER() \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SPACER; \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_CHECKBOX(label) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_CHECKBOX; \
|
|
||||||
uiCheckboxInit(&menu->items[menuIndex].checkbox, label); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_BUTTON(label) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_BUTTON; \
|
|
||||||
uiButtonInit(&menu->items[menuIndex].button, label); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_TAB(label) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_TAB; \
|
|
||||||
uiTabInit(&menu->items[menuIndex].tab, label); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_SLIDER_FLOAT(label, value, min, max, step) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \
|
|
||||||
uiSliderInitFloat( \
|
|
||||||
&menu->items[menuIndex].slider, label, value, min, max, step \
|
|
||||||
); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_SLIDER_INT(label, value, min, max, step) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_SLIDER; \
|
|
||||||
uiSliderInitInt( \
|
|
||||||
&menu->items[menuIndex].slider, label, value, min, max, step \
|
|
||||||
); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_DROPDOWN(label, options, optionCount, selectedIndex) \
|
|
||||||
assertTrue(menuIndex < menuCapacity, "Menu item count exceeds capacity"); \
|
|
||||||
menu->items[menuIndex].type = UI_MENU_WIDGET_TYPE_DROPDOWN; \
|
|
||||||
uiDropdownInit( \
|
|
||||||
&menu->items[menuIndex].dropdown, label, options, optionCount, \
|
|
||||||
selectedIndex \
|
|
||||||
); \
|
|
||||||
++menuIndex
|
|
||||||
|
|
||||||
#define MENU_END(itemsArray, columns) \
|
|
||||||
uiMenuSetItems(menu, (itemsArray), menuIndex, (columns))
|
|
||||||
|
|
||||||
//EOF
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiscrolling.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
void uiScrollingInit(uiscrolling_t *scrolling) {
|
|
||||||
assertNotNull(scrolling, "Scrolling container cannot be NULL");
|
|
||||||
// Nothing to initialize yet -- uiscrolling_t is currently a
|
|
||||||
// placeholder with no fields.
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
|
|
||||||
} uiscrolling_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a scrolling container. Currently a placeholder -- no
|
|
||||||
* scrolling behavior is implemented yet.
|
|
||||||
*
|
|
||||||
* @param scrolling The scrolling container to initialize.
|
|
||||||
*/
|
|
||||||
void uiScrollingInit(uiscrolling_t *scrolling);
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uislider.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/math.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
|
|
||||||
void uiSliderInitFloat(
|
|
||||||
uislider_t *slider,
|
|
||||||
const char_t *label,
|
|
||||||
const float_t value,
|
|
||||||
const float_t min,
|
|
||||||
const float_t max,
|
|
||||||
const float_t step
|
|
||||||
) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertTrue(max > min, "Slider max must be greater than min");
|
|
||||||
assertTrue(step > 0.0f, "Slider step must be greater than zero");
|
|
||||||
|
|
||||||
memoryZero(slider, sizeof(uislider_t));
|
|
||||||
uiWidgetLabelInit(&slider->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&slider->label, label);
|
|
||||||
uiWidgetLabelInit(&slider->valueLabel, &FONT_DEFAULT);
|
|
||||||
slider->type = UI_SLIDER_TYPE_FLOAT;
|
|
||||||
slider->min.f = min;
|
|
||||||
slider->max.f = max;
|
|
||||||
slider->step.f = step;
|
|
||||||
slider->value.f = mathClamp(value, min, max);
|
|
||||||
uiSliderRebuildValueLabel(slider);
|
|
||||||
uiSliderRebuildGeometry(slider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderInitInt(
|
|
||||||
uislider_t *slider,
|
|
||||||
const char_t *label,
|
|
||||||
const int32_t value,
|
|
||||||
const int32_t min,
|
|
||||||
const int32_t max,
|
|
||||||
const int32_t step
|
|
||||||
) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertTrue(max > min, "Slider max must be greater than min");
|
|
||||||
assertTrue(step > 0, "Slider step must be greater than zero");
|
|
||||||
|
|
||||||
memoryZero(slider, sizeof(uislider_t));
|
|
||||||
uiWidgetLabelInit(&slider->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&slider->label, label);
|
|
||||||
uiWidgetLabelInit(&slider->valueLabel, &FONT_DEFAULT);
|
|
||||||
slider->type = UI_SLIDER_TYPE_INT;
|
|
||||||
slider->min.i = min;
|
|
||||||
slider->max.i = max;
|
|
||||||
slider->step.i = step;
|
|
||||||
slider->value.i = mathClamp(value, min, max);
|
|
||||||
uiSliderRebuildValueLabel(slider);
|
|
||||||
uiSliderRebuildGeometry(slider);
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t uiSliderGetFloat(const uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
return slider->type == UI_SLIDER_TYPE_INT ?
|
|
||||||
(float_t)slider->value.i : slider->value.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t uiSliderGetInt(const uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
assertTrue(
|
|
||||||
slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider"
|
|
||||||
);
|
|
||||||
return slider->value.i;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderSetFloat(uislider_t *slider, const float_t value) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
assertTrue(
|
|
||||||
slider->type == UI_SLIDER_TYPE_FLOAT, "Slider is not a float slider"
|
|
||||||
);
|
|
||||||
slider->value.f = mathClamp(value, slider->min.f, slider->max.f);
|
|
||||||
uiSliderRebuildValueLabel(slider);
|
|
||||||
uiSliderRebuildGeometry(slider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderSetInt(uislider_t *slider, const int32_t value) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
assertTrue(
|
|
||||||
slider->type == UI_SLIDER_TYPE_INT, "Slider is not an int slider"
|
|
||||||
);
|
|
||||||
slider->value.i = mathClamp(value, slider->min.i, slider->max.i);
|
|
||||||
uiSliderRebuildValueLabel(slider);
|
|
||||||
uiSliderRebuildGeometry(slider);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderStepUp(uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
if(slider->type == UI_SLIDER_TYPE_INT) {
|
|
||||||
int32_t next = slider->value.i + slider->step.i;
|
|
||||||
uiSliderSetInt(slider, next > slider->max.i ? slider->min.i : next);
|
|
||||||
} else {
|
|
||||||
float_t next = slider->value.f + slider->step.f;
|
|
||||||
uiSliderSetFloat(slider, next > slider->max.f ? slider->min.f : next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderStepDown(uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
if(slider->type == UI_SLIDER_TYPE_INT) {
|
|
||||||
int32_t next = slider->value.i - slider->step.i;
|
|
||||||
uiSliderSetInt(slider, next < slider->min.i ? slider->max.i : next);
|
|
||||||
} else {
|
|
||||||
float_t next = slider->value.f - slider->step.f;
|
|
||||||
uiSliderSetFloat(slider, next < slider->min.f ? slider->max.f : next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float_t uiSliderGetRatio(const uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
if(slider->type == UI_SLIDER_TYPE_INT) {
|
|
||||||
if(slider->max.i == slider->min.i) return 0.0f;
|
|
||||||
return (float_t)(slider->value.i - slider->min.i) /
|
|
||||||
(float_t)(slider->max.i - slider->min.i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(slider->max.f == slider->min.f) return 0.0f;
|
|
||||||
return (slider->value.f - slider->min.f) / (slider->max.f - slider->min.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t uiSliderGetStepCount(const uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
if(slider->type != UI_SLIDER_TYPE_INT || slider->step.i <= 0) return 0;
|
|
||||||
return (slider->max.i - slider->min.i) / slider->step.i;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiSliderIsHighlighted(const uislider_t *slider) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
return slider->highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
slider->highlighted = highlighted;
|
|
||||||
color_t color = highlighted ? COLOR_RED : COLOR_WHITE;
|
|
||||||
uiWidgetLabelSetColor(&slider->label, color);
|
|
||||||
uiWidgetLabelSetColor(&slider->valueLabel, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiSliderDraw(
|
|
||||||
const uislider_t *slider,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(slider, "Slider cannot be NULL");
|
|
||||||
|
|
||||||
errorChain(uiWidgetLabelDraw(&slider->label, x, y));
|
|
||||||
|
|
||||||
shadermaterial_t trackMaterial = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_DARK_GRAY,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
spritebatchsprite_t track = spriteBatchSpriteTranslate(
|
|
||||||
&slider->cachedTrack, x, y
|
|
||||||
);
|
|
||||||
errorChain(spriteBatchBuffer(&track, 1, &SHADER_UNLIT, trackMaterial));
|
|
||||||
|
|
||||||
if(slider->cachedFillVisible) {
|
|
||||||
shadermaterial_t fillMaterial = {
|
|
||||||
.unlit = {
|
|
||||||
.color = slider->highlighted ? COLOR_RED : COLOR_WHITE,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
spritebatchsprite_t fill = spriteBatchSpriteTranslate(
|
|
||||||
&slider->cachedFill, x, y
|
|
||||||
);
|
|
||||||
errorChain(spriteBatchBuffer(&fill, 1, &SHADER_UNLIT, fillMaterial));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(slider->cachedMarkerCount > 0) {
|
|
||||||
spritebatchsprite_t markers[UI_SLIDER_STEP_MARKERS_MAX];
|
|
||||||
for(int32_t i = 0; i < slider->cachedMarkerCount; i++) {
|
|
||||||
markers[i] = spriteBatchSpriteTranslate(&slider->cachedMarkers[i], x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
shadermaterial_t markerMaterial = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_WHITE,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(
|
|
||||||
markers, slider->cachedMarkerCount, &SHADER_UNLIT, markerMaterial
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(uiWidgetLabelDraw(
|
|
||||||
&slider->valueLabel,
|
|
||||||
x + slider->cachedTrack.max[0] + UI_SLIDER_GAP,
|
|
||||||
y
|
|
||||||
));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderRebuildValueLabel(uislider_t *slider) {
|
|
||||||
char_t valueText[UI_WIDGET_LABEL_TEXT_MAX];
|
|
||||||
if(slider->type == UI_SLIDER_TYPE_INT) {
|
|
||||||
stringFormat(
|
|
||||||
valueText, UI_WIDGET_LABEL_TEXT_MAX - 1, "%d", slider->value.i
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
stringFormat(
|
|
||||||
valueText, UI_WIDGET_LABEL_TEXT_MAX - 1, "%.2f", slider->value.f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
uiWidgetLabelSetText(&slider->valueLabel, valueText);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiSliderRebuildGeometry(uislider_t *slider) {
|
|
||||||
int32_t labelW, labelH;
|
|
||||||
uiWidgetLabelGetSize(&slider->label, &labelW, &labelH);
|
|
||||||
|
|
||||||
float_t trackX = (float_t)labelW + UI_SLIDER_GAP;
|
|
||||||
float_t trackY = ((float_t)labelH - UI_SLIDER_TRACK_HEIGHT) * 0.5f;
|
|
||||||
|
|
||||||
slider->cachedTrack = (spritebatchsprite_t){
|
|
||||||
.min = { trackX, trackY, 0.0f },
|
|
||||||
.max = {
|
|
||||||
trackX + UI_SLIDER_TRACK_WIDTH, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f
|
|
||||||
},
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
|
|
||||||
float_t fillWidth = UI_SLIDER_TRACK_WIDTH * uiSliderGetRatio(slider);
|
|
||||||
slider->cachedFillVisible = fillWidth > 0.0f;
|
|
||||||
if(slider->cachedFillVisible) {
|
|
||||||
slider->cachedFill = (spritebatchsprite_t){
|
|
||||||
.min = { trackX, trackY, 0.0f },
|
|
||||||
.max = { trackX + fillWidth, trackY + UI_SLIDER_TRACK_HEIGHT, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t stepCount = uiSliderGetStepCount(slider);
|
|
||||||
if(stepCount > 0 && stepCount < UI_SLIDER_STEP_MARKERS_MAX) {
|
|
||||||
for(int32_t i = 0; i <= stepCount; i++) {
|
|
||||||
float_t markerX = trackX +
|
|
||||||
UI_SLIDER_TRACK_WIDTH * (float_t)i / (float_t)stepCount;
|
|
||||||
slider->cachedMarkers[i] = (spritebatchsprite_t){
|
|
||||||
.min = {
|
|
||||||
markerX - UI_SLIDER_STEP_MARKER_WIDTH * 0.5f,
|
|
||||||
trackY - UI_SLIDER_STEP_MARKER_OVERHANG,
|
|
||||||
0.0f
|
|
||||||
},
|
|
||||||
.max = {
|
|
||||||
markerX + UI_SLIDER_STEP_MARKER_WIDTH * 0.5f,
|
|
||||||
trackY + UI_SLIDER_TRACK_HEIGHT + UI_SLIDER_STEP_MARKER_OVERHANG,
|
|
||||||
0.0f
|
|
||||||
},
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
slider->cachedMarkerCount = stepCount + 1;
|
|
||||||
} else {
|
|
||||||
slider->cachedMarkerCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
|
|
||||||
#define UI_SLIDER_TRACK_WIDTH 80.0f
|
|
||||||
#define UI_SLIDER_TRACK_HEIGHT 4.0f
|
|
||||||
#define UI_SLIDER_STEP_MARKER_WIDTH 2.0f
|
|
||||||
#define UI_SLIDER_STEP_MARKER_OVERHANG 1.0f
|
|
||||||
#define UI_SLIDER_GAP 4.0f
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of discrete steps below which an int slider renders individual
|
|
||||||
* step markers on its track instead of a smooth fill.
|
|
||||||
*/
|
|
||||||
#define UI_SLIDER_STEP_MARKERS_MAX 10
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UI_SLIDER_TYPE_FLOAT,
|
|
||||||
UI_SLIDER_TYPE_INT,
|
|
||||||
|
|
||||||
UI_SLIDER_TYPE_COUNT
|
|
||||||
} uislidertype_t;
|
|
||||||
|
|
||||||
typedef union {
|
|
||||||
float_t f;
|
|
||||||
int32_t i;
|
|
||||||
} uislidervalue_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
uiwidgetlabel_t valueLabel;
|
|
||||||
uislidertype_t type;
|
|
||||||
uislidervalue_t value;
|
|
||||||
uislidervalue_t min;
|
|
||||||
uislidervalue_t max;
|
|
||||||
uislidervalue_t step;
|
|
||||||
bool_t highlighted;
|
|
||||||
|
|
||||||
// Track/fill/marker geometry, cached relative to origin (0,0) and only
|
|
||||||
// rebuilt when the value or step configuration changes (see
|
|
||||||
// uiSliderRebuildGeometry) -- uiSliderDraw just translates these into
|
|
||||||
// position instead of re-deriving them every frame.
|
|
||||||
spritebatchsprite_t cachedTrack;
|
|
||||||
spritebatchsprite_t cachedFill;
|
|
||||||
bool_t cachedFillVisible;
|
|
||||||
spritebatchsprite_t cachedMarkers[UI_SLIDER_STEP_MARKERS_MAX];
|
|
||||||
int32_t cachedMarkerCount;
|
|
||||||
} uislider_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a floating point slider.
|
|
||||||
*
|
|
||||||
* @param slider The slider to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
* @param value Initial value, clamped to [min, max].
|
|
||||||
* @param min Minimum value.
|
|
||||||
* @param max Maximum value.
|
|
||||||
* @param step The amount a single step moves the value by.
|
|
||||||
*/
|
|
||||||
void uiSliderInitFloat(
|
|
||||||
uislider_t *slider,
|
|
||||||
const char_t *label,
|
|
||||||
const float_t value,
|
|
||||||
const float_t min,
|
|
||||||
const float_t max,
|
|
||||||
const float_t step
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an integer slider.
|
|
||||||
*
|
|
||||||
* @param slider The slider to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
* @param value Initial value, clamped to [min, max].
|
|
||||||
* @param min Minimum value.
|
|
||||||
* @param max Maximum value.
|
|
||||||
* @param step The amount a single step moves the value by.
|
|
||||||
*/
|
|
||||||
void uiSliderInitInt(
|
|
||||||
uislider_t *slider,
|
|
||||||
const char_t *label,
|
|
||||||
const int32_t value,
|
|
||||||
const int32_t min,
|
|
||||||
const int32_t max,
|
|
||||||
const int32_t step
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the slider's current value as a float. Works for both slider
|
|
||||||
* types; int values are widened to float.
|
|
||||||
*
|
|
||||||
* @param slider The slider to query.
|
|
||||||
* @returns The current value.
|
|
||||||
*/
|
|
||||||
float_t uiSliderGetFloat(const uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the slider's current value as an int. Only valid for
|
|
||||||
* UI_SLIDER_TYPE_INT sliders.
|
|
||||||
*
|
|
||||||
* @param slider The slider to query.
|
|
||||||
* @returns The current value.
|
|
||||||
*/
|
|
||||||
int32_t uiSliderGetInt(const uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the slider's value, clamped to [min, max]. Only valid for
|
|
||||||
* UI_SLIDER_TYPE_FLOAT sliders.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
* @param value The new value.
|
|
||||||
*/
|
|
||||||
void uiSliderSetFloat(uislider_t *slider, const float_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the slider's value, clamped to [min, max]. Only valid for
|
|
||||||
* UI_SLIDER_TYPE_INT sliders.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
* @param value The new value.
|
|
||||||
*/
|
|
||||||
void uiSliderSetInt(uislider_t *slider, const int32_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the slider's value up by one step, wrapping around to its min
|
|
||||||
* if the step would exceed its max.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
*/
|
|
||||||
void uiSliderStepUp(uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves the slider's value down by one step, wrapping around to its
|
|
||||||
* max if the step would go below its min.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
*/
|
|
||||||
void uiSliderStepDown(uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the slider's current value normalized to a 0..1 range based
|
|
||||||
* on its min/max.
|
|
||||||
*
|
|
||||||
* @param slider The slider to query.
|
|
||||||
* @returns The normalized value.
|
|
||||||
*/
|
|
||||||
float_t uiSliderGetRatio(const uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of discrete steps between min and max. Always 0
|
|
||||||
* for float sliders.
|
|
||||||
*
|
|
||||||
* @param slider The slider to query.
|
|
||||||
* @returns The step count.
|
|
||||||
*/
|
|
||||||
int32_t uiSliderGetStepCount(const uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the slider is highlighted.
|
|
||||||
*
|
|
||||||
* @param slider The slider to query.
|
|
||||||
* @returns True if highlighted.
|
|
||||||
*/
|
|
||||||
bool_t uiSliderIsHighlighted(const uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the highlighted state of the slider.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
* @param highlighted The new highlighted state.
|
|
||||||
*/
|
|
||||||
void uiSliderSetHighlighted(uislider_t *slider, const bool_t highlighted);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the slider at the given screen position: label, then track,
|
|
||||||
* then the current value as text. Int sliders with fewer than
|
|
||||||
* UI_SLIDER_STEP_MARKERS_MAX discrete steps render a marker per step
|
|
||||||
* instead of a plain track.
|
|
||||||
*
|
|
||||||
* @param slider The slider to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiSliderDraw(
|
|
||||||
const uislider_t *slider,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the slider's cached value label from its current value.
|
|
||||||
* Called internally whenever value changes.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
*/
|
|
||||||
void uiSliderRebuildValueLabel(uislider_t *slider);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the slider's cached track/fill/marker geometry (relative to
|
|
||||||
* origin (0,0)) from its current label size, value, and step
|
|
||||||
* configuration. Called internally on init and whenever the value
|
|
||||||
* changes -- uiSliderDraw never recomputes this itself.
|
|
||||||
*
|
|
||||||
* @param slider The slider to update.
|
|
||||||
*/
|
|
||||||
void uiSliderRebuildGeometry(uislider_t *slider);
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitab.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/texture/texture.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
|
|
||||||
void uiTabInit(uitab_t *tab, const char_t *label) {
|
|
||||||
assertNotNull(tab, "Tab cannot be NULL");
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
uiWidgetLabelInit(&tab->label, &FONT_DEFAULT);
|
|
||||||
uiWidgetLabelSetText(&tab->label, label);
|
|
||||||
tab->active = false;
|
|
||||||
uiTabRebuildBackground(tab);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTabIsActive(const uitab_t *tab) {
|
|
||||||
assertNotNull(tab, "Tab cannot be NULL");
|
|
||||||
return tab->active;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTabSetActive(uitab_t *tab, const bool_t active) {
|
|
||||||
assertNotNull(tab, "Tab cannot be NULL");
|
|
||||||
tab->active = active;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTabDraw(
|
|
||||||
const uitab_t *tab,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(tab, "Tab cannot be NULL");
|
|
||||||
|
|
||||||
spritebatchsprite_t sprite = spriteBatchSpriteTranslate(
|
|
||||||
&tab->cachedBackground, x, y
|
|
||||||
);
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = tab->active ? COLOR_GREEN : COLOR_RED,
|
|
||||||
.texture = &TEXTURE_WHITE
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(&sprite, 1, &SHADER_UNLIT, material));
|
|
||||||
|
|
||||||
errorChain(uiWidgetLabelDraw(&tab->label, x, y));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTabRebuildBackground(uitab_t *tab) {
|
|
||||||
assertNotNull(tab, "Tab cannot be NULL");
|
|
||||||
|
|
||||||
int32_t labelW, labelH;
|
|
||||||
uiWidgetLabelGetSize(&tab->label, &labelW, &labelH);
|
|
||||||
tab->cachedBackground = (spritebatchsprite_t){
|
|
||||||
.min = { 0.0f, 0.0f, 0.0f },
|
|
||||||
.max = { (float_t)labelW, (float_t)labelH, 0.0f },
|
|
||||||
.uvMin = { 0.0f, 0.0f },
|
|
||||||
.uvMax = { 1.0f, 1.0f }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/widget/uiwidgetlabel.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uiwidgetlabel_t label;
|
|
||||||
bool_t active;
|
|
||||||
|
|
||||||
// Background quad, cached relative to origin (0,0) from the label's
|
|
||||||
// size at init -- uiTabDraw only translates this into position, since
|
|
||||||
// active/inactive only changes the material color, not the geometry.
|
|
||||||
spritebatchsprite_t cachedBackground;
|
|
||||||
} uitab_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a tab.
|
|
||||||
*
|
|
||||||
* @param tab The tab to initialize.
|
|
||||||
* @param label Display label.
|
|
||||||
*/
|
|
||||||
void uiTabInit(uitab_t *tab, const char_t *label);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the tab is active.
|
|
||||||
*
|
|
||||||
* @param tab The tab to query.
|
|
||||||
* @returns True if active.
|
|
||||||
*/
|
|
||||||
bool_t uiTabIsActive(const uitab_t *tab);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the active state of the tab.
|
|
||||||
*
|
|
||||||
* @param tab The tab to update.
|
|
||||||
* @param active The new active state.
|
|
||||||
*/
|
|
||||||
void uiTabSetActive(uitab_t *tab, const bool_t active);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the tab at the given screen position. Active tabs draw with a
|
|
||||||
* green background; inactive tabs draw with a red background.
|
|
||||||
*
|
|
||||||
* @param tab The tab to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTabDraw(
|
|
||||||
const uitab_t *tab,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the tab's cached background quad (relative to origin (0,0))
|
|
||||||
* from its current label size. Called internally by uiTabInit -- the
|
|
||||||
* background's geometry never changes afterwards, since the tab has no
|
|
||||||
* API to change its label text.
|
|
||||||
*
|
|
||||||
* @param tab The tab to update.
|
|
||||||
*/
|
|
||||||
void uiTabRebuildBackground(uitab_t *tab);
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uiwidgetlabel.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
|
|
||||||
void uiWidgetLabelInit(uiwidgetlabel_t *label, font_t *font) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(font, "Font cannot be NULL");
|
|
||||||
|
|
||||||
memoryZero(label, sizeof(uiwidgetlabel_t));
|
|
||||||
label->font = font;
|
|
||||||
label->color = COLOR_WHITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiWidgetLabelSetText(uiwidgetlabel_t *label, const char_t *text) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(text, "Text cannot be NULL");
|
|
||||||
assertStrLenMax(text, UI_WIDGET_LABEL_TEXT_MAX, "Label text too long");
|
|
||||||
|
|
||||||
stringCopy(label->text, text, UI_WIDGET_LABEL_TEXT_MAX);
|
|
||||||
label->spriteCount = textBuildSpriteCache(
|
|
||||||
label->text, label->font, label->sprites,
|
|
||||||
UI_WIDGET_LABEL_SPRITE_COUNT_MAX, &label->width, &label->height
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiWidgetLabelSetColor(uiwidgetlabel_t *label, const color_t color) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
label->color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiWidgetLabelGetSize(
|
|
||||||
const uiwidgetlabel_t *label,
|
|
||||||
int32_t *outWidth,
|
|
||||||
int32_t *outHeight
|
|
||||||
) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
assertNotNull(outWidth, "Output width cannot be NULL");
|
|
||||||
assertNotNull(outHeight, "Output height cannot be NULL");
|
|
||||||
*outWidth = label->width;
|
|
||||||
*outHeight = label->height;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiWidgetLabelDraw(
|
|
||||||
const uiwidgetlabel_t *label,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
) {
|
|
||||||
assertNotNull(label, "Label cannot be NULL");
|
|
||||||
if(label->spriteCount == 0) errorOk();
|
|
||||||
|
|
||||||
spritebatchsprite_t scratch[UI_WIDGET_LABEL_SPRITE_COUNT_MAX];
|
|
||||||
errorChain(textDrawSpriteCache(
|
|
||||||
label->sprites, label->spriteCount, scratch, x, y, label->color,
|
|
||||||
label->font->texture
|
|
||||||
));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "display/text/font.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
|
|
||||||
// A cached, cheap-to-redraw label sized for embedding directly inside
|
|
||||||
// small widgets (buttons, checkboxes, sliders, ...) rather than
|
|
||||||
// referencing text by pointer and re-deriving glyph geometry every
|
|
||||||
// frame. Capacity is intentionally much smaller than uilabel_t's -- it
|
|
||||||
// gets embedded by value in every widget instance (and inside
|
|
||||||
// uimenuitem_t's union, which is often arrayed), so keeping it small
|
|
||||||
// matters on memory-constrained targets. See uilabel_t for the
|
|
||||||
// general-purpose, larger-capacity equivalent.
|
|
||||||
#define UI_WIDGET_LABEL_TEXT_MAX 64
|
|
||||||
#define UI_WIDGET_LABEL_SPRITE_COUNT_MAX UI_WIDGET_LABEL_TEXT_MAX
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char_t text[UI_WIDGET_LABEL_TEXT_MAX];
|
|
||||||
color_t color;
|
|
||||||
font_t *font;
|
|
||||||
|
|
||||||
spritebatchsprite_t sprites[UI_WIDGET_LABEL_SPRITE_COUNT_MAX];
|
|
||||||
uint32_t spriteCount;
|
|
||||||
int32_t width;
|
|
||||||
int32_t height;
|
|
||||||
} uiwidgetlabel_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a widget label, defaulting to white text and no text set.
|
|
||||||
*
|
|
||||||
* @param label The label to initialize.
|
|
||||||
* @param font Font to use for rendering. Must outlive the label.
|
|
||||||
*/
|
|
||||||
void uiWidgetLabelInit(uiwidgetlabel_t *label, font_t *font);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the label's text, rebuilding its cached sprites immediately.
|
|
||||||
* Call this whenever the displayed text changes -- uiWidgetLabelDraw
|
|
||||||
* never recomputes it.
|
|
||||||
*
|
|
||||||
* @param label The label to update.
|
|
||||||
* @param text Null-terminated string to display. Must be shorter than
|
|
||||||
* UI_WIDGET_LABEL_TEXT_MAX.
|
|
||||||
*/
|
|
||||||
void uiWidgetLabelSetText(uiwidgetlabel_t *label, const char_t *text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the label's tint color. Cheap -- doesn't touch the sprite cache.
|
|
||||||
*
|
|
||||||
* @param label The label to update.
|
|
||||||
* @param color The new tint color.
|
|
||||||
*/
|
|
||||||
void uiWidgetLabelSetColor(uiwidgetlabel_t *label, const color_t color);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the measured size (in pixels) of the label's current text, cached
|
|
||||||
* from the last uiWidgetLabelSetText call.
|
|
||||||
*
|
|
||||||
* @param label The label to query.
|
|
||||||
* @param outWidth Pointer to store the width.
|
|
||||||
* @param outHeight Pointer to store the height.
|
|
||||||
*/
|
|
||||||
void uiWidgetLabelGetSize(
|
|
||||||
const uiwidgetlabel_t *label,
|
|
||||||
int32_t *outWidth,
|
|
||||||
int32_t *outHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the label's cached sprites at the given screen position in a
|
|
||||||
* single batched buffer call. Does not recompute glyph layout.
|
|
||||||
*
|
|
||||||
* @param label The label to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiWidgetLabelDraw(
|
|
||||||
const uiwidgetlabel_t *label,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y
|
|
||||||
);
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
uitextbox.c
|
|
||||||
uitextboxmain.c
|
|
||||||
)
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitextbox.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "util/string.h"
|
|
||||||
#include "time/time.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
|
|
||||||
void uiTextboxInit(
|
|
||||||
uitextbox_t *box,
|
|
||||||
char_t *text,
|
|
||||||
const uint32_t maxLength,
|
|
||||||
uitextboxline_t *lines,
|
|
||||||
const uint32_t linesMax
|
|
||||||
) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
assertNotNull(text, "Text buffer cannot be NULL");
|
|
||||||
assertTrue(maxLength >= 1, "maxLength must be at least 1");
|
|
||||||
assertNotNull(lines, "Lines buffer cannot be NULL");
|
|
||||||
assertTrue(linesMax >= 1, "linesMax must be at least 1");
|
|
||||||
memoryZero(box, sizeof(uitextbox_t));
|
|
||||||
box->text = text;
|
|
||||||
box->maxLength = maxLength;
|
|
||||||
box->lines = lines;
|
|
||||||
box->linesMax = linesMax;
|
|
||||||
box->glyphsBuiltForPage = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxSetText(uitextbox_t *box, const char_t *text) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
assertNotNull(text, "Text cannot be NULL");
|
|
||||||
stringCopy(box->text, text, box->maxLength);
|
|
||||||
box->currentPage = 0;
|
|
||||||
box->scroll = 0;
|
|
||||||
box->layoutWidth = 0.0f;
|
|
||||||
box->layoutHeight = 0.0f;
|
|
||||||
box->glyphsBuiltForPage = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxBuildLayout(
|
|
||||||
uitextbox_t *box,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
|
|
||||||
box->glyphsBuiltForPage = -1;
|
|
||||||
box->layoutWidth = width;
|
|
||||||
box->layoutHeight = height;
|
|
||||||
box->lineCount = 0;
|
|
||||||
box->pageCount = 1;
|
|
||||||
|
|
||||||
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
|
|
||||||
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
|
|
||||||
if(fontW <= 0.0f || fontH <= 0.0f) return;
|
|
||||||
|
|
||||||
box->charsPerLine = (int32_t)(width / fontW);
|
|
||||||
box->linesPerPage = (int32_t)(height / (fontH + UI_TEXTBOX_LINE_SPACING));
|
|
||||||
if(box->linesPerPage > UI_TEXTBOX_LINES_PER_PAGE_MAX) {
|
|
||||||
box->linesPerPage = UI_TEXTBOX_LINES_PER_PAGE_MAX;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(box->charsPerLine <= 0 || box->linesPerPage <= 0) return;
|
|
||||||
if(box->text[0] == '\0') return;
|
|
||||||
|
|
||||||
char_t *src = box->text;
|
|
||||||
int32_t i = 0;
|
|
||||||
|
|
||||||
while(src[i] != '\0' && box->lineCount < (int32_t)box->linesMax) {
|
|
||||||
if(src[i] == '\t') {
|
|
||||||
i++;
|
|
||||||
int32_t rem = box->lineCount % box->linesPerPage;
|
|
||||||
int32_t pad = rem > 0 ? box->linesPerPage - rem : 0;
|
|
||||||
while(pad > 0 && box->lineCount < (int32_t)box->linesMax) {
|
|
||||||
box->lines[box->lineCount].start = i;
|
|
||||||
box->lines[box->lineCount].count = 0;
|
|
||||||
box->lineCount++;
|
|
||||||
pad--;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t lineStart = i;
|
|
||||||
int32_t lineWidth = 0;
|
|
||||||
|
|
||||||
while(src[i] != '\0') {
|
|
||||||
char_t c = src[i];
|
|
||||||
|
|
||||||
if(c == '\n') { i++; break; }
|
|
||||||
if(c == '\t') break;
|
|
||||||
|
|
||||||
if(c == ' ') {
|
|
||||||
int32_t wordLen = 0;
|
|
||||||
int32_t j = i + 1;
|
|
||||||
while(
|
|
||||||
src[j] != ' ' && src[j] != '\n' &&
|
|
||||||
src[j] != '\t' && src[j] != '\0'
|
|
||||||
) {
|
|
||||||
wordLen++;
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(lineWidth > 0 && lineWidth + 1 + wordLen > box->charsPerLine) {
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lineWidth++;
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
if(lineWidth >= box->charsPerLine) break;
|
|
||||||
lineWidth++;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
box->lines[box->lineCount].start = lineStart;
|
|
||||||
box->lines[box->lineCount].count = lineWidth;
|
|
||||||
box->lineCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(box->lineCount == 0) {
|
|
||||||
box->pageCount = 1;
|
|
||||||
} else {
|
|
||||||
box->pageCount =
|
|
||||||
(box->lineCount + box->linesPerPage - 1) / box->linesPerPage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTextboxUpdate(uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
|
|
||||||
#ifdef DUSK_TIME_DYNAMIC
|
|
||||||
if(TIME.dynamicUpdate) errorOk();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if(!uiTextboxPageIsComplete(box)) {
|
|
||||||
box->scroll += UI_TEXTBOX_SCROLL_CHARS_PER_TICK;
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTextboxDraw(
|
|
||||||
uitextbox_t *box,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
|
|
||||||
float_t startX = (float_t)UI_FRAME_START_X;
|
|
||||||
float_t startY = (float_t)UI_FRAME_START_Y;
|
|
||||||
float_t contentX = x + startX;
|
|
||||||
float_t contentY = y + startY;
|
|
||||||
float_t contentW = width - 2.0f * startX;
|
|
||||||
float_t contentH = height - 2.0f * startY;
|
|
||||||
|
|
||||||
if(contentW != box->layoutWidth || contentH != box->layoutHeight) {
|
|
||||||
uiTextboxBuildLayout(box, contentW, contentH);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorChain(uiFrameDrawCached(&box->frameCache, x, y, width, height));
|
|
||||||
|
|
||||||
if(box->lineCount == 0 || box->text[0] == '\0') errorOk();
|
|
||||||
|
|
||||||
if(box->glyphsBuiltForPage != box->currentPage) {
|
|
||||||
uiTextboxBuildPageGlyphs(box);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(box->glyphCount == 0) errorOk();
|
|
||||||
|
|
||||||
int32_t visibleCount = 0;
|
|
||||||
while(
|
|
||||||
visibleCount < box->glyphCount &&
|
|
||||||
box->glyphs[visibleCount].revealAt <= box->scroll
|
|
||||||
) visibleCount++;
|
|
||||||
|
|
||||||
if(visibleCount == 0) errorOk();
|
|
||||||
|
|
||||||
spritebatchsprite_t scratch[UI_TEXTBOX_PAGE_GLYPHS_MAX];
|
|
||||||
for(int32_t i = 0; i < visibleCount; i++) {
|
|
||||||
scratch[i] = spriteBatchSpriteTranslate(
|
|
||||||
&box->glyphs[i].sprite, contentX, contentY
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_WHITE,
|
|
||||||
.texture = FONT_DEFAULT.texture
|
|
||||||
}
|
|
||||||
};
|
|
||||||
errorChain(spriteBatchBuffer(scratch, visibleCount, &SHADER_UNLIT, material));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxBuildPageGlyphs(uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
|
|
||||||
box->glyphCount = 0;
|
|
||||||
|
|
||||||
int32_t pageFirst = box->currentPage * box->linesPerPage;
|
|
||||||
int32_t pageLast = pageFirst + box->linesPerPage;
|
|
||||||
if(pageLast > box->lineCount) pageLast = box->lineCount;
|
|
||||||
|
|
||||||
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
|
|
||||||
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
|
|
||||||
int32_t consumed = 0;
|
|
||||||
for(int32_t li = pageFirst; li < pageLast; li++) {
|
|
||||||
uitextboxline_t *line = &box->lines[li];
|
|
||||||
float_t lineY =
|
|
||||||
(float_t)(li - pageFirst) * (fontH + UI_TEXTBOX_LINE_SPACING);
|
|
||||||
|
|
||||||
for(int32_t ci = 0; ci < line->count; ci++) {
|
|
||||||
consumed++;
|
|
||||||
char_t c = box->text[line->start + ci];
|
|
||||||
if(c == ' ') continue;
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
box->glyphCount < UI_TEXTBOX_PAGE_GLYPHS_MAX,
|
|
||||||
"Textbox page produces too many glyphs"
|
|
||||||
);
|
|
||||||
uitextboxglyph_t *glyph = &box->glyphs[box->glyphCount++];
|
|
||||||
glyph->sprite = textGetSprite(
|
|
||||||
(vec2){ (float_t)ci * fontW, lineY }, c, &FONT_DEFAULT
|
|
||||||
);
|
|
||||||
glyph->revealAt = consumed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
box->glyphsBuiltForPage = box->currentPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t uiTextboxGetPageCharCount(const uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
int32_t first = box->currentPage * box->linesPerPage;
|
|
||||||
int32_t last = first + box->linesPerPage;
|
|
||||||
if(last > box->lineCount) last = box->lineCount;
|
|
||||||
int32_t total = 0;
|
|
||||||
for(int32_t i = first; i < last; i++) {
|
|
||||||
total += box->lines[i].count;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxPageIsComplete(const uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
return box->scroll >= uiTextboxGetPageCharCount(box);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxHasNextPage(const uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
return box->currentPage + 1 < box->pageCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxNextPage(uitextbox_t *box) {
|
|
||||||
assertNotNull(box, "Textbox cannot be NULL");
|
|
||||||
if(!uiTextboxHasNextPage(box)) return;
|
|
||||||
box->currentPage++;
|
|
||||||
box->scroll = 0;
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
#include "display/spritebatch/spritebatchsprite.h"
|
|
||||||
|
|
||||||
#define UI_TEXTBOX_LINES_PER_PAGE_MAX 4
|
|
||||||
#define UI_TEXTBOX_SCROLL_CHARS_PER_TICK 1
|
|
||||||
#define UI_TEXTBOX_LINE_SPACING 0.0f
|
|
||||||
|
|
||||||
// Fixed capacity for a page's cached glyph sprites (see uitextboxglyph_t).
|
|
||||||
// Sized generously above UI_TEXTBOX_LINES_PER_PAGE_MAX worth of glyphs at
|
|
||||||
// typical textbox widths; uiTextboxBuildPageGlyphs asserts if a page
|
|
||||||
// somehow produces more than this.
|
|
||||||
#define UI_TEXTBOX_PAGE_GLYPHS_MAX 512
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int32_t start;
|
|
||||||
int32_t count;
|
|
||||||
} uitextboxline_t;
|
|
||||||
|
|
||||||
// A single visible glyph's cached sprite (relative to the textbox's
|
|
||||||
// content origin, i.e. (0,0)) plus the scroll value at/after which it
|
|
||||||
// becomes visible -- lets uiTextboxDraw turn the typewriter scroll into
|
|
||||||
// a simple prefix-count instead of recomputing glyph geometry every
|
|
||||||
// frame.
|
|
||||||
typedef struct {
|
|
||||||
spritebatchsprite_t sprite;
|
|
||||||
int32_t revealAt;
|
|
||||||
} uitextboxglyph_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char_t *text;
|
|
||||||
uint32_t maxLength;
|
|
||||||
|
|
||||||
uitextboxline_t *lines;
|
|
||||||
uint32_t linesMax;
|
|
||||||
int32_t lineCount;
|
|
||||||
int32_t charsPerLine;
|
|
||||||
int32_t linesPerPage;
|
|
||||||
int32_t pageCount;
|
|
||||||
|
|
||||||
// last dimensions used for layout; rebuild triggers when these change
|
|
||||||
float_t layoutWidth;
|
|
||||||
float_t layoutHeight;
|
|
||||||
|
|
||||||
int32_t currentPage;
|
|
||||||
int32_t scroll;
|
|
||||||
|
|
||||||
// Cached glyph sprites for the current page (see uitextboxglyph_t),
|
|
||||||
// rebuilt only when currentPage no longer matches
|
|
||||||
// glyphsBuiltForPage -- not every frame/scroll tick.
|
|
||||||
uitextboxglyph_t glyphs[UI_TEXTBOX_PAGE_GLYPHS_MAX];
|
|
||||||
int32_t glyphCount;
|
|
||||||
int32_t glyphsBuiltForPage;
|
|
||||||
|
|
||||||
uiframecache_t frameCache;
|
|
||||||
} uitextbox_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a textbox, zeroing all state and binding it to caller-owned
|
|
||||||
* text and line storage.
|
|
||||||
*
|
|
||||||
* @param box The textbox to initialize.
|
|
||||||
* @param text Caller-owned buffer the textbox copies its text into.
|
|
||||||
* @param maxLength Capacity of text, in characters.
|
|
||||||
* @param lines Caller-owned buffer the textbox lays lines out into.
|
|
||||||
* @param linesMax Capacity of lines, in entries.
|
|
||||||
*/
|
|
||||||
void uiTextboxInit(
|
|
||||||
uitextbox_t *box,
|
|
||||||
char_t *text,
|
|
||||||
const uint32_t maxLength,
|
|
||||||
uitextboxline_t *lines,
|
|
||||||
const uint32_t linesMax
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies text into the textbox and marks layout as dirty.
|
|
||||||
* Resets currentPage and scroll to 0.
|
|
||||||
*
|
|
||||||
* @param box The textbox to update.
|
|
||||||
* @param text Null-terminated source string.
|
|
||||||
*/
|
|
||||||
void uiTextboxSetText(uitextbox_t *box, const char_t *text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds word-wrap and page layout for the given draw dimensions.
|
|
||||||
* Called automatically by uiTextboxDraw when width or height changes.
|
|
||||||
*
|
|
||||||
* @param box The textbox to rebuild.
|
|
||||||
* @param width Available content width in pixels.
|
|
||||||
* @param height Available content height in pixels.
|
|
||||||
*/
|
|
||||||
void uiTextboxBuildLayout(
|
|
||||||
uitextbox_t *box,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances the typewriter scroll by UI_TEXTBOX_SCROLL_CHARS_PER_TICK.
|
|
||||||
* Skipped on dynamic ticks.
|
|
||||||
*
|
|
||||||
* @param box The textbox to update.
|
|
||||||
* @returns Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTextboxUpdate(uitextbox_t *box);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the textbox frame and visible text. Rebuilds layout automatically
|
|
||||||
* if width or height differs from the last draw call.
|
|
||||||
*
|
|
||||||
* @param box The textbox to draw.
|
|
||||||
* @param x Screen x position.
|
|
||||||
* @param y Screen y position.
|
|
||||||
* @param width Draw width in pixels.
|
|
||||||
* @param height Draw height in pixels.
|
|
||||||
* @returns Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTextboxDraw(
|
|
||||||
uitextbox_t *box,
|
|
||||||
const float_t x,
|
|
||||||
const float_t y,
|
|
||||||
const float_t width,
|
|
||||||
const float_t height
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the total visible char count for the current page.
|
|
||||||
*
|
|
||||||
* @param box The textbox to query.
|
|
||||||
* @returns Total chars on the current page.
|
|
||||||
*/
|
|
||||||
int32_t uiTextboxGetPageCharCount(const uitextbox_t *box);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when scroll has fully revealed the current page.
|
|
||||||
*
|
|
||||||
* @param box The textbox to query.
|
|
||||||
* @returns True if the current page is fully visible.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxPageIsComplete(const uitextbox_t *box);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when there is at least one more page after the current one.
|
|
||||||
*
|
|
||||||
* @param box The textbox to query.
|
|
||||||
* @returns True if a next page exists.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxHasNextPage(const uitextbox_t *box);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances to the next page and resets scroll to 0.
|
|
||||||
* Has no effect if already on the last page.
|
|
||||||
*
|
|
||||||
* @param box The textbox to advance.
|
|
||||||
*/
|
|
||||||
void uiTextboxNextPage(uitextbox_t *box);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuilds the cached glyph sprites (see uitextboxglyph_t) for the
|
|
||||||
* current page from its line layout. Called automatically by
|
|
||||||
* uiTextboxDraw whenever currentPage no longer matches the page the
|
|
||||||
* cache was last built for.
|
|
||||||
*
|
|
||||||
* @param box The textbox to rebuild.
|
|
||||||
*/
|
|
||||||
void uiTextboxBuildPageGlyphs(uitextbox_t *box);
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitextboxmain.h"
|
|
||||||
#include "ui/focus/uifocus.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/color.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
#include "display/shader/shaderunlit.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
|
|
||||||
uitextboxmain_t UI_TEXTBOX_MAIN;
|
|
||||||
static uifocusitem_t *focusItem = NULL;
|
|
||||||
|
|
||||||
errorret_t uiTextboxMainInit(void) {
|
|
||||||
uiTextboxInit(
|
|
||||||
&UI_TEXTBOX_MAIN.box,
|
|
||||||
UI_TEXTBOX_MAIN.text, UI_TEXTBOX_MAIN_TEXT_MAX,
|
|
||||||
UI_TEXTBOX_MAIN.lines, UI_TEXTBOX_MAIN_LINES_MAX
|
|
||||||
);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxMainSetText(const char_t *text) {
|
|
||||||
uiTextboxSetText(&UI_TEXTBOX_MAIN.box, text);
|
|
||||||
if(focusItem != NULL) return;
|
|
||||||
focusItem = uiFocusPush(
|
|
||||||
1, 1,
|
|
||||||
uiTextboxMainFocusSelected,
|
|
||||||
NULL,
|
|
||||||
uiTextboxMainFocusClosed,
|
|
||||||
NULL,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTextboxMainUpdate(void) {
|
|
||||||
if(focusItem == NULL) errorOk();
|
|
||||||
return uiTextboxUpdate(&UI_TEXTBOX_MAIN.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTextboxMainDraw(void) {
|
|
||||||
if(focusItem == NULL) errorOk();
|
|
||||||
float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight;
|
|
||||||
float_t h = (float_t)UI_TEXTBOX_MAIN_LINES * fontH +
|
|
||||||
(float_t)(UI_TEXTBOX_MAIN_LINES - 1) * UI_TEXTBOX_LINE_SPACING +
|
|
||||||
2.0f * (float_t)UI_FRAME_START_Y;
|
|
||||||
float_t w = (float_t)SCREEN.scanWidth;
|
|
||||||
float_t x = (float_t)SCREEN.scanX;
|
|
||||||
float_t y = (float_t)(SCREEN.scanY + SCREEN.scanHeight) - h;
|
|
||||||
errorChain(uiTextboxDraw(&UI_TEXTBOX_MAIN.box, x, y, w, h));
|
|
||||||
|
|
||||||
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) errorOk();
|
|
||||||
|
|
||||||
float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth;
|
|
||||||
float_t contentX = x + (float_t)UI_FRAME_START_X;
|
|
||||||
float_t contentY = y + (float_t)UI_FRAME_START_Y;
|
|
||||||
float_t contentW = w - 2.0f * (float_t)UI_FRAME_START_X;
|
|
||||||
float_t contentH = h - 2.0f * (float_t)UI_FRAME_START_Y;
|
|
||||||
|
|
||||||
shadermaterial_t material = {
|
|
||||||
.unlit = {
|
|
||||||
.color = COLOR_WHITE,
|
|
||||||
.texture = FONT_DEFAULT.texture
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
spritebatchsprite_t caret = textGetSprite(
|
|
||||||
(vec2){
|
|
||||||
contentX + contentW - fontW,
|
|
||||||
contentY + contentH - fontH
|
|
||||||
},
|
|
||||||
'v',
|
|
||||||
&FONT_DEFAULT
|
|
||||||
);
|
|
||||||
errorChain(spriteBatchBuffer(&caret, 1, &SHADER_UNLIT, material));
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxMainPageIsComplete(void) {
|
|
||||||
return uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxMainHasNextPage(void) {
|
|
||||||
return uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uiTextboxMainNextPage(void) {
|
|
||||||
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxMainIsActive(void) {
|
|
||||||
return focusItem != NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item) {
|
|
||||||
if(!uiTextboxPageIsComplete(&UI_TEXTBOX_MAIN.box)) {
|
|
||||||
UI_TEXTBOX_MAIN.box.scroll =
|
|
||||||
uiTextboxGetPageCharCount(&UI_TEXTBOX_MAIN.box);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(uiTextboxHasNextPage(&UI_TEXTBOX_MAIN.box)) {
|
|
||||||
uiTextboxNextPage(&UI_TEXTBOX_MAIN.box);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiFocusPopItem(focusItem);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item) {
|
|
||||||
focusItem = NULL;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "uitextbox.h"
|
|
||||||
#include "ui/focus/uifocusitem.h"
|
|
||||||
|
|
||||||
#define UI_TEXTBOX_MAIN_LINES 4
|
|
||||||
#define UI_TEXTBOX_MAIN_TEXT_MAX 1024
|
|
||||||
#define UI_TEXTBOX_MAIN_LINES_MAX 64
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uitextbox_t box;
|
|
||||||
char_t text[UI_TEXTBOX_MAIN_TEXT_MAX];
|
|
||||||
uitextboxline_t lines[UI_TEXTBOX_MAIN_LINES_MAX];
|
|
||||||
} uitextboxmain_t;
|
|
||||||
|
|
||||||
extern uitextboxmain_t UI_TEXTBOX_MAIN;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes UI_TEXTBOX_MAIN.
|
|
||||||
*
|
|
||||||
* @returns Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTextboxMainInit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies text into UI_TEXTBOX_MAIN and resets page and scroll.
|
|
||||||
*
|
|
||||||
* @param text Null-terminated source string.
|
|
||||||
*/
|
|
||||||
void uiTextboxMainSetText(const char_t *text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances the typewriter scroll for UI_TEXTBOX_MAIN.
|
|
||||||
*
|
|
||||||
* @returns Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTextboxMainUpdate(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws UI_TEXTBOX_MAIN full-width at the bottom of the screen.
|
|
||||||
* Position and size are derived from SCREEN each call.
|
|
||||||
*
|
|
||||||
* @returns Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTextboxMainDraw(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the current page is fully scrolled in.
|
|
||||||
*
|
|
||||||
* @returns True if the current page is complete.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxMainPageIsComplete(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when at least one more page follows the current one.
|
|
||||||
*
|
|
||||||
* @returns True if a next page exists.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxMainHasNextPage(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances UI_TEXTBOX_MAIN to the next page and resets scroll.
|
|
||||||
* Has no effect if already on the last page.
|
|
||||||
*/
|
|
||||||
void uiTextboxMainNextPage(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when UI_TEXTBOX_MAIN has focus (is visible and active).
|
|
||||||
*
|
|
||||||
* @returns True if the textbox is currently active.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxMainIsActive(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - skip scroll or advance page or dismiss.
|
|
||||||
*
|
|
||||||
* @param item The active focus item.
|
|
||||||
* @returns True.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxMainFocusSelected(const uifocusitem_t *item);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal focus callback - clears the focus item pointer on dismiss.
|
|
||||||
*
|
|
||||||
* @param item The focus item being closed.
|
|
||||||
* @returns True.
|
|
||||||
*/
|
|
||||||
bool_t uiTextboxMainFocusClosed(const uifocusitem_t *item);
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "uitestlabel.h"
|
|
||||||
#include "ui/widget/uilabel.h"
|
|
||||||
#include "display/text/text.h"
|
|
||||||
#include "display/screen/screen.h"
|
|
||||||
#include "engine/engine.h"
|
|
||||||
|
|
||||||
uilabel_t UI_TEST_LABEL;
|
|
||||||
|
|
||||||
errorret_t uiTestLabelInit() {
|
|
||||||
uiLabelInit(&UI_TEST_LABEL, &FONT_DEFAULT);
|
|
||||||
uiLabelSetText(&UI_TEST_LABEL, "duskrpg");
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t uiTestLabelDraw() {
|
|
||||||
int32_t versionWidth, versionHeight;
|
|
||||||
textMeasure(ENGINE.version, &FONT_DEFAULT, &versionWidth, &versionHeight);
|
|
||||||
|
|
||||||
errorChain(uiLabelDraw(
|
|
||||||
&UI_TEST_LABEL,
|
|
||||||
(float_t)(SCREEN.scanX + SCREEN.scanWidth - UI_TEST_LABEL.width),
|
|
||||||
(float_t)(
|
|
||||||
SCREEN.scanY + SCREEN.scanHeight - versionHeight -
|
|
||||||
UI_TEST_LABEL.height
|
|
||||||
)
|
|
||||||
));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the test label, a smoke test for the duskrpg-side UI
|
|
||||||
* element extension point (uielementgame.h).
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTestLabelInit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the test label above the engine's debug version number.
|
|
||||||
*
|
|
||||||
* @return Any error that occurs.
|
|
||||||
*/
|
|
||||||
errorret_t uiTestLabelDraw();
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Copyright (c) 2026 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
include(dusktest)
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
dusktest(test_uiframe.c)
|
|
||||||
dusktest(test_uislider.c)
|
|
||||||
dusktest(test_uitab.c)
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "dusktest.h"
|
|
||||||
#include "ui/frame/uiframe.h"
|
|
||||||
|
|
||||||
static void test_uiFrameBuildSprites_layout(void **state) {
|
|
||||||
UI_FRAME.tileset = (tileset_t){
|
|
||||||
.tileWidth = 1,
|
|
||||||
.tileHeight = 1,
|
|
||||||
.tileCount = 9,
|
|
||||||
.columns = 3,
|
|
||||||
.rows = 3,
|
|
||||||
.uv = { 0.25f, 0.25f }
|
|
||||||
};
|
|
||||||
|
|
||||||
const float_t x = 100.0f;
|
|
||||||
const float_t y = 50.0f;
|
|
||||||
const float_t width = 40.0f;
|
|
||||||
const float_t height = 30.0f;
|
|
||||||
const float_t tileW = (float_t)UI_FRAME_BORDER_WIDTH;
|
|
||||||
const float_t tileH = (float_t)UI_FRAME_BORDER_HEIGHT;
|
|
||||||
|
|
||||||
spritebatchsprite_t sprites[9];
|
|
||||||
uiFrameBuildSprites(sprites, x, y, width, height);
|
|
||||||
|
|
||||||
// Top-left corner sits exactly at the rect's origin.
|
|
||||||
assert_float_equal(sprites[0].min[0], x, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].min[1], y, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].max[0], x + tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].max[1], y + tileH, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].uvMin[0], 0.0f, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].uvMin[1], 0.0f, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].uvMax[0], 0.25f, 0.0001f);
|
|
||||||
assert_float_equal(sprites[0].uvMax[1], 0.25f, 0.0001f);
|
|
||||||
|
|
||||||
// Top-middle edge stretches to fill the width minus both corners.
|
|
||||||
assert_float_equal(sprites[1].min[0], x + tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[1].max[0], x + width - tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[1].max[1], y + tileH, 0.0001f);
|
|
||||||
|
|
||||||
// Center tile fills the remaining interior on both axes.
|
|
||||||
assert_float_equal(sprites[4].min[0], x + tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[4].min[1], y + tileH, 0.0001f);
|
|
||||||
assert_float_equal(sprites[4].max[0], x + width - tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[4].max[1], y + height - tileH, 0.0001f);
|
|
||||||
|
|
||||||
// Bottom-right corner sits exactly at the rect's far corner, sampling
|
|
||||||
// tileset column 2, row 2.
|
|
||||||
assert_float_equal(sprites[8].min[0], x + width - tileW, 0.0001f);
|
|
||||||
assert_float_equal(sprites[8].min[1], y + height - tileH, 0.0001f);
|
|
||||||
assert_float_equal(sprites[8].max[0], x + width, 0.0001f);
|
|
||||||
assert_float_equal(sprites[8].max[1], y + height, 0.0001f);
|
|
||||||
assert_float_equal(sprites[8].uvMin[0], 0.5f, 0.0001f);
|
|
||||||
assert_float_equal(sprites[8].uvMin[1], 0.5f, 0.0001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
const struct CMUnitTest tests[] = {
|
|
||||||
cmocka_unit_test(test_uiFrameBuildSprites_layout),
|
|
||||||
};
|
|
||||||
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "dusktest.h"
|
|
||||||
#include "ui/widget/uislider.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
// uiSliderInitFloat/InitInt require a real font (FONT_DEFAULT) to build
|
|
||||||
// the label's glyph cache, which needs a live GL context this test binary
|
|
||||||
// doesn't have -- see the label size caching contract for why this is
|
|
||||||
// safe to bypass here (uiSliderRebuildGeometry only reads slider->label's
|
|
||||||
// already-measured width/height, it never touches the font itself).
|
|
||||||
static void sliderSetupFloat(
|
|
||||||
uislider_t *slider,
|
|
||||||
const float_t value,
|
|
||||||
const float_t min,
|
|
||||||
const float_t max,
|
|
||||||
const float_t step
|
|
||||||
) {
|
|
||||||
memoryZero(slider, sizeof(uislider_t));
|
|
||||||
slider->label.width = 20;
|
|
||||||
slider->label.height = 10;
|
|
||||||
slider->type = UI_SLIDER_TYPE_FLOAT;
|
|
||||||
slider->min.f = min;
|
|
||||||
slider->max.f = max;
|
|
||||||
slider->step.f = step;
|
|
||||||
slider->value.f = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sliderSetupInt(
|
|
||||||
uislider_t *slider,
|
|
||||||
const int32_t value,
|
|
||||||
const int32_t min,
|
|
||||||
const int32_t max,
|
|
||||||
const int32_t step
|
|
||||||
) {
|
|
||||||
memoryZero(slider, sizeof(uislider_t));
|
|
||||||
slider->label.width = 20;
|
|
||||||
slider->label.height = 10;
|
|
||||||
slider->type = UI_SLIDER_TYPE_INT;
|
|
||||||
slider->min.i = min;
|
|
||||||
slider->max.i = max;
|
|
||||||
slider->step.i = step;
|
|
||||||
slider->value.i = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_uiSliderRebuildGeometry_trackPosition(void **state) {
|
|
||||||
uislider_t slider;
|
|
||||||
sliderSetupFloat(&slider, 0.0f, 0.0f, 1.0f, 0.1f);
|
|
||||||
uiSliderRebuildGeometry(&slider);
|
|
||||||
|
|
||||||
const float_t expectedX = (float_t)slider.label.width + UI_SLIDER_GAP;
|
|
||||||
const float_t expectedY =
|
|
||||||
((float_t)slider.label.height - UI_SLIDER_TRACK_HEIGHT) * 0.5f;
|
|
||||||
|
|
||||||
assert_float_equal(slider.cachedTrack.min[0], expectedX, 0.0001f);
|
|
||||||
assert_float_equal(slider.cachedTrack.min[1], expectedY, 0.0001f);
|
|
||||||
assert_float_equal(
|
|
||||||
slider.cachedTrack.max[0], expectedX + UI_SLIDER_TRACK_WIDTH, 0.0001f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_uiSliderRebuildGeometry_fillVisibility(void **state) {
|
|
||||||
uislider_t slider;
|
|
||||||
|
|
||||||
// At the minimum value, the ratio is 0 -- no fill should be visible.
|
|
||||||
sliderSetupFloat(&slider, 0.0f, 0.0f, 10.0f, 1.0f);
|
|
||||||
uiSliderRebuildGeometry(&slider);
|
|
||||||
assert_false(slider.cachedFillVisible);
|
|
||||||
|
|
||||||
// Halfway to max -- fill should cover half the track.
|
|
||||||
sliderSetupFloat(&slider, 5.0f, 0.0f, 10.0f, 1.0f);
|
|
||||||
uiSliderRebuildGeometry(&slider);
|
|
||||||
assert_true(slider.cachedFillVisible);
|
|
||||||
assert_float_equal(
|
|
||||||
slider.cachedFill.max[0] - slider.cachedFill.min[0],
|
|
||||||
UI_SLIDER_TRACK_WIDTH * 0.5f,
|
|
||||||
0.0001f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_uiSliderRebuildGeometry_stepMarkers(void **state) {
|
|
||||||
uislider_t slider;
|
|
||||||
|
|
||||||
// 5 steps between 0 and 10 -- under the marker cap, so markers render.
|
|
||||||
sliderSetupInt(&slider, 0, 0, 10, 2);
|
|
||||||
uiSliderRebuildGeometry(&slider);
|
|
||||||
assert_int_equal(slider.cachedMarkerCount, 6);
|
|
||||||
assert_float_equal(
|
|
||||||
slider.cachedMarkers[0].min[0], slider.cachedTrack.min[0] -
|
|
||||||
UI_SLIDER_STEP_MARKER_WIDTH * 0.5f,
|
|
||||||
0.0001f
|
|
||||||
);
|
|
||||||
|
|
||||||
// 100 steps -- over the marker cap, so it falls back to a smooth fill
|
|
||||||
// with no markers.
|
|
||||||
sliderSetupInt(&slider, 0, 0, 100, 1);
|
|
||||||
uiSliderRebuildGeometry(&slider);
|
|
||||||
assert_int_equal(slider.cachedMarkerCount, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
const struct CMUnitTest tests[] = {
|
|
||||||
cmocka_unit_test(test_uiSliderRebuildGeometry_trackPosition),
|
|
||||||
cmocka_unit_test(test_uiSliderRebuildGeometry_fillVisibility),
|
|
||||||
cmocka_unit_test(test_uiSliderRebuildGeometry_stepMarkers),
|
|
||||||
};
|
|
||||||
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "dusktest.h"
|
|
||||||
#include "ui/widget/uitab.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
|
|
||||||
// uiTabInit requires a real font (FONT_DEFAULT) to build the label's
|
|
||||||
// glyph cache, which needs a live GL context this test binary doesn't
|
|
||||||
// have. uiTabRebuildBackground only reads the label's already-measured
|
|
||||||
// width/height, so it's safe to set those directly and exercise it here.
|
|
||||||
static void test_uiTabRebuildBackground_matchesLabelSize(void **state) {
|
|
||||||
uitab_t tab;
|
|
||||||
memoryZero(&tab, sizeof(uitab_t));
|
|
||||||
tab.label.width = 42;
|
|
||||||
tab.label.height = 12;
|
|
||||||
|
|
||||||
uiTabRebuildBackground(&tab);
|
|
||||||
|
|
||||||
assert_float_equal(tab.cachedBackground.min[0], 0.0f, 0.0001f);
|
|
||||||
assert_float_equal(tab.cachedBackground.min[1], 0.0f, 0.0001f);
|
|
||||||
assert_float_equal(tab.cachedBackground.max[0], 42.0f, 0.0001f);
|
|
||||||
assert_float_equal(tab.cachedBackground.max[1], 12.0f, 0.0001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
const struct CMUnitTest tests[] = {
|
|
||||||
cmocka_unit_test(test_uiTabRebuildBackground_matchesLabelSize),
|
|
||||||
};
|
|
||||||
|
|
||||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"moves": {
|
||||||
|
"fire": {
|
||||||
|
// Target flags
|
||||||
|
"enemies": true,
|
||||||
|
"allies": true,
|
||||||
|
"multiple": false,
|
||||||
|
"self": true,
|
||||||
|
"default": "enemy",
|
||||||
|
"target": "SINGLE_ENEMY"
|
||||||
|
},
|
||||||
|
|
||||||
|
"slash": {
|
||||||
|
"enemies": true,
|
||||||
|
"allies": true,
|
||||||
|
"self": false,
|
||||||
|
"default": "enemy",
|
||||||
|
"target": "SINGLE_ENEMY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "Fire",
|
||||||
|
"message": "A burst of flame scorches the target."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WAIT",
|
||||||
|
"seconds": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BATTLE_FIGHTER_DAMAGE",
|
||||||
|
"fighter": "target",
|
||||||
|
"amount": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "Slash",
|
||||||
|
"message": "A quick blade strike cuts across the target."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WAIT",
|
||||||
|
"seconds": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BATTLE_FIGHTER_DAMAGE",
|
||||||
|
"fighter": "target",
|
||||||
|
"amount": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "BATTLE_START",
|
||||||
|
"enemies": [
|
||||||
|
{
|
||||||
|
"attack": 8, "defense": 4, "magic": 0, "speed": 5, "luck": 2,
|
||||||
|
"healthMax": 30, "mpMax": 0,
|
||||||
|
"abilities": ["fire"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attack": 6, "defense": 3, "magic": 0, "speed": 7, "luck": 2,
|
||||||
|
"healthMax": 20, "mpMax": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
// First thing, try and find a save device, used for settings and what not.
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "initial.checking_save.title",
|
||||||
|
"message": "initial.checking_save.message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WAIT",
|
||||||
|
"seconds": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SAVE_DEVICE_CHECK",
|
||||||
|
"successMarker": "CONTINUE",
|
||||||
|
"failureMarker": "SAVE_DEVICE_NOT_FOUND"
|
||||||
|
},
|
||||||
|
|
||||||
|
// No save device found
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "SAVE_DEVICE_NOT_FOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_OPTIONS_MARKERS",
|
||||||
|
"title": "initial.no_device.title",
|
||||||
|
"message": "initial.no_device.message",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "initial.no_device.retry",
|
||||||
|
"marker": "SAVE_DEVICE_RETRY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "initial.no_device.continue",
|
||||||
|
"marker": "CONTINUE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save device not found, user to retry, just close modal and loop
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "SAVE_DEVICE_RETRY"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "RESTART"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save device found (or continuing without one)
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "CONTINUE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CUTSCENE",
|
||||||
|
"name": "cutscenes/main_menu.jsonc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "MAIN_MENU_IDLE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "UI_SHOW",
|
||||||
|
"name": "main_menu"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Options button goes here - no options screen yet, so this is just
|
||||||
|
// an alias landing right back on the idle loop below.
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "OPTIONS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "IDLE"
|
||||||
|
},
|
||||||
|
|
||||||
|
// New Game button goes here.
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "NEW_GAME"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "main_menu.checking_save.title",
|
||||||
|
"message": "main_menu.checking_save.message"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WAIT",
|
||||||
|
"seconds": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SAVE_DEVICE_CHECK",
|
||||||
|
"successMarker": "SAVE_DEVICE_FOUND",
|
||||||
|
"failureMarker": "SAVE_DEVICE_NOT_FOUND"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save device not found
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "SAVE_DEVICE_NOT_FOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_OPTIONS_MARKERS",
|
||||||
|
"title": "main_menu.no_device.title",
|
||||||
|
"message": "main_menu.no_device.message",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "main_menu.no_device.retry",
|
||||||
|
"marker": "NEW_GAME"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "main_menu.no_device.continue",
|
||||||
|
"marker": "SAVE_DEVICE_FOUND"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Save data failed to load (e.g. corrupt/unreadable) - only option is
|
||||||
|
// to retry, no "continue without saving" here since we already know a
|
||||||
|
// device is present.
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "LOAD_ERROR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_OPTIONS_MARKERS",
|
||||||
|
"title": "main_menu.save_load_error.title",
|
||||||
|
"message": "main_menu.save_load_error.message",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "main_menu.save_load_error.retry",
|
||||||
|
"marker": "NEW_GAME"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quit Game button goes here. Cancelling jumps back to
|
||||||
|
// MAIN_MENU_IDLE; confirming falls through to QUIT_GAME, which
|
||||||
|
// ends the process directly.
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "QUIT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_OPTIONS_MARKERS",
|
||||||
|
"title": "main_menu.quit_confirm",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"text": "ui.confirm.confirm",
|
||||||
|
"marker": "QUIT_CONFIRMED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "ui.confirm.cancel",
|
||||||
|
"marker": "MAIN_MENU_IDLE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "QUIT_CONFIRMED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "QUIT_GAME"
|
||||||
|
},
|
||||||
|
|
||||||
|
// main menu just listens for cutscene ending to show the select save modal
|
||||||
|
// so we have that here.
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "SAVE_DEVICE_FOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "MODAL_CLOSE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SAVE_LOAD_ALL_SLOTS",
|
||||||
|
"successMarker": "LOADED",
|
||||||
|
"failureMarker": "LOAD_ERROR"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Must be the very last item - falling off the end here is what
|
||||||
|
// completes the cutscene and fires uiMainMenuOpenSelectSave (see
|
||||||
|
// cutsceneSystemSetOnComplete in ui/screen/mainmenu/uimainmenu.c).
|
||||||
|
// Placing this anywhere earlier would fall through into whatever
|
||||||
|
// comes next instead (e.g. the QUIT block, if placed just before it).
|
||||||
|
{
|
||||||
|
"type": "MARKER",
|
||||||
|
"name": "LOADED"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
// Placeholder for the new-game intro flow - just shows a modal for now.
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "MODAL",
|
||||||
|
"title": "New Game",
|
||||||
|
"message": "This is a placeholder for the new game intro.",
|
||||||
|
"blocking": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"items": {
|
||||||
|
"potion": { "type": "MEDICINE" },
|
||||||
|
"potato": { "type": "FOOD" },
|
||||||
|
"apple": { "type": "FOOD" }
|
||||||
|
}
|
||||||
|
}
|
||||||
+224
-1
@@ -5,11 +5,113 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n<7 ? 2 : 3));\n"
|
||||||
|
|
||||||
|
# Initial Scene
|
||||||
|
msgid "initial.checking_save.title"
|
||||||
|
msgstr "Checking for save data"
|
||||||
|
|
||||||
|
msgid "initial.checking_save.message"
|
||||||
|
msgstr "Please wait..."
|
||||||
|
|
||||||
|
msgid "initial.no_device.title"
|
||||||
|
msgstr "No Save Device Found"
|
||||||
|
|
||||||
|
msgid "initial.no_device.message"
|
||||||
|
msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved."
|
||||||
|
|
||||||
|
msgid "initial.no_device.retry"
|
||||||
|
msgstr "Try again"
|
||||||
|
|
||||||
|
msgid "initial.no_device.continue"
|
||||||
|
msgstr "Continue without saving"
|
||||||
|
|
||||||
|
|
||||||
|
# Main Menu Scene
|
||||||
|
msgid "main_menu.start_game"
|
||||||
|
msgstr "Start Game"
|
||||||
|
|
||||||
|
msgid "main_menu.options"
|
||||||
|
msgstr "Options"
|
||||||
|
|
||||||
|
msgid "main_menu.quit"
|
||||||
|
msgstr "Quit Game"
|
||||||
|
|
||||||
|
msgid "main_menu.quit_confirm"
|
||||||
|
msgstr "Are you sure you want to quit?"
|
||||||
|
|
||||||
|
msgid "main_menu.checking_save.title"
|
||||||
|
msgstr "Checking for save data"
|
||||||
|
|
||||||
|
msgid "main_menu.checking_save.message"
|
||||||
|
msgstr "Please wait..."
|
||||||
|
|
||||||
|
msgid "main_menu.no_device.title"
|
||||||
|
msgstr "No Save Device Found"
|
||||||
|
|
||||||
|
msgid "main_menu.no_device.message"
|
||||||
|
msgstr "Could not find a save device, ensure it is connected and try again. You can continue, but progress will not be saved."
|
||||||
|
|
||||||
|
msgid "main_menu.no_device.retry"
|
||||||
|
msgstr "Try again"
|
||||||
|
|
||||||
|
msgid "main_menu.no_device.continue"
|
||||||
|
msgstr "Continue without saving"
|
||||||
|
|
||||||
|
msgid "main_menu.save_load_error.title"
|
||||||
|
msgstr "Error"
|
||||||
|
|
||||||
|
msgid "main_menu.save_load_error.message"
|
||||||
|
msgstr "Failed to load save data. Please try again."
|
||||||
|
|
||||||
|
msgid "main_menu.save_load_error.retry"
|
||||||
|
msgstr "Try Again"
|
||||||
|
|
||||||
|
|
||||||
|
# Select Save Screen
|
||||||
|
msgid "ui.select_save.title"
|
||||||
|
msgstr "Select Save"
|
||||||
|
|
||||||
|
msgid "ui.select_save.empty"
|
||||||
|
msgstr "Empty Slot"
|
||||||
|
|
||||||
|
msgid "ui.select_save.corrupt"
|
||||||
|
msgstr "Corrupted Save"
|
||||||
|
|
||||||
|
msgid "ui.select_save.corrupt_warning"
|
||||||
|
msgstr "This save file is corrupted and could not be loaded. Please contact support if this issue persists."
|
||||||
|
|
||||||
|
msgid "ui.select_save.slot_format"
|
||||||
|
msgstr "%s Lv.%d %s"
|
||||||
|
|
||||||
|
msgid "ui.select_save.delete_mode"
|
||||||
|
msgstr "Delete a Save"
|
||||||
|
|
||||||
|
msgid "ui.select_save.delete_confirm"
|
||||||
|
msgstr "Are you sure you want to delete this save?"
|
||||||
|
|
||||||
|
msgid "ui.select_save.name_title"
|
||||||
|
msgstr "Enter game save file name"
|
||||||
|
|
||||||
|
msgid "ui.save_slot.number_format"
|
||||||
|
msgstr "Slot %d"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#: ui/menu.c:10
|
#: ui/menu.c:10
|
||||||
msgid "ui.title"
|
msgid "ui.title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Welcome"
|
"Welcome"
|
||||||
|
|
||||||
|
msgid "save.linux.mkdirp_failed"
|
||||||
|
msgstr "Failed to create save directory, check the disk is not full or write-protected."
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
#: src/dusk/ui/frame/settings/uisettings.c
|
||||||
msgid "ui.settings.tabs.general"
|
msgid "ui.settings.tabs.general"
|
||||||
msgstr "General"
|
msgstr "General"
|
||||||
@@ -56,6 +158,118 @@ msgstr "Items"
|
|||||||
msgid "ui.game_menu.settings"
|
msgid "ui.game_menu.settings"
|
||||||
msgstr "Settings"
|
msgstr "Settings"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save"
|
||||||
|
msgstr "Save"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_success"
|
||||||
|
msgstr "Game saved."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_cancelled"
|
||||||
|
msgstr "Save cancelled."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_unavailable"
|
||||||
|
msgstr "Can't save - no save device found."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_temporary"
|
||||||
|
msgstr "This session is temporary - no save device was found, so saving is disabled."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_create_confirm"
|
||||||
|
msgstr "No save data found. Create a new save?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_failed_format"
|
||||||
|
msgstr "Save failed: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/game/uigamemenu.c
|
||||||
|
msgid "ui.game_menu.save_check_failed_format"
|
||||||
|
msgstr "Can't save: %s"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.message"
|
||||||
|
msgstr "No save device found. You can continue, but\nprogress will not be saved."
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.retry"
|
||||||
|
msgstr "Retry"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialnocard.c
|
||||||
|
msgid "ui.initial.no_card.continue"
|
||||||
|
msgstr "Continue Anyway"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.message"
|
||||||
|
msgstr "No save data found. Create a new save?"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.yes"
|
||||||
|
msgstr "Yes"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/initial/uiinitialcreatesave.c
|
||||||
|
msgid "ui.initial.create_save.no"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.confirm"
|
||||||
|
msgstr "Confirm"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/uiconfirm.c
|
||||||
|
msgid "ui.confirm.cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.attack"
|
||||||
|
msgstr "Attack"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.flee"
|
||||||
|
msgstr "Flee"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlemenu.c
|
||||||
|
msgid "ui.battle.menu.target_format"
|
||||||
|
msgstr "Enemy %u (%u/%u HP)"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.hp_format"
|
||||||
|
msgstr "HP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/battle/uibattlehud.c
|
||||||
|
msgid "ui.battle.hud.mp_format"
|
||||||
|
msgstr "MP %u/%u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsaudio.c
|
||||||
|
msgid "ui.settings.audio.placeholder"
|
||||||
|
msgstr "No audio settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsdisplay.c
|
||||||
|
msgid "ui.settings.display.placeholder"
|
||||||
|
msgstr "No display settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/settings/uisettingsinput.c
|
||||||
|
msgid "ui.settings.input.placeholder"
|
||||||
|
msgstr "No input settings yet"
|
||||||
|
|
||||||
|
#: src/dusk/ui/frame/backpack/uibackpack.c
|
||||||
|
msgid "ui.backpack.category_format"
|
||||||
|
msgstr "Category %u"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiloading.c
|
||||||
|
msgid "ui.loading.text"
|
||||||
|
msgstr "loading"
|
||||||
|
|
||||||
|
#: src/dusk/ui/overlay/uiautosave.c
|
||||||
|
msgid "ui.autosave.saving"
|
||||||
|
msgstr "SAVING"
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Items
|
||||||
|
#
|
||||||
msgid "item.potion.name"
|
msgid "item.potion.name"
|
||||||
msgstr "Potion"
|
msgstr "Potion"
|
||||||
|
|
||||||
@@ -63,4 +277,13 @@ msgid "item.potato.name"
|
|||||||
msgstr "Potato"
|
msgstr "Potato"
|
||||||
|
|
||||||
msgid "item.apple.name"
|
msgid "item.apple.name"
|
||||||
msgstr "Apple"
|
msgstr "Apple"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Moves
|
||||||
|
#
|
||||||
|
msgid "move.fire.name"
|
||||||
|
msgstr "Fire"
|
||||||
|
|
||||||
|
msgid "move.slash.name"
|
||||||
|
msgstr "Slash"
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: ExampleApp 1.0\n"
|
|
||||||
"Language: es\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n==1 ? 0 : 1);\n"
|
|
||||||
|
|
||||||
#: ui/menu.c:10
|
|
||||||
msgid "ui.title"
|
|
||||||
msgstr ""
|
|
||||||
"Bienvenido"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.general"
|
|
||||||
msgstr "General"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.input"
|
|
||||||
msgstr "Entrada"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.display"
|
|
||||||
msgstr "Pantalla"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.audio"
|
|
||||||
msgstr "Audio"
|
|
||||||
|
|
||||||
msgid "ui.settings.input.deadzone"
|
|
||||||
msgstr "Deadzone"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
|
||||||
msgid "ui.settings.general.language"
|
|
||||||
msgstr "Idioma"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
|
||||||
msgid "ui.settings.general.language_detail"
|
|
||||||
msgstr "Se aplica después de reiniciar la aplicación."
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.apply"
|
|
||||||
msgstr "Aplicar"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/uiconfirm.c
|
|
||||||
msgid "ui.confirm.discard_changes"
|
|
||||||
msgstr "¿Descartar los cambios no guardados?"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.characters"
|
|
||||||
msgstr "Personajes"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.items"
|
|
||||||
msgstr "Objetos"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.settings"
|
|
||||||
msgstr "Configuración"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.potion.name"
|
|
||||||
msgstr "Poción"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.potato.name"
|
|
||||||
msgstr "Papa"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.apple.name"
|
|
||||||
msgstr "Manzana"
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: ExampleApp 1.0\n"
|
|
||||||
"Language: ja\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=(0);\n"
|
|
||||||
|
|
||||||
#: ui/menu.c:10
|
|
||||||
msgid "ui.title"
|
|
||||||
msgstr ""
|
|
||||||
"歓迎"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.general"
|
|
||||||
msgstr "一般"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.input"
|
|
||||||
msgstr "入力"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.display"
|
|
||||||
msgstr "表示"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.tabs.audio"
|
|
||||||
msgstr "オーディオ"
|
|
||||||
|
|
||||||
msgid "ui.settings.input.deadzone"
|
|
||||||
msgstr "デッドゾーン"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
|
||||||
msgid "ui.settings.general.language"
|
|
||||||
msgstr "言語"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettingsgeneral.c
|
|
||||||
msgid "ui.settings.general.language_detail"
|
|
||||||
msgstr "アプリケーションを再起動すると適用されます。"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/settings/uisettings.c
|
|
||||||
msgid "ui.settings.apply"
|
|
||||||
msgstr "適用"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/uiconfirm.c
|
|
||||||
msgid "ui.confirm.discard_changes"
|
|
||||||
msgstr "未保存の変更を破棄しますか?"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.characters"
|
|
||||||
msgstr "キャラクター"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.items"
|
|
||||||
msgstr "アイテム"
|
|
||||||
|
|
||||||
#: src/dusk/ui/frame/game/uigamemenu.c
|
|
||||||
msgid "ui.game_menu.settings"
|
|
||||||
msgstr "設定"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.potion.name"
|
|
||||||
msgstr "ポーション"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.potato.name"
|
|
||||||
msgstr "ジャガイモ"
|
|
||||||
|
|
||||||
#: src/dusk/rpg/item/item.json
|
|
||||||
msgid "item.apple.name"
|
|
||||||
msgstr "リンゴ"
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"meshes":[{"file":"house_4_4.dmf","pos":[2,1,0]},{"file":"house_6_3.dmf","pos":[1,9,0]},{"file":"house_2_2.dmf","pos":[12,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[6,0],[2,0],[2,0],[2,0],[2,0],[2,0],[2,0],[7,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[1,1],[1,1],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[4,0],[12,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[3,0],[1,1],[1,1],[5,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[8,0],[4,0],[4,0],[9,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[{"file":"house_5_3.dmf","pos":[1,1,0]}],"entities":[{"type":"global","globalId":3,"pos":[8,8,1]},{"type":"global","globalId":4,"pos":[12,2,0]}],"areas":[{"min":[11,3,0],"max":[16,9,10],"callbackId":1,"notify":3,"trigger":6}]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"meshes":[{"file":"house_8_4.dmf","pos":[1,1,0]},{"file":"house_3_3.dmf","pos":[11,2,0]},{"file":"house_2_2.dmf","pos":[3,11,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,null,null,null,null,null,null,[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"meshes":[{"file":"house_3_2.dmf","pos":[1,1,0]},{"file":"house_2_3.dmf","pos":[7,2,0]},{"file":"house_4_2.dmf","pos":[1,8,0]},{"file":"house_1_1.dmf","pos":[11,10,0]},{"file":"house_5_2.dmf","pos":[13,6,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"meshes":[{"file":"house_5_5.dmf","pos":[3,2,0]},{"file":"house_2_3.dmf","pos":[10,3,0]},{"file":"house_3_1.dmf","pos":[1,12,0]}],"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],null,null,null,[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]]}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"tiles":[[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],null,null,[1,0],[1,0],[1,0],[1,0],[1,3],[1,3],[1,3],[1,3],[1,3],[1,3],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,2],[4,2],[4,2],[4,2],[4,2],[4,2],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,1],[4,1],[4,1],[4,1],[4,1],[4,1],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[4,0],[4,0],[4,0],[4,0],[4,0],[4,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0],[1,0]],"meshes":[]}
|
||||||
Binary file not shown.
Binary file not shown.
Loaded 100 of 936 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user