mirror of
https://github.com/kichikuou/xsystem35-sdl2.git
synced 2026-09-24 23:48:04 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82c8b76a58 | ||
|
|
1918350b19 | ||
|
|
9504ee7b65 | ||
|
|
f033a507d3 | ||
|
|
fbeb9a780e | ||
|
|
8c69f53073 | ||
|
|
cc96c85381 | ||
|
|
a2618319d9 | ||
|
|
30fd5768da | ||
|
|
15a769d83c | ||
|
|
253de6adc8 | ||
|
|
a0daa3261c | ||
|
|
98e9d8bcfb | ||
|
|
b7854aaec1 | ||
|
|
905eb34590 | ||
|
|
8f22566adb | ||
|
|
48ded43e2a | ||
|
|
571bec63a2 | ||
|
|
345c017c22 | ||
|
|
205004086b | ||
|
|
6820bcfcb8 | ||
|
|
5ce1494b59 | ||
|
|
dd45e566f6 | ||
|
|
1ccc7630c1 | ||
|
|
6456f59c37 | ||
|
|
f748e8f678 | ||
|
|
a9e23508b7 | ||
|
|
c3bd978f02 |
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: Install Deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
|
||||
sudo apt install libsdl2-dev libfreetype-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
msystem: ${{ matrix.sys }}
|
||||
pacboy: >-
|
||||
SDL2:p
|
||||
SDL2_ttf:p
|
||||
freetype:p
|
||||
SDL2_mixer:p
|
||||
libwebp:p
|
||||
${{ matrix.deps }}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 2.20.0 - 2026-09-11
|
||||
- Improved font rendering to more closely match the original engine. On
|
||||
Windows, MS Gothic and MS Mincho are now used by default when available.
|
||||
- The experimental `enable_zb` option has been removed. (#44)
|
||||
- SACT: Message skipping now also skips text animation, effects, waits, and
|
||||
related input waits.
|
||||
- SACT: BGM now honors the loop count specified by the game.
|
||||
- SACT: Fixed timer handling in 楽園行.
|
||||
- The popup menu and dialogs can now be operated with the keyboard.
|
||||
- Fixed mouse coordinates changing unexpectedly when automatic mouse movement
|
||||
is disabled in System 3.8/3.9 games.
|
||||
- Android: The status bar now stays hidden after returning to the game. (#81)
|
||||
- Debugger: Added an `info cache` command for viewing cache usage and hit rates.
|
||||
|
||||
## 2.19.1 - 2026-08-02
|
||||
- Fixed an issue that prevented moving upward during battles in Rance 4.
|
||||
|
||||
|
||||
+10
-9
@@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
|
||||
enable_testing()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
|
||||
set(XSYSTEM35_VERSION "2.19.1")
|
||||
set(XSYSTEM35_VERSION "2.20.0")
|
||||
|
||||
include(CheckSymbolExists)
|
||||
include(FetchContent)
|
||||
@@ -55,6 +55,8 @@ check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)
|
||||
check_symbol_exists(sigaction "signal.h" HAVE_SIGACTION)
|
||||
|
||||
if (EMSCRIPTEN)
|
||||
option(JSPI "Use JavaScript Promise Integration instead of Asyncify" OFF)
|
||||
|
||||
function(add_emscripten_library name option)
|
||||
add_library(${name} INTERFACE)
|
||||
set_target_properties(${name} PROPERTIES
|
||||
@@ -63,14 +65,14 @@ if (EMSCRIPTEN)
|
||||
endfunction()
|
||||
add_emscripten_library(zlib -sUSE_ZLIB=1)
|
||||
add_emscripten_library(sdl2 -sUSE_SDL=2)
|
||||
add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2)
|
||||
add_emscripten_library(freetype2 -sUSE_FREETYPE=1)
|
||||
set(DEFAULT_FONT_PATH /fonts/)
|
||||
fetch_webp()
|
||||
set(HAVE_WEBP 1)
|
||||
|
||||
elseif (ANDROID)
|
||||
add_library(sdl2 ALIAS SDL2)
|
||||
add_library(sdl2_ttf ALIAS SDL2_ttf)
|
||||
add_library(freetype2 ALIAS freetype)
|
||||
add_library(sdl2_mixer ALIAS SDL2_mixer)
|
||||
find_library(ndk_log log)
|
||||
find_library(ndk_zlib z)
|
||||
@@ -88,18 +90,17 @@ else() # non-emscripten, non-android
|
||||
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0)
|
||||
pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf)
|
||||
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
|
||||
pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer)
|
||||
if (WIN32)
|
||||
add_static_library(sdl2 SDL2)
|
||||
add_static_library(sdl2_ttf SDL2TTF)
|
||||
# Workaround for linking error
|
||||
set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES
|
||||
$<LINK_GROUP:RESCAN,${SDL2TTF_STATIC_LIBRARIES} -lstdc++>)
|
||||
add_static_library(freetype2 FREETYPE)
|
||||
# harfbuzz and graphite2, which the static freetype depends on, are C++ libraries.
|
||||
target_link_libraries(freetype2 INTERFACE -lstdc++)
|
||||
add_static_library(sdl2_mixer SDL2MIXER)
|
||||
else()
|
||||
add_library(sdl2 ALIAS PkgConfig::SDL2)
|
||||
add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF)
|
||||
add_library(freetype2 ALIAS PkgConfig::FREETYPE)
|
||||
add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER)
|
||||
set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/)
|
||||
endif()
|
||||
|
||||
@@ -105,7 +105,7 @@ See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage.
|
||||
### Linux (Debian / Ubuntu)
|
||||
|
||||
```bash
|
||||
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
|
||||
$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
@@ -117,7 +117,7 @@ $ make && make install
|
||||
[Homebrew](https://brew.sh/) is required.
|
||||
|
||||
```bash
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor
|
||||
$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi cjson asciidoctor
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
@@ -129,7 +129,7 @@ $ make && make install
|
||||
[MSYS2](https://www.msys2.org) is required.
|
||||
|
||||
```bash
|
||||
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
|
||||
$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson
|
||||
$ mkdir -p out/debug
|
||||
$ cd out/debug
|
||||
$ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../
|
||||
|
||||
@@ -17,7 +17,7 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 36
|
||||
versionName "2.19.1" + gitRevision()
|
||||
versionName "2.20.0" + gitRevision()
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DANDROID_APP_PLATFORM=android-19", "-DANDROID_STL=c++_static"
|
||||
|
||||
@@ -11,9 +11,10 @@ FetchContent_Declare(
|
||||
URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16
|
||||
)
|
||||
FetchContent_Declare(
|
||||
SDL_ttf
|
||||
URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz
|
||||
URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d
|
||||
freetype
|
||||
URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz
|
||||
https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz
|
||||
URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678
|
||||
)
|
||||
FetchContent_Declare(
|
||||
SDL_mixer
|
||||
@@ -21,9 +22,12 @@ FetchContent_Declare(
|
||||
URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499
|
||||
)
|
||||
|
||||
set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE)
|
||||
set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE)
|
||||
set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE)
|
||||
# Only FreeType's own rasterizer is needed, none of its optional dependencies.
|
||||
set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE)
|
||||
set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE)
|
||||
set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE)
|
||||
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE)
|
||||
set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE)
|
||||
|
||||
set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE)
|
||||
set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE)
|
||||
@@ -33,7 +37,7 @@ set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE)
|
||||
set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE)
|
||||
set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(SDL SDL_ttf SDL_mixer)
|
||||
FetchContent_MakeAvailable(SDL freetype SDL_mixer)
|
||||
|
||||
# The main CMakeLists.txt of xsystem35
|
||||
add_subdirectory(${PROJECT_ROOT_DIR} xsystem35)
|
||||
@@ -49,7 +53,5 @@ file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mi
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg)
|
||||
file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui)
|
||||
file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz)
|
||||
file(COPY_FILE ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype)
|
||||
file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer)
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
<!-- Example of setting SDL hints from AndroidManifest.xml:
|
||||
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
|
||||
-->
|
||||
<meta-data android:name="SDL_ENV.SDL_IOS_ORIENTATIONS" android:value="LandscapeLeft LandscapeRight"/>
|
||||
|
||||
<activity android:name=".LauncherActivity"
|
||||
android:exported="true">
|
||||
|
||||
@@ -45,6 +45,8 @@ class GameActivity : SDLActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
gameRoot = File(intent.getStringExtra(EXTRA_GAME_ROOT)!!)
|
||||
// Workaround for https://github.com/libsdl-org/SDL/issues/8995
|
||||
SDLActivity.setWindowStyle(true)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
||||
@@ -12,11 +12,9 @@ class LicensesMenuActivity : Activity() {
|
||||
Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"),
|
||||
Entry("SDL", "SDL", "https://www.libsdl.org/"),
|
||||
Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"),
|
||||
Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"),
|
||||
Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"),
|
||||
Entry("microui", "microui", "https://github.com/rxi/microui"),
|
||||
Entry("FreeType", "freetype", "https://freetype.org/"),
|
||||
Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"),
|
||||
Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"),
|
||||
Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"),
|
||||
)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#cmakedefine PACKAGE "@PACKAGE@"
|
||||
#define VERSION "@XSYSTEM35_VERSION@"
|
||||
|
||||
#cmakedefine CACHE_TOTALSIZE @CACHE_TOTALSIZE@
|
||||
|
||||
#cmakedefine ENABLE_DEBUGGER @ENABLE_DEBUGGER@
|
||||
#cmakedefine ENABLE_MIDI_SDLMIXER @ENABLE_MIDI_SDLMIXER@
|
||||
#cmakedefine ENABLE_MIDI_PORTMIDI @ENABLE_MIDI_PORTMIDI@
|
||||
|
||||
@@ -12,8 +12,8 @@ or in your `.xsys35rc` file. For example:
|
||||
xsystem35 -censor misc/censor/kichikuou.txt
|
||||
```
|
||||
|
||||
The list files contain image numbers (integers), one per line. Lines starting
|
||||
with `#` are treated as comments.
|
||||
The list files contain image numbers (integers), one per line. A `#` starts a
|
||||
comment that continues to the end of the line.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# Censor list for ランス -光をもとめて- (Rance: The Quest for Hikari)
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
5
|
||||
6
|
||||
8
|
||||
22
|
||||
26
|
||||
27
|
||||
29
|
||||
32
|
||||
39
|
||||
40
|
||||
41
|
||||
51
|
||||
56
|
||||
57
|
||||
75
|
||||
76
|
||||
77
|
||||
78 # *
|
||||
80
|
||||
82
|
||||
89
|
||||
91
|
||||
96 # *
|
||||
97 # *
|
||||
98
|
||||
99
|
||||
101
|
||||
208
|
||||
@@ -0,0 +1,59 @@
|
||||
# Censor list for ランスII (Rance II)
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
26
|
||||
39 # *
|
||||
40
|
||||
41
|
||||
77
|
||||
78
|
||||
86
|
||||
98
|
||||
103
|
||||
104
|
||||
121
|
||||
122
|
||||
123
|
||||
124
|
||||
130
|
||||
131
|
||||
132
|
||||
133
|
||||
134
|
||||
140
|
||||
141
|
||||
142
|
||||
148
|
||||
156
|
||||
157
|
||||
158
|
||||
159
|
||||
161
|
||||
166
|
||||
171
|
||||
172
|
||||
185
|
||||
187
|
||||
191
|
||||
195
|
||||
197
|
||||
200
|
||||
201
|
||||
234
|
||||
236
|
||||
245
|
||||
246
|
||||
247
|
||||
272
|
||||
274
|
||||
275
|
||||
276
|
||||
278
|
||||
280
|
||||
281
|
||||
282
|
||||
284
|
||||
289
|
||||
292
|
||||
299
|
||||
300
|
||||
302
|
||||
@@ -0,0 +1,121 @@
|
||||
# Censor list for Rance 3
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
1 # *
|
||||
2 # *
|
||||
3
|
||||
6
|
||||
8
|
||||
9
|
||||
32 # *
|
||||
53 # *
|
||||
54
|
||||
70
|
||||
71
|
||||
72
|
||||
73
|
||||
75
|
||||
77 # *
|
||||
78
|
||||
79
|
||||
112 # *
|
||||
113
|
||||
114
|
||||
115
|
||||
116
|
||||
117
|
||||
118 # *
|
||||
150
|
||||
151
|
||||
152
|
||||
153
|
||||
154
|
||||
155
|
||||
166
|
||||
167
|
||||
180
|
||||
181
|
||||
183
|
||||
184
|
||||
185
|
||||
186
|
||||
187
|
||||
188
|
||||
189
|
||||
190
|
||||
195
|
||||
196
|
||||
208
|
||||
209
|
||||
210
|
||||
211
|
||||
212
|
||||
213
|
||||
215
|
||||
216
|
||||
217
|
||||
218
|
||||
219
|
||||
231
|
||||
232
|
||||
233
|
||||
234
|
||||
235
|
||||
236
|
||||
238
|
||||
239
|
||||
260
|
||||
261
|
||||
263
|
||||
264
|
||||
265
|
||||
266
|
||||
268
|
||||
269
|
||||
330
|
||||
331
|
||||
332
|
||||
334
|
||||
335
|
||||
336
|
||||
337
|
||||
338
|
||||
339
|
||||
420
|
||||
421
|
||||
422
|
||||
423
|
||||
424
|
||||
425
|
||||
426
|
||||
427
|
||||
428
|
||||
429
|
||||
440
|
||||
441
|
||||
442
|
||||
443
|
||||
444
|
||||
445
|
||||
447
|
||||
473
|
||||
474
|
||||
475
|
||||
476
|
||||
477
|
||||
479
|
||||
519 # *
|
||||
521 # *
|
||||
523
|
||||
524
|
||||
525
|
||||
526
|
||||
530
|
||||
533 # *
|
||||
534
|
||||
535
|
||||
536
|
||||
537
|
||||
570
|
||||
571
|
||||
572
|
||||
581
|
||||
@@ -0,0 +1,168 @@
|
||||
# Censor list for Rance IV
|
||||
# Images marked with a * might be allowed in an 18+ stream.
|
||||
1
|
||||
2
|
||||
10
|
||||
20
|
||||
26
|
||||
28
|
||||
29
|
||||
30 # *
|
||||
31
|
||||
32
|
||||
33
|
||||
34
|
||||
35
|
||||
36
|
||||
37
|
||||
38
|
||||
39 # *
|
||||
40
|
||||
41
|
||||
42
|
||||
43
|
||||
44
|
||||
45
|
||||
46
|
||||
48
|
||||
49
|
||||
75
|
||||
76
|
||||
77
|
||||
78
|
||||
79
|
||||
122
|
||||
126
|
||||
140
|
||||
142
|
||||
148
|
||||
149
|
||||
162 # *
|
||||
163
|
||||
164
|
||||
165 # *
|
||||
166
|
||||
167
|
||||
168
|
||||
169 # *
|
||||
170
|
||||
171
|
||||
172
|
||||
176
|
||||
177
|
||||
178
|
||||
185
|
||||
186
|
||||
188
|
||||
189
|
||||
205
|
||||
207
|
||||
217 # *
|
||||
220
|
||||
221
|
||||
222
|
||||
223
|
||||
228
|
||||
241
|
||||
242
|
||||
251
|
||||
252
|
||||
253
|
||||
254
|
||||
260
|
||||
261
|
||||
262 # *
|
||||
264 # *
|
||||
265
|
||||
266
|
||||
267
|
||||
268
|
||||
269
|
||||
280
|
||||
281
|
||||
283
|
||||
284
|
||||
285
|
||||
286 # *
|
||||
287
|
||||
288
|
||||
292
|
||||
294
|
||||
299
|
||||
370
|
||||
371
|
||||
372
|
||||
373
|
||||
376
|
||||
378
|
||||
382
|
||||
384
|
||||
385 # *
|
||||
386
|
||||
387
|
||||
388
|
||||
389
|
||||
414 # *
|
||||
416
|
||||
428 # *
|
||||
531 # *
|
||||
532
|
||||
533
|
||||
534 # *
|
||||
535
|
||||
550
|
||||
551
|
||||
552
|
||||
553
|
||||
554
|
||||
555
|
||||
562 # *
|
||||
565
|
||||
615
|
||||
625
|
||||
626
|
||||
711
|
||||
723
|
||||
1055
|
||||
1056
|
||||
1057
|
||||
1058
|
||||
1059
|
||||
1060
|
||||
1061
|
||||
1062
|
||||
1545 # *
|
||||
1546 # *
|
||||
1547 # *
|
||||
1548 # *
|
||||
1549 # *
|
||||
1653 # *
|
||||
1654 # *
|
||||
1655 # *
|
||||
1656 # *
|
||||
1657 # *
|
||||
1658 # *
|
||||
1659 # *
|
||||
1660 # *
|
||||
1661 # *
|
||||
1662 # *
|
||||
1663 # *
|
||||
1664 # *
|
||||
1665 # *
|
||||
1728
|
||||
1729
|
||||
2004
|
||||
2005
|
||||
2006
|
||||
2007
|
||||
2008
|
||||
2009
|
||||
2010
|
||||
2011
|
||||
2012
|
||||
2013
|
||||
2014
|
||||
2015
|
||||
2016
|
||||
2017
|
||||
2018
|
||||
+18
-9
@@ -33,6 +33,7 @@
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "ald_manager.h"
|
||||
#include "audio_meta.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "xsystem35.h"
|
||||
@@ -286,8 +287,11 @@ static void DrawEffect() {
|
||||
if (sact.version >= 110) {
|
||||
wEffectkey = getCaliValue();
|
||||
}
|
||||
|
||||
sp_eupdate(wType, wEffectTime, wEffectkey);
|
||||
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping())
|
||||
sp_update_all(true);
|
||||
else
|
||||
sp_eupdate(wType, wEffectTime, wEffectkey);
|
||||
|
||||
TRACE("SACT.DrawEffect %d,%d,%d:", wType, wEffectTime, wEffectkey);
|
||||
}
|
||||
@@ -304,7 +308,10 @@ static void DrawEffectAlphaMap() {
|
||||
int wEffectTime = getCaliValue();
|
||||
int wEffectKey = getCaliValue();
|
||||
|
||||
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping())
|
||||
sp_update_all(true);
|
||||
else
|
||||
sp_eupdate_amap(nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
|
||||
TRACE("SACT.DrawEffectAlphaMap %d,%d,%d:", nIndexAlphaMap, wEffectTime, wEffectKey);
|
||||
}
|
||||
@@ -622,9 +629,10 @@ static void QuakeSprite() {
|
||||
if (sact.version >= 110) {
|
||||
nfKeyEnable = getCaliValue();
|
||||
}
|
||||
|
||||
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
|
||||
|
||||
|
||||
if (!msgskip_isSkipping())
|
||||
sp_quake_sprite(wType, wAmplitudeX, wAmplitudeY, wCount, nfKeyEnable);
|
||||
|
||||
TRACE("SACT.QuakeSprite %d,%d,%d,%d:", wType, wAmplitudeX, wAmplitudeY, wCount);
|
||||
}
|
||||
|
||||
@@ -1615,7 +1623,7 @@ static void TimerWait() {
|
||||
|
||||
int msec = (wCount - stimer_get(wTimerID)) * 10;
|
||||
if (msec > 0)
|
||||
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE);
|
||||
sys_keywait(msec, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
|
||||
|
||||
TRACE("SACT.TimerWait %d,%d:", wTimerID, wCount);
|
||||
}
|
||||
@@ -1628,7 +1636,7 @@ static void TimerWait() {
|
||||
static void Wait() {
|
||||
int wCount = getCaliValue();
|
||||
|
||||
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE);
|
||||
sys_keywait(wCount * 10, KEYWAIT_CTRL_CANCELABLE | KEYWAIT_SKIPPABLE);
|
||||
|
||||
TRACE("SACT.Wait %d:", wCount);
|
||||
}
|
||||
@@ -1845,8 +1853,9 @@ static void MusicPlay() {
|
||||
int wNum = getCaliValue();
|
||||
int wFadeTime = getCaliValue();
|
||||
int wVolume = getCaliValue();
|
||||
bgi_t *bgi = bgi_find(wNum);
|
||||
|
||||
musbgm_play(wNum, wFadeTime, wVolume, 0);
|
||||
musbgm_play(wNum, wFadeTime, wVolume, bgi ? bgi->loopno : 0);
|
||||
|
||||
TRACE("SACT.MusicPlay %d,%d,%d:", wNum, wFadeTime, wVolume);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,11 @@ static void draw_log() {
|
||||
if (0 == strcmp(str, "\n")) {
|
||||
SDL_BlitSurface(hline, NULL, main_surface, &(SDL_Rect){0, y + FONTSIZE/2, main_surface->w, 3});
|
||||
} else {
|
||||
FontSpec font_spec = { .type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC, .size = FONTSIZE };
|
||||
FontSpec font_spec = {
|
||||
.type = cur < 6 ? FONT_MINCHO : FONT_GOTHIC,
|
||||
.weight = cur < 6 ? FONT_WEIGHT_NORMAL : FONT_WEIGHT_BOLD,
|
||||
.size = FONTSIZE
|
||||
};
|
||||
gfx_drawString(0, y, str, 255, font_spec);
|
||||
}
|
||||
y += FONTSIZE;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "system.h"
|
||||
#include "nact.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "sactsound.h"
|
||||
#include "music.h"
|
||||
#include "sact.h"
|
||||
@@ -120,7 +121,7 @@ void ssnd_waitkey(int no, vmvar_t *res) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sact.waitskiplv > 1) {
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping()) {
|
||||
*res = SYS35KEY_RET;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "sacttimer.h"
|
||||
#include "system.h"
|
||||
|
||||
#define MAX_TIMER 10
|
||||
#define MAX_TIMER 128
|
||||
#define TICKS_PER_CENTISECOND 10
|
||||
|
||||
uint32_t ticks_base[MAX_TIMER];
|
||||
|
||||
@@ -39,11 +39,6 @@
|
||||
@param cancel: キー抜け(0:なし, 1:あり)
|
||||
*/
|
||||
void sp_eupdate(int type, int time, int cancel) {
|
||||
if (sact.waitskiplv > 1) {
|
||||
sp_update_all(true);
|
||||
return;
|
||||
}
|
||||
|
||||
sp_update_all(false);
|
||||
|
||||
enum effect_type effect = from_sact_effect(type);
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "ags.h"
|
||||
#include "gfx.h"
|
||||
#include "nact.h"
|
||||
#include "variable.h"
|
||||
#include "input.h"
|
||||
#include "msgskip.h"
|
||||
#include "sact.h"
|
||||
#include "sprite.h"
|
||||
#include "drawtext.h"
|
||||
@@ -240,7 +242,8 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
char *msg;
|
||||
sprite_t *sp;
|
||||
int len = 0; // 処理した文字数?
|
||||
bool needupdate = false;
|
||||
bool has_batched_output = false;
|
||||
bool flush_batched_output = wSpeed > 0;
|
||||
SDL_Rect uparea = {0,0,0,0};
|
||||
|
||||
// wRSize == 0 -> ルビ無し(SACT.MessageOutputからの呼出)
|
||||
@@ -250,7 +253,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
if (!is_messagesprite(wNum)) return;
|
||||
|
||||
// MessageSkip中は文字送り速度を最大に
|
||||
if (sact.waitskiplv > 1) wSpeed = 0;
|
||||
if (sact.waitskiplv > 1 || msgskip_isSkipping()) wSpeed = 0;
|
||||
|
||||
// shortcut
|
||||
sp = sact.sp[wNum];
|
||||
@@ -302,7 +305,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
mbuf,
|
||||
wColorR, wColorG, wColorB);
|
||||
|
||||
needupdate = true;
|
||||
has_batched_output = true;
|
||||
|
||||
append_to_log(mbuf);
|
||||
|
||||
@@ -313,7 +316,7 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
cw,
|
||||
wSize + wRSize + wRLineSpace);
|
||||
sp_update_clipped();
|
||||
needupdate = false;
|
||||
has_batched_output = false;
|
||||
|
||||
// keywait
|
||||
delta = sys_get_ticks() - wcnt;
|
||||
@@ -333,11 +336,17 @@ void smsg_out(int wNum, int wSize, int wColorR, int wColorG, int wColorB, int wF
|
||||
// バッファリング中の文字のクリア
|
||||
sact.msgbuf[0] = '\0';
|
||||
|
||||
// Waitなしの出力は最後にupdate
|
||||
if (needupdate) {
|
||||
// Register the update area after rendering without waits.
|
||||
if (has_batched_output) {
|
||||
uparea.w = sp->width;
|
||||
uparea.h = min(sp->height, uparea.y - sp->u.msg.dspcur.y + wLineSpace + wLineSpace + wRSize);
|
||||
uparea.h = min(sp->height - uparea.y,
|
||||
sp->u.msg.dspcur.y - uparea.y + wSize + wRSize + wRLineSpace);
|
||||
sp_updateme_part(sp, uparea.x, uparea.y, uparea.w, uparea.h);
|
||||
// Present now if waiting was skipped.
|
||||
if (flush_batched_output) {
|
||||
sp_update_clipped();
|
||||
gfx_updateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
// ????
|
||||
@@ -408,7 +417,7 @@ int smsg_keywait(int wNum1, int wNum2, int msglen) {
|
||||
struct markinfo minfo[6];
|
||||
int i = 0, j, maxstep;
|
||||
|
||||
if (sact.waitskiplv > 0) {
|
||||
if (sact.waitskiplv > 0 || msgskip_isSkipping()) {
|
||||
sys_getInputInfo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,11 @@
|
||||
#include "ags.h"
|
||||
#include "font.h"
|
||||
|
||||
static FontType ftype;
|
||||
static int fsize; // フォントの大きさ
|
||||
static FontSpec dt_spec = {
|
||||
.type = FONT_GOTHIC,
|
||||
.weight = FONT_WEIGHT_BOLD,
|
||||
.size = 12
|
||||
};
|
||||
|
||||
/**
|
||||
* 次に描く文字のフォントの種類と大きさを設定
|
||||
@@ -48,8 +51,8 @@ void dt_setfont(FontType type, int size) {
|
||||
}
|
||||
#endif
|
||||
|
||||
ftype = type;
|
||||
fsize = size;
|
||||
dt_spec.type = type;
|
||||
dt_spec.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +69,7 @@ void dt_setfont(FontType type, int size) {
|
||||
* @return: 実際に描画した幅
|
||||
*/
|
||||
int dt_drawtext_col(SDL_Surface *sf, int x, int y, char *buf, int r, int g, int b) {
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, (FontSpec){ .type = ftype, .size = fsize });
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(buf, r, g, b, dt_spec);
|
||||
if (glyph == NULL) return 0;
|
||||
|
||||
SDL_Rect rect = {x, y, glyph->w, glyph->h};
|
||||
|
||||
+21
-15
@@ -35,6 +35,7 @@
|
||||
#include "ags.h"
|
||||
#include "variable.h"
|
||||
#include "sactcg.h"
|
||||
#include "ald_manager.h"
|
||||
#include "cg.h"
|
||||
#include "gfx.h"
|
||||
|
||||
@@ -220,7 +221,8 @@ void scg_create_text(int wNumCG, int wSize, int wR, int wG, int wB, int wText) {
|
||||
// 勝手に出ていいのかな?
|
||||
if (svar_length(wText) == 0) return;
|
||||
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, (FontSpec){ .size = wSize });
|
||||
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(svar_get(wText), wR, wG, wB, spec);
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
|
||||
@@ -244,7 +246,8 @@ void scg_create_textnum(int wNumCG, int wSize, int wR, int wG, int wB, int wFigs
|
||||
}
|
||||
sprintf(s, ss, wValue);
|
||||
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, (FontSpec){ .size = wSize });
|
||||
FontSpec spec = { .type = FONT_GOTHIC, .weight = FONT_WEIGHT_BOLD, .size = wSize };
|
||||
SDL_Surface *glyph = ags_drawStringToSurface(s, wR, wG, wB, spec);
|
||||
|
||||
SDL_Surface *sf = SDL_CreateRGBSurfaceWithFormat(0, glyph->w, wSize, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
SDL_SetSurfaceBlendMode(glyph, SDL_BLENDMODE_NONE);
|
||||
@@ -326,24 +329,27 @@ void scg_free(int no) {
|
||||
// CGの種類を取得
|
||||
int scg_querytype(int wNumCG) {
|
||||
if (wNumCG >= (CGMAX -1)) return CG_NOTUSED;
|
||||
if (!cg_store || !cg_store[wNumCG]) return CG_NOTUSED;
|
||||
return cg_store[wNumCG]->type;
|
||||
if (cg_store && cg_store[wNumCG])
|
||||
return cg_store[wNumCG]->type;
|
||||
// Linked CGs are valid even before they are loaded into cg_store.
|
||||
return ald_is_linked(DRIFILE_CG, wNumCG - 1) ? CG_LINKED : CG_NOTUSED;
|
||||
}
|
||||
|
||||
// CGの大きさを取得
|
||||
bool scg_querysize(int wNumCG, vmvar_t *w, vmvar_t *h) {
|
||||
if (wNumCG >= (CGMAX -1)) goto errexit;
|
||||
if (!cg_store || !cg_store[wNumCG]) goto errexit;
|
||||
if (cg_store[wNumCG]->sf == NULL) goto errexit;
|
||||
|
||||
*w = cg_store[wNumCG]->sf->w;
|
||||
*h = cg_store[wNumCG]->sf->h;
|
||||
|
||||
return true;
|
||||
|
||||
errexit:
|
||||
*w = *h = 0;
|
||||
return false;
|
||||
|
||||
if (wNumCG >= (CGMAX - 1))
|
||||
return false;
|
||||
cginfo_t *cg = cg_store ? cg_store[wNumCG] : NULL;
|
||||
if (!cg && ald_is_linked(DRIFILE_CG, wNumCG - 1))
|
||||
cg = scg_get(wNumCG);
|
||||
if (!cg || !cg->sf)
|
||||
return false;
|
||||
|
||||
*w = cg->sf->w;
|
||||
*h = cg->sf->h;
|
||||
return true;
|
||||
}
|
||||
|
||||
// CGのBPPを取得
|
||||
|
||||
+27
-12
@@ -10,6 +10,7 @@ add_library(src_lib STATIC
|
||||
cali.c
|
||||
dri.c
|
||||
filecheck.c
|
||||
font.c
|
||||
gameresource.c
|
||||
hankaku.c
|
||||
mmap.c
|
||||
@@ -72,7 +73,6 @@ target_sources(xsystem35 PRIVATE
|
||||
ecopy.c
|
||||
effect.c
|
||||
event.c
|
||||
font.c
|
||||
hacks.c
|
||||
gfx_draw.c
|
||||
gfx_image.c
|
||||
@@ -137,16 +137,17 @@ if (ENABLE_DEBUGGER)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_sources(src_lib PRIVATE win/resources.c)
|
||||
target_sources(xsystem35 PRIVATE
|
||||
win/dialog.c win/resources.rc win/resources.c win/menubar.c win/console.c)
|
||||
win/dialog.c win/resources.rc win/menubar.c win/console.c)
|
||||
endif()
|
||||
|
||||
if (NOT HAVE_LIBINTL)
|
||||
target_sources(xsystem35 PRIVATE nls.c)
|
||||
endif()
|
||||
|
||||
target_link_libraries(src_lib PRIVATE sdl2 zlib)
|
||||
target_link_libraries(xsystem35 PRIVATE m zlib sdl2 sdl2_ttf)
|
||||
target_link_libraries(src_lib PRIVATE sdl2 freetype2 zlib)
|
||||
target_link_libraries(xsystem35 PRIVATE m zlib sdl2)
|
||||
if (TARGET sdl2_mixer)
|
||||
target_link_libraries(xsystem35 PRIVATE sdl2_mixer)
|
||||
endif()
|
||||
@@ -157,18 +158,28 @@ if (EMSCRIPTEN)
|
||||
# Without optimizations, Asyncify generates very large code.
|
||||
list(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG "-O1")
|
||||
|
||||
if (JSPI)
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sJSPI
|
||||
-sJSPI_IMPORTS=wait_vsync
|
||||
)
|
||||
else()
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_IGNORE_INDIRECT=1
|
||||
-sASYNCIFY_REMOVE=SDL_Delay
|
||||
-sASYNCIFY_IMPORTS=wait_vsync
|
||||
# Functions that call an asynchronous function via a function pointer must
|
||||
# be added to ASYNCIFY_ADD, even if it had a direct call to another
|
||||
# asynchronous function, so that the indirect call itself is instrumented.
|
||||
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_options(xsystem35 PRIVATE
|
||||
-sENVIRONMENT=web
|
||||
-sMODULARIZE=1
|
||||
-sEXPORT_ES6=1
|
||||
-sASYNCIFY=1
|
||||
-sASYNCIFY_IGNORE_INDIRECT=1
|
||||
-sASYNCIFY_REMOVE=SDL_Delay
|
||||
-sASYNCIFY_IMPORTS=wait_vsync
|
||||
# Functions that call an asynchronous function via a function pointer must
|
||||
# be added to ASYNCIFY_ADD, even if it had a direct call to another
|
||||
# asynchronous function, so that the indirect call itself is instrumented.
|
||||
-sASYNCIFY_ADD=commands2F60,nact_main,send_agsevent,cb_waitkey_sprite,modal_run
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sNO_EXIT_RUNTIME=1
|
||||
-sEXPORTED_FUNCTIONS=_main,_malloc
|
||||
@@ -191,12 +202,16 @@ else() # non-emscripten, non-android
|
||||
|
||||
add_executable(src_tests
|
||||
src_tests.c
|
||||
cache_test.c
|
||||
font_test.c
|
||||
gameresource_test.c
|
||||
hankaku_test.c
|
||||
qnt_test.c
|
||||
variable_test.c
|
||||
)
|
||||
target_compile_options(src_tests PRIVATE -Wno-pointer-sign -Wall)
|
||||
target_compile_definitions(src_tests PRIVATE
|
||||
TEST_FONT_DIR="${PROJECT_SOURCE_DIR}/fonts")
|
||||
target_link_libraries(src_tests PRIVATE src_lib sdl2)
|
||||
add_test(NAME src_tests COMMAND src_tests)
|
||||
file(COPY testdata DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -125,7 +125,7 @@ bool ags_check_param_xy(int *x, int *y) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ags_init(const char *render_driver, bool enable_zb) {
|
||||
void ags_init(const char *render_driver) {
|
||||
nact->ags.mouse_warp_enabled = true;
|
||||
nact->ags.world_width = SYS35_DEFAULT_WIDTH;
|
||||
nact->ags.world_height = SYS35_DEFAULT_HEIGHT;
|
||||
@@ -138,8 +138,7 @@ void ags_init(const char *render_driver, bool enable_zb) {
|
||||
nact->ags.font_type = FONT_GOTHIC;
|
||||
nact->ags.text_decoration_type = 0;
|
||||
nact->ags.text_decoration_color = 0;
|
||||
nact->ags.enable_zb = enable_zb;
|
||||
nact->ags.font_weight = enable_zb ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
nact->ags.font_weight = FONT_WEIGHT_BOLD;
|
||||
|
||||
gfx_Initialize(render_driver);
|
||||
event_init();
|
||||
@@ -479,7 +478,7 @@ int ags_drawString(int x, int y, const char *src, int col, int size, SDL_Rect *r
|
||||
SDL_Surface *ags_drawStringToSurface(const char *str, int r, int g, int b, FontSpec font) {
|
||||
char *utf8 = toUTF8(str);
|
||||
SDL_Color color = {r, g, b, 255};
|
||||
SDL_Surface *sf = font_render_text(font, utf8, color);
|
||||
SDL_Surface *sf = font_render_text(font, utf8, color, true);
|
||||
free(utf8);
|
||||
return sf;
|
||||
}
|
||||
@@ -729,10 +728,10 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
if (!for_selection) {
|
||||
if (!sl_is_s380 && !for_selection) {
|
||||
// We can't move the actual cursor in the browser, but can change the
|
||||
// internal mouse coordinates. This can help with keyboard/gamepad
|
||||
// navigation.
|
||||
// navigation in Toushin Toshi 2.
|
||||
event_set_mouse_internal_location(x, y);
|
||||
EM_ASM({ xsystem35.shell.showMouseMoveEffect($0, $1); }, x, y);
|
||||
sys_sleep(cursor_move_time);
|
||||
@@ -750,7 +749,7 @@ void ags_setCursorLocation(int x, int y, bool is_dibgeo, bool for_selection) {
|
||||
sys_sleep(cursor_move_time / 7);
|
||||
}
|
||||
gfx_warpMouse(x, y);
|
||||
} else if (!for_selection) {
|
||||
} else if (!sl_is_s380 && !for_selection) {
|
||||
event_set_mouse_internal_location(x, y);
|
||||
sys_sleep(cursor_move_time);
|
||||
}
|
||||
|
||||
@@ -129,7 +129,6 @@ struct _ags {
|
||||
int text_decoration_color;
|
||||
|
||||
bool mouse_warp_enabled;
|
||||
bool enable_zb;
|
||||
bool noantialias; /* antialias を使用しない */
|
||||
bool noimagecursor; /* リソースファイルのカーソルを読みこまない */
|
||||
};
|
||||
@@ -138,7 +137,7 @@ typedef struct _ags ags_t;
|
||||
extern SDL_Surface *main_surface;
|
||||
|
||||
/* 初期化関係 */
|
||||
void ags_init(const char *render_driver, bool enable_zb);
|
||||
void ags_init(const char *render_driver);
|
||||
void ags_remove(void);
|
||||
void ags_reset(void);
|
||||
|
||||
|
||||
+37
-7
@@ -33,18 +33,34 @@
|
||||
/* drifiles object */
|
||||
static drifiles *dri[DRIFILETYPEMAX];
|
||||
|
||||
/* cache handler for dri file */
|
||||
static cacher *cacheid;
|
||||
static Cache *dri_cache;
|
||||
|
||||
#ifndef ALD_CACHE_SIZE
|
||||
#define ALD_CACHE_SIZE (10 << 20)
|
||||
#endif
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
/*
|
||||
* free dridata
|
||||
* dfile: dridata to be free
|
||||
*/
|
||||
static void ald_free(dridata *dfile) {
|
||||
static void ald_free(void *data) {
|
||||
dridata *dfile = data;
|
||||
free(dfile->data_raw);
|
||||
free(dfile);
|
||||
}
|
||||
|
||||
static bool ald_is_pinned(const void *data) {
|
||||
return ((const dridata *)data)->refcnt != 0;
|
||||
}
|
||||
|
||||
bool ald_is_linked(DRIFILETYPE type, int no) {
|
||||
if (type >= DRIFILETYPEMAX || !dri[type])
|
||||
return false;
|
||||
@@ -80,11 +96,12 @@ dridata *ald_getdata(DRIFILETYPE type, int no) {
|
||||
if (dri[type]->mmapped) return dri_getdata(dri[type], no);
|
||||
|
||||
/* not mmapped */
|
||||
if (NULL == (ddata = (dridata *)cache_foreach(cacheid, (type << 16) + no))) {
|
||||
int key = (type << 16) + no;
|
||||
if (NULL == (ddata = cache_get(dri_cache, &key))) {
|
||||
ddata = dri_getdata(dri[type], no);
|
||||
if (ddata != NULL) {
|
||||
ddata->refcnt = 0;
|
||||
cache_insert(cacheid, (type << 16) + no, (void *)ddata, ddata->size, &(ddata->refcnt));
|
||||
ddata->cached = cache_insert(dri_cache, &key, ddata, ddata->size) == CACHE_INSERT_OK;
|
||||
}
|
||||
}
|
||||
if (ddata != NULL)
|
||||
@@ -105,6 +122,8 @@ void ald_freedata(dridata *data) {
|
||||
free(data);
|
||||
} else {
|
||||
data->refcnt--;
|
||||
if (!data->cached && data->refcnt == 0)
|
||||
ald_free(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +131,15 @@ void ald_init(int type, const char **file, int cnt, bool use_mmap) {
|
||||
if (type >= DRIFILETYPEMAX || cnt <= 0)
|
||||
return;
|
||||
dri[type] = dri_init(file, cnt, use_mmap);
|
||||
if (!dri[type]->mmapped) {
|
||||
cacheid = cache_new(ald_free);
|
||||
if (!dri[type]->mmapped && !dri_cache) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = ald_free,
|
||||
.is_pinned = ald_is_pinned,
|
||||
};
|
||||
dri_cache = cache_new((size_t)ALD_CACHE_SIZE, &ops);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,3 +148,7 @@ int ald_get_maxno(DRIFILETYPE type) {
|
||||
return 0;
|
||||
return dri[type]->maxno;
|
||||
}
|
||||
|
||||
CacheStats ald_get_cache_stats(void) {
|
||||
return cache_get_stats(dri_cache);
|
||||
}
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
#define __ALD_MANAGER__
|
||||
|
||||
#include "portab.h"
|
||||
#include "cache.h"
|
||||
#include "dri.h"
|
||||
|
||||
#define DRIFILETYPEMAX 7
|
||||
@@ -44,6 +45,6 @@ bool ald_exists(DRIFILETYPE type, int no);
|
||||
dridata *ald_getdata(DRIFILETYPE type, int no);
|
||||
void ald_freedata(dridata *data);
|
||||
int ald_get_maxno(DRIFILETYPE type);
|
||||
CacheStats ald_get_cache_stats(void);
|
||||
|
||||
#endif /* !__ALD_MANAGER__ */
|
||||
|
||||
|
||||
+6
-3
@@ -111,11 +111,14 @@ bool musbgm_play(int no, int time, int vol, int loop_count) {
|
||||
if (!bgm_load(no))
|
||||
return false;
|
||||
|
||||
// We don't use the loop information in the BGI file, but SDL_mixer
|
||||
// understands loop info in the WAVE's "smpl" chunk.
|
||||
// We don't use the loop positions in the BGI file; SDL_mixer instead
|
||||
// understands loop positions in the WAVE's "smpl" chunk.
|
||||
|
||||
apply_music_volume(vol);
|
||||
if (Mix_FadeInMusic(mix_music, loop_count == 0 ? -1 : loop_count, time * 10) != 0) {
|
||||
// SDL_mixer counts repeats after the first play, while System 3.x counts
|
||||
// the total number of plays. In both APIs, an infinite loop is special.
|
||||
int loops = loop_count == 0 ? -1 : loop_count - 1;
|
||||
if (Mix_FadeInMusic(mix_music, loops, time * 10) != 0) {
|
||||
free_music();
|
||||
return false;
|
||||
}
|
||||
|
||||
+162
-115
@@ -1,8 +1,5 @@
|
||||
/*
|
||||
* cache.c general cache manager
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,130 +16,180 @@
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
/* $Id: cache.c,v 1.5 2003/07/21 23:06:47 chikama Exp $ */
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include "portab.h"
|
||||
#include "cache.h"
|
||||
|
||||
/* maximum cache size (in MB) */
|
||||
#ifndef CACHE_TOTALSIZE
|
||||
#define CACHE_TOTALSIZE 20
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int totalsize; /* total size in cache */
|
||||
static int dummyfalse = 0; /* dummy in_use flag */
|
||||
static int dummytrue = 1; /* dummy in_use flag */
|
||||
#define CACHE_BUCKETS 1024
|
||||
|
||||
/*
|
||||
* static methods
|
||||
*/
|
||||
static void remove_in_cache(cacher *id);
|
||||
typedef struct CacheEntry {
|
||||
struct CacheEntry *hash_next;
|
||||
struct CacheEntry *lru_prev;
|
||||
struct CacheEntry *lru_next;
|
||||
uint32_t hash;
|
||||
size_t cost;
|
||||
void *data;
|
||||
unsigned char key[];
|
||||
} CacheEntry;
|
||||
|
||||
/*
|
||||
* Remove data in cache
|
||||
* id: cache handler
|
||||
*/
|
||||
static void remove_in_cache(cacher *id) {
|
||||
cacheinfo *ip = id->top;
|
||||
cacheinfo *ic = ip->next;
|
||||
if (!ic)
|
||||
return;
|
||||
|
||||
while(ic->next != NULL) {
|
||||
if (!*ic->in_use) {
|
||||
totalsize -= ic->size;
|
||||
ip->next = ic->next;
|
||||
id->free_(ic->data);
|
||||
free(ic);
|
||||
} else {
|
||||
ip = ic;
|
||||
}
|
||||
ic = ip->next;
|
||||
}
|
||||
return;
|
||||
struct Cache {
|
||||
CacheOps ops;
|
||||
CacheEntry *buckets[CACHE_BUCKETS];
|
||||
CacheEntry *lru_head;
|
||||
CacheEntry *lru_tail;
|
||||
size_t capacity;
|
||||
size_t size;
|
||||
size_t count;
|
||||
size_t hits;
|
||||
size_t misses;
|
||||
};
|
||||
|
||||
static bool is_pinned(const Cache *cache, const CacheEntry *entry) {
|
||||
return cache->ops.is_pinned && cache->ops.is_pinned(entry->data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create new cache object
|
||||
* delcallback: callback function for delete cache data object
|
||||
* return: new cache handler
|
||||
*/
|
||||
cacher *cache_new(void *delcallback) {
|
||||
cacher *c = calloc(1, sizeof(cacher));
|
||||
|
||||
c->top = calloc(1, sizeof(cacheinfo));
|
||||
c->top->next = NULL;
|
||||
c->top->in_use = &dummytrue;
|
||||
c->free_ = delcallback;
|
||||
return c;
|
||||
static void lru_remove(Cache *cache, CacheEntry *entry) {
|
||||
if (entry->lru_prev)
|
||||
entry->lru_prev->lru_next = entry->lru_next;
|
||||
else
|
||||
cache->lru_head = entry->lru_next;
|
||||
if (entry->lru_next)
|
||||
entry->lru_next->lru_prev = entry->lru_prev;
|
||||
else
|
||||
cache->lru_tail = entry->lru_prev;
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert data to cache
|
||||
* id : cache handler
|
||||
* key : data key
|
||||
* data : data to be cached
|
||||
* size : data size
|
||||
* in_use: in_use mark pointer, if in_use is nonzero, dont remove from cache
|
||||
*/
|
||||
void cache_insert(cacher *id, int key, void *data, int size, int *in_use) {
|
||||
cacheinfo *i = id->top;
|
||||
|
||||
if (CACHE_TOTALSIZE <= (totalsize >> 20)) {
|
||||
remove_in_cache(id);
|
||||
}
|
||||
|
||||
while(i->next != NULL) {
|
||||
i = i->next;
|
||||
}
|
||||
|
||||
i->key = key;
|
||||
i->data = data;
|
||||
i->size = size;
|
||||
i->next = calloc(1, sizeof(cacheinfo));
|
||||
i->next->next = NULL;
|
||||
if (in_use) {
|
||||
i->in_use = in_use;
|
||||
} else {
|
||||
i->in_use = &dummyfalse;
|
||||
}
|
||||
totalsize += size;
|
||||
static void lru_prepend(Cache *cache, CacheEntry *entry) {
|
||||
entry->lru_prev = NULL;
|
||||
entry->lru_next = cache->lru_head;
|
||||
if (cache->lru_head)
|
||||
cache->lru_head->lru_prev = entry;
|
||||
else
|
||||
cache->lru_tail = entry;
|
||||
cache->lru_head = entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search data in cache
|
||||
* id : cache handler
|
||||
* key: data search key
|
||||
* return: pointer to cached data
|
||||
*/
|
||||
void *cache_foreach(cacher *id, int key) {
|
||||
cacheinfo *i = id->top;
|
||||
|
||||
while(i != NULL) {
|
||||
if (i->key == key) {
|
||||
return i->data;
|
||||
}
|
||||
i = i->next;
|
||||
static void remove_entry(Cache *cache, CacheEntry *entry) {
|
||||
CacheEntry **link = &cache->buckets[entry->hash % CACHE_BUCKETS];
|
||||
while (*link != entry)
|
||||
link = &(*link)->hash_next;
|
||||
*link = entry->hash_next;
|
||||
lru_remove(cache, entry);
|
||||
cache->size -= entry->cost;
|
||||
cache->count--;
|
||||
cache->ops.destroy(entry->data);
|
||||
free(entry);
|
||||
}
|
||||
|
||||
static CacheEntry *find_entry(Cache *cache, const void *key, uint32_t hash) {
|
||||
for (CacheEntry *entry = cache->buckets[hash % CACHE_BUCKETS]; entry;
|
||||
entry = entry->hash_next) {
|
||||
if (entry->hash == hash && cache->ops.equal(entry->key, key))
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cache_clear(cacher *id) {
|
||||
cacheinfo *ic = id->top->next;
|
||||
cacheinfo *next;
|
||||
|
||||
while (ic != NULL) {
|
||||
next = ic->next;
|
||||
if (ic->data) {
|
||||
totalsize -= ic->size;
|
||||
id->free_(ic->data);
|
||||
}
|
||||
free(ic);
|
||||
ic = next;
|
||||
}
|
||||
id->top->next = NULL;
|
||||
Cache *cache_new(size_t capacity, const CacheOps *ops) {
|
||||
if (!ops || !ops->key_size || !ops->hash || !ops->equal || !ops->destroy)
|
||||
return NULL;
|
||||
Cache *cache = calloc(1, sizeof(Cache));
|
||||
if (!cache)
|
||||
return NULL;
|
||||
cache->ops = *ops;
|
||||
cache->capacity = capacity;
|
||||
return cache;
|
||||
}
|
||||
|
||||
void cache_destroy(Cache *cache) {
|
||||
if (!cache)
|
||||
return;
|
||||
while (cache->lru_tail)
|
||||
remove_entry(cache, cache->lru_tail);
|
||||
free(cache);
|
||||
}
|
||||
|
||||
void *cache_get(Cache *cache, const void *key) {
|
||||
if (!cache || !key)
|
||||
return NULL;
|
||||
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
|
||||
if (!entry) {
|
||||
cache->misses++;
|
||||
return NULL;
|
||||
}
|
||||
cache->hits++;
|
||||
lru_remove(cache, entry);
|
||||
lru_prepend(cache, entry);
|
||||
return entry->data;
|
||||
}
|
||||
|
||||
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost) {
|
||||
if (!cache || !key || !data)
|
||||
return CACHE_INSERT_NOMEM;
|
||||
uint32_t hash = cache->ops.hash(key);
|
||||
if (find_entry(cache, key, hash))
|
||||
return CACHE_INSERT_EXISTS;
|
||||
if (cost > cache->capacity)
|
||||
return CACHE_INSERT_FULL;
|
||||
|
||||
CacheEntry *entry = malloc(sizeof(CacheEntry) + cache->ops.key_size);
|
||||
if (!entry)
|
||||
return CACHE_INSERT_NOMEM;
|
||||
|
||||
for (CacheEntry *old = cache->lru_tail, *prev;
|
||||
old && cache->size > cache->capacity - cost; old = prev) {
|
||||
prev = old->lru_prev;
|
||||
if (!is_pinned(cache, old))
|
||||
remove_entry(cache, old);
|
||||
}
|
||||
if (cache->size > cache->capacity - cost) {
|
||||
free(entry);
|
||||
return CACHE_INSERT_FULL;
|
||||
}
|
||||
|
||||
entry->hash = hash;
|
||||
entry->cost = cost;
|
||||
entry->data = data;
|
||||
memcpy(entry->key, key, cache->ops.key_size);
|
||||
unsigned bucket = hash % CACHE_BUCKETS;
|
||||
entry->hash_next = cache->buckets[bucket];
|
||||
cache->buckets[bucket] = entry;
|
||||
lru_prepend(cache, entry);
|
||||
cache->size += cost;
|
||||
cache->count++;
|
||||
return CACHE_INSERT_OK;
|
||||
}
|
||||
|
||||
bool cache_remove(Cache *cache, const void *key) {
|
||||
if (!cache || !key)
|
||||
return false;
|
||||
CacheEntry *entry = find_entry(cache, key, cache->ops.hash(key));
|
||||
if (!entry || is_pinned(cache, entry))
|
||||
return false;
|
||||
remove_entry(cache, entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t cache_clear(Cache *cache) {
|
||||
if (!cache)
|
||||
return 0;
|
||||
for (CacheEntry *entry = cache->lru_tail, *prev; entry; entry = prev) {
|
||||
prev = entry->lru_prev;
|
||||
if (!is_pinned(cache, entry))
|
||||
remove_entry(cache, entry);
|
||||
}
|
||||
return cache->count;
|
||||
}
|
||||
|
||||
CacheStats cache_get_stats(const Cache *cache) {
|
||||
if (!cache)
|
||||
return (CacheStats){0};
|
||||
return (CacheStats){
|
||||
.count = cache->count,
|
||||
.size = cache->size,
|
||||
.capacity = cache->capacity,
|
||||
.hits = cache->hits,
|
||||
.misses = cache->misses,
|
||||
};
|
||||
}
|
||||
|
||||
+46
-28
@@ -1,8 +1,5 @@
|
||||
/*
|
||||
* cache.h general cache manager
|
||||
*
|
||||
* Copyright (C) 1997-1998 Masaki Chikama (Wren) <chikama@kasumi.ipl.mech.nagoya-u.ac.jp>
|
||||
* 1998- <masaki-c@is.aist-nara.ac.jp>
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -19,33 +16,54 @@
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
/* $Id: cache.h,v 1.2 2003/07/21 23:06:47 chikama Exp $ */
|
||||
#ifndef XSYSTEM35_CACHE_H
|
||||
#define XSYSTEM35_CACHE_H
|
||||
|
||||
#ifndef __CASHE__
|
||||
#define __CASHE__
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "portab.h"
|
||||
typedef struct Cache Cache;
|
||||
|
||||
/* cache controlr infomartion */
|
||||
struct _cacheinfo {
|
||||
int key; /* key of data */
|
||||
int size; /* data size */
|
||||
struct _cacheinfo *next; /* next data */
|
||||
int *in_use; /* if *in_use is nonzero, dont remove from cache */
|
||||
void *data; /* real data */
|
||||
};
|
||||
typedef struct _cacheinfo cacheinfo;
|
||||
typedef struct {
|
||||
size_t key_size;
|
||||
uint32_t (*hash)(const void *key);
|
||||
bool (*equal)(const void *a, const void *b);
|
||||
void (*destroy)(void *data);
|
||||
bool (*is_pinned)(const void *data);
|
||||
} CacheOps;
|
||||
|
||||
/* cache handler */
|
||||
struct _cacher {
|
||||
void (*free_)(void *); /* free data callback */
|
||||
struct _cacheinfo *top; /* pointer to data */
|
||||
};
|
||||
typedef struct _cacher cacher;
|
||||
typedef enum {
|
||||
CACHE_INSERT_OK,
|
||||
CACHE_INSERT_EXISTS,
|
||||
CACHE_INSERT_FULL,
|
||||
CACHE_INSERT_NOMEM,
|
||||
} CacheInsertResult;
|
||||
|
||||
extern cacher *cache_new(void *delcallback);
|
||||
extern void cache_insert(cacher *id, int key, void *data, int size, int *in_use);
|
||||
extern void *cache_foreach(cacher *id, int key);
|
||||
extern void cache_clear(cacher *id);
|
||||
typedef struct {
|
||||
size_t count;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
size_t hits;
|
||||
size_t misses;
|
||||
} CacheStats;
|
||||
|
||||
#endif /* !__CASHE__ */
|
||||
Cache *cache_new(size_t capacity, const CacheOps *ops);
|
||||
void cache_destroy(Cache *cache);
|
||||
|
||||
/* The returned pointer is owned by the cache and is invalidated by removal. */
|
||||
void *cache_get(Cache *cache, const void *key);
|
||||
|
||||
/* Ownership of data is transferred only when CACHE_INSERT_OK is returned. */
|
||||
CacheInsertResult cache_insert(Cache *cache, const void *key, void *data, size_t cost);
|
||||
|
||||
/* Pinned entries cannot be removed. */
|
||||
bool cache_remove(Cache *cache, const void *key);
|
||||
|
||||
/* Removes all unpinned entries and returns the number of entries left. */
|
||||
size_t cache_clear(Cache *cache);
|
||||
|
||||
/* Returns zero-filled statistics when cache is NULL. */
|
||||
CacheStats cache_get_stats(const Cache *cache);
|
||||
|
||||
#endif /* XSYSTEM35_CACHE_H */
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "unittest.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
int value;
|
||||
bool pinned;
|
||||
} TestValue;
|
||||
|
||||
static int destroyed;
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
static void destroy_value(void *data) {
|
||||
destroyed++;
|
||||
free(data);
|
||||
}
|
||||
|
||||
static bool value_is_pinned(const void *data) {
|
||||
return ((const TestValue *)data)->pinned;
|
||||
}
|
||||
|
||||
static TestValue *new_value(int value) {
|
||||
TestValue *data = malloc(sizeof(TestValue));
|
||||
ASSERT_TRUE(data);
|
||||
data->value = value;
|
||||
data->pinned = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
void cache_test(void) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = destroy_value,
|
||||
.is_pinned = value_is_pinned,
|
||||
};
|
||||
Cache *cache = cache_new(2, &ops);
|
||||
ASSERT_TRUE(cache);
|
||||
destroyed = 0;
|
||||
CacheStats stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.count, 0);
|
||||
ASSERT_EQUAL(stats.size, 0);
|
||||
ASSERT_EQUAL(stats.capacity, 2);
|
||||
ASSERT_EQUAL(stats.hits, 0);
|
||||
ASSERT_EQUAL(stats.misses, 0);
|
||||
|
||||
// A lookup makes k1 most-recently used, so inserting k3 evicts k2.
|
||||
int k1 = 1, k2 = 2, k3 = 3, k4 = 4, k5 = 5;
|
||||
ASSERT_EQUAL(cache_insert(cache, &k1, new_value(1), 1), CACHE_INSERT_OK);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k2, new_value(2), 1), CACHE_INSERT_OK);
|
||||
stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.count, 2);
|
||||
ASSERT_EQUAL(stats.size, 2);
|
||||
ASSERT_EQUAL(stats.capacity, 2);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k3, new_value(3), 1), CACHE_INSERT_OK);
|
||||
ASSERT_NULL(cache_get(cache, &k2));
|
||||
stats = cache_get_stats(cache);
|
||||
ASSERT_EQUAL(stats.hits, 1);
|
||||
ASSERT_EQUAL(stats.misses, 1);
|
||||
|
||||
// Pinned entries survive both capacity eviction and cache_clear().
|
||||
((TestValue *)cache_get(cache, &k1))->pinned = true;
|
||||
ASSERT_EQUAL(cache_insert(cache, &k4, new_value(4), 1), CACHE_INSERT_OK);
|
||||
ASSERT_NULL(cache_get(cache, &k3));
|
||||
((TestValue *)cache_get(cache, &k4))->pinned = true;
|
||||
|
||||
// Insertion fails when every remaining entry is pinned.
|
||||
TestValue *rejected = new_value(5);
|
||||
ASSERT_EQUAL(cache_insert(cache, &k5, rejected, 1), CACHE_INSERT_FULL);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k1))->value, 1);
|
||||
ASSERT_EQUAL(((TestValue *)cache_get(cache, &k4))->value, 4);
|
||||
ASSERT_EQUAL(cache_clear(cache), 2);
|
||||
ASSERT_EQUAL(destroyed, 2);
|
||||
free(rejected);
|
||||
|
||||
// Once unpinned, cache_clear() destroys the remaining entries.
|
||||
((TestValue *)cache_get(cache, &k1))->pinned = false;
|
||||
((TestValue *)cache_get(cache, &k4))->pinned = false;
|
||||
ASSERT_EQUAL(cache_clear(cache), 0);
|
||||
ASSERT_EQUAL(destroyed, 4);
|
||||
cache_destroy(cache);
|
||||
|
||||
stats = cache_get_stats(NULL);
|
||||
ASSERT_EQUAL(stats.count, 0);
|
||||
ASSERT_EQUAL(stats.size, 0);
|
||||
ASSERT_EQUAL(stats.capacity, 0);
|
||||
ASSERT_EQUAL(stats.hits, 0);
|
||||
ASSERT_EQUAL(stats.misses, 0);
|
||||
}
|
||||
@@ -68,8 +68,11 @@ int cg_brightness;
|
||||
static CG_WHERETODISP loc_policy, loc_policy0;
|
||||
static SDL_Point loc_where, loc_where0;
|
||||
|
||||
/* extracted cg data cache control object */
|
||||
static cacher *cacheid;
|
||||
static Cache *cg_cache;
|
||||
|
||||
#ifndef CG_CACHE_SIZE
|
||||
#define CG_CACHE_SIZE (10 << 20)
|
||||
#endif
|
||||
|
||||
/* static methods */
|
||||
static CG_TYPE check_cgformat(uint8_t *data);
|
||||
@@ -78,6 +81,23 @@ static SDL_Point set_display_loc(cgdata *cg);
|
||||
static void clear_display_loc();
|
||||
static void display_cg(cgdata *cg, int x, int y, int sprite_color, bool alpha_blend);
|
||||
static cgdata *loader(int no);
|
||||
static void release_cg(cgdata *cg);
|
||||
|
||||
static uint32_t int_hash(const void *key) {
|
||||
return (uint32_t)*(const int *)key;
|
||||
}
|
||||
|
||||
static bool int_equal(const void *a, const void *b) {
|
||||
return *(const int *)a == *(const int *)b;
|
||||
}
|
||||
|
||||
static void destroy_cg(void *data) {
|
||||
cgdata_free(data);
|
||||
}
|
||||
|
||||
static bool cg_is_pinned(const void *data) {
|
||||
return ((const cgdata *)data)->refcnt != 0;
|
||||
}
|
||||
|
||||
static int compare_integers(const void *a, const void *b) {
|
||||
int int_a = *(const int *)a;
|
||||
@@ -244,7 +264,10 @@ static cgdata *loader(int no) {
|
||||
cgdata *cg = NULL;
|
||||
|
||||
/* search in cache */
|
||||
if (NULL != (cg = (cgdata *)cache_foreach(cacheid, no))) return cg;
|
||||
if (NULL != (cg = cache_get(cg_cache, &no))) {
|
||||
cg->refcnt++;
|
||||
return cg;
|
||||
}
|
||||
|
||||
/* read from file */
|
||||
if (NULL == (dfile = ald_getdata(DRIFILE_CG, no))) return NULL;
|
||||
@@ -286,8 +309,9 @@ static cgdata *loader(int no) {
|
||||
cg_mosaic(cg);
|
||||
}
|
||||
/* insert to cache */
|
||||
int size = cg->width * cg->height * (cg->depth / 8);
|
||||
cache_insert(cacheid, no, cg, size, NULL);
|
||||
size_t size = (size_t)cg->width * cg->height * (cg->depth / 8);
|
||||
cg->refcnt = 1;
|
||||
cg->cached = cache_insert(cg_cache, &no, cg, size) == CACHE_INSERT_OK;
|
||||
}
|
||||
|
||||
/* ok to free */
|
||||
@@ -296,11 +320,27 @@ static cgdata *loader(int no) {
|
||||
return cg;
|
||||
}
|
||||
|
||||
static void release_cg(cgdata *cg) {
|
||||
if (!cg)
|
||||
return;
|
||||
cg->refcnt--;
|
||||
if (!cg->cached && cg->refcnt == 0)
|
||||
cgdata_free(cg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initilize cache
|
||||
*/
|
||||
void cg_init(void) {
|
||||
cacheid = cache_new(cgdata_free);
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(int),
|
||||
.hash = int_hash,
|
||||
.equal = int_equal,
|
||||
.destroy = destroy_cg,
|
||||
.is_pinned = cg_is_pinned,
|
||||
};
|
||||
cache_destroy(cg_cache);
|
||||
cg_cache = cache_new((size_t)CG_CACHE_SIZE, &ops);
|
||||
cg_reset();
|
||||
}
|
||||
|
||||
@@ -314,6 +354,10 @@ void cg_reset(void) {
|
||||
memset(&loc_where0, 0, sizeof(loc_where0));
|
||||
}
|
||||
|
||||
CacheStats cg_get_cache_stats(void) {
|
||||
return cache_get_stats(cg_cache);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set cg display location
|
||||
* x : display location x
|
||||
@@ -405,6 +449,7 @@ void cg_load(int no, int flg) {
|
||||
}
|
||||
/* clear display offset */
|
||||
clear_display_loc();
|
||||
release_cg(cg);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -413,7 +458,7 @@ void cg_load(int no, int flg) {
|
||||
* shadowno: file no for alpha ( >= 0 )
|
||||
*/
|
||||
void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
cgdata *cg = NULL, *scg;
|
||||
cgdata *cg = NULL, *scg = NULL;
|
||||
SDL_Point p;
|
||||
|
||||
/* load pixel */
|
||||
@@ -421,15 +466,15 @@ void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
if (NULL == (cg = loader(cgno))) return;
|
||||
if (cg->type != ALCG_PMS16) {
|
||||
WARNING("commandGX cg_no != 16bitPMS");
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* load alpha pixel */
|
||||
if (NULL == (scg = loader(shadowno))) return;
|
||||
if (NULL == (scg = loader(shadowno))) goto cleanup;
|
||||
if (scg->type != ALCG_PMS8) {
|
||||
WARNING("commandGX shadow_no != 8bitPMS");
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* set alpha pixel offset */
|
||||
@@ -445,6 +490,10 @@ void cg_load_with_alpha(int cgno, int shadowno) {
|
||||
|
||||
/* clear display offset */
|
||||
clear_display_loc();
|
||||
|
||||
cleanup:
|
||||
release_cg(cg);
|
||||
release_cg(scg);
|
||||
}
|
||||
|
||||
static uint8_t* load_cg_from_file(char *fname_utf8, int *status, long *filesize) {
|
||||
@@ -555,6 +604,7 @@ void cg_get_info(int no, SDL_Rect *info) {
|
||||
info->y = p.y;
|
||||
info->w = cg->width;
|
||||
info->h = cg->height;
|
||||
release_cg(cg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,8 +700,8 @@ SDL_Surface *cg_load_as_sdlsurface(int no) {
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void load_censor_list(const char *path) {
|
||||
if (cacheid)
|
||||
cache_clear(cacheid);
|
||||
if (cg_cache)
|
||||
cache_clear(cg_cache);
|
||||
if (censor_list) {
|
||||
free(censor_list);
|
||||
censor_list = NULL;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define __CG__
|
||||
|
||||
#include <SDL_rect.h>
|
||||
#include "cache.h"
|
||||
#include "portab.h"
|
||||
|
||||
struct SDL_Surface;
|
||||
@@ -61,6 +62,8 @@ typedef struct {
|
||||
|
||||
int vsp_bank; // palette bank for vsp
|
||||
int pms_bank; // palette bank for pms
|
||||
int refcnt; // references held by cache users
|
||||
bool cached; // owned by the CG cache
|
||||
} cgdata;
|
||||
|
||||
/*
|
||||
@@ -86,6 +89,7 @@ extern void cgdata_free(cgdata *cg);
|
||||
extern struct SDL_Surface *cg_load_as_sdlsurface(int no);
|
||||
struct SDL_Surface *cg_load_as_sdlsurface_from_data(uint8_t *data, size_t size, bool mosaic, bool as_alpha);
|
||||
extern void load_censor_list(const char *path);
|
||||
extern CacheStats cg_get_cache_stats(void);
|
||||
|
||||
extern int cg_vspPB;
|
||||
extern int cg_fflg;
|
||||
|
||||
+3
-3
@@ -562,7 +562,7 @@ void commands2F52() {
|
||||
void commands2F53() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_ec = eFlag;
|
||||
nact->patch_ec = (eFlag == 1);
|
||||
|
||||
TRACE("patchEC %d:", eFlag);
|
||||
}
|
||||
@@ -936,7 +936,7 @@ void commands2F6F() {
|
||||
void commands2F70() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_emen = eFlag;
|
||||
nact->patch_emen = (eFlag == 1);
|
||||
|
||||
TRACE("patchEMEN %d:", eFlag);
|
||||
}
|
||||
@@ -1152,7 +1152,7 @@ void commands2F85() {
|
||||
void commands2F86() {
|
||||
int eFlag = getCaliValue();
|
||||
|
||||
nact->patch_g0 = eFlag;
|
||||
nact->patch_g0 = (eFlag == 1);
|
||||
|
||||
TRACE("patchG0 %d:", eFlag);
|
||||
}
|
||||
|
||||
+5
-5
@@ -55,13 +55,13 @@ void commandEC() {
|
||||
int num = getCaliValue();
|
||||
Ecomtbl *e;
|
||||
SDL_Rect *r;
|
||||
int adj = nact->patch_ec ? 0 : -1;
|
||||
|
||||
if (num == 0) {
|
||||
r = &nact->ags.view_area;
|
||||
ags_fillRectangle(r->x, r->y, r->w, r->h, 0);
|
||||
ags_updateArea(r->x, r->y, r->w, r->h);
|
||||
ags_fillRectangle(r->x, r->y, r->w + adj, r->h + adj, 0);
|
||||
ags_updateArea(r->x, r->y, r->w + adj, r->h + adj);
|
||||
} else {
|
||||
int adj = (nact->patch_ec == 0) ? -1 : 0;
|
||||
e = &tbl[num - 1];
|
||||
ags_fillRectangle(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj, e->pal);
|
||||
ags_updateArea(e->r.x, e->r.y, e->r.w + adj, e->r.h + adj);
|
||||
@@ -92,7 +92,7 @@ void commandEM() {
|
||||
|
||||
SDL_Point p = {x, y};
|
||||
SDL_Rect r = tbl[num - 1].r;
|
||||
if (nact->patch_emen == 0) {
|
||||
if (!nact->patch_emen) {
|
||||
r.w -= 1;
|
||||
r.h -= 1;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ void commandEN() {
|
||||
|
||||
for (int i = min; i <= max; i++) {
|
||||
SDL_Rect r = tbl[i - 1].r;
|
||||
if (nact->patch_emen == 0) {
|
||||
if (!nact->patch_emen) {
|
||||
r.w -= 1;
|
||||
r.h -= 1;
|
||||
}
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ void commandY() {
|
||||
}
|
||||
break;
|
||||
case 1900:
|
||||
nact->patch_ec = p2;
|
||||
nact->patch_ec = (p2 == 1);
|
||||
// We're sure this game was built for System3.9 v5.50, which by default
|
||||
// routes CD-DA commands to DRIFILE_WAVE.
|
||||
muscd_init_bgm(DRIFILE_WAVE, 999);
|
||||
|
||||
+3
-20
@@ -114,8 +114,7 @@ void commandZB() {
|
||||
/* メッセージ文字を太さを設定 */
|
||||
int weight = getCaliValue();
|
||||
|
||||
if (nact->ags.enable_zb)
|
||||
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
nact->ags.font_weight = weight > 5 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_NORMAL;
|
||||
|
||||
TRACE("ZB %d:", weight);
|
||||
}
|
||||
@@ -377,24 +376,8 @@ void commandZZ1() {
|
||||
void commandZZ2() {
|
||||
/* 機種文字列を文字列領域 num に返す(MAX12文字)*/
|
||||
int num = getCaliValue();
|
||||
#if defined(linux)
|
||||
static uint8_t str[] = {0x82, 0x6B, 0x82, 0x89, 0x82, 0x8E, 0x82, 0x95, 0x82, 0x98, 0};
|
||||
#elif defined(__FreeBSD__)
|
||||
static uint8_t str[] = {0x82, 0x65, 0x82, 0x92, 0x82, 0x85, 0x82, 0x85, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(__OpenBSD__)
|
||||
static uint8_t str[] = {0x82, 0x6E, 0x82, 0x90, 0x82, 0x85, 0x82, 0x8E, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(__NetBSD__)
|
||||
static uint8_t str[] = {0x82, 0x6D, 0x82, 0x85, 0x82, 0x94, 0x82, 0x61, 0x82, 0x72, 0x82, 0x63, 0};
|
||||
#elif defined(sun)
|
||||
static uint8_t str[] = {0x82, 0x72, 0x82, 0x95, 0x82, 0x8E, 0x82, 0x6E, 0x82, 0x72, 0};
|
||||
#elif defined(__osf__)
|
||||
static uint8_t str[] = {0x82, 0x63, 0x82, 0x89, 0x82, 0x87, 0x82, 0x89, 0x82, 0x94, 0x82, 0x81, 0x82, 0x8C, 0x82, 0x74, 0x82, 0x6D, 0x82, 0x68, 0x82, 0x77, 0};
|
||||
#elif defined(sgi)
|
||||
static uint8_t str[] = {0x82, 0x68, 0x82, 0x71, 0x82, 0x68, 0x82, 0x77, 0};
|
||||
#else
|
||||
static uint8_t str[] = {0x82, 0x74, 0x82, 0x8e, 0x82, 0x8b, 0x82, 0x8e, 0x82, 0x8f, 0x82, 0x97, 0x82, 0x8e, 0};
|
||||
#endif
|
||||
svar_set(num, str);
|
||||
|
||||
svar_set(num, SDL_GetPlatform());
|
||||
|
||||
TRACE("ZZ2 %d:",num);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include "debugger.h"
|
||||
#include "debugger_private.h"
|
||||
#include "debug_symbol.h"
|
||||
#include "ald_manager.h"
|
||||
#include "cg.h"
|
||||
#include "font.h"
|
||||
#include "nact.h"
|
||||
#include "variable.h"
|
||||
#ifdef HAVE_SIGACTION
|
||||
@@ -207,6 +210,34 @@ static const char * const help_help = NULL;
|
||||
|
||||
static CommandResult cmd_help(void);
|
||||
|
||||
static const char desc_info[] = "Print information about the program.";
|
||||
static const char help_info[] =
|
||||
"Syntax: info cache\n"
|
||||
"\n"
|
||||
"Displays usage and lookup statistics for each cache.";
|
||||
|
||||
static void print_cache_stats(const char *name, CacheStats stats) {
|
||||
size_t lookups = stats.hits + stats.misses;
|
||||
double hit_rate = lookups ? 100.0 * stats.hits / lookups : 0.0;
|
||||
printf("%-8s %8zu %12zu %12zu %8zu %8zu %8.1f%%\n",
|
||||
name, stats.count, stats.size, stats.capacity,
|
||||
stats.hits, stats.misses, hit_rate);
|
||||
}
|
||||
|
||||
static CommandResult cmd_info(void) {
|
||||
char *arg = strtok(NULL, whitespaces);
|
||||
if (!arg || strcmp(arg, "cache") || strtok(NULL, whitespaces)) {
|
||||
puts(help_info);
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
puts("Cache Entries Size Capacity Hits Misses Hit rate");
|
||||
print_cache_stats("Glyph", font_get_cache_stats());
|
||||
print_cache_stats("CG", cg_get_cache_stats());
|
||||
print_cache_stats("Archive", ald_get_cache_stats());
|
||||
return CONTINUE_REPL;
|
||||
}
|
||||
|
||||
static const char desc_list[] = "List specified function or line.";
|
||||
static const char help_list[] =
|
||||
"Syntax: list\n"
|
||||
@@ -361,6 +392,7 @@ const Command dbg_cui_commands[] = {
|
||||
{"continue", "c", desc_continue, help_continue, cmd_continue},
|
||||
{"delete", "d", desc_delete, help_delete, cmd_delete},
|
||||
{"help", "h", desc_help, help_help, cmd_help},
|
||||
{"info", "i", desc_info, help_info, cmd_info},
|
||||
{"list", "l", desc_list, help_list, cmd_list},
|
||||
{"step", "s", desc_step, help_step, cmd_step},
|
||||
{"finish", NULL, desc_finish, help_finish, cmd_finish},
|
||||
|
||||
@@ -46,6 +46,7 @@ struct _dridata {
|
||||
char *data; /* real data */
|
||||
char *name; /* not used */
|
||||
int refcnt; /* reference count */
|
||||
bool cached; /* owned by the archive cache */
|
||||
drifiles *a; /* archive file obj */
|
||||
};
|
||||
typedef struct _dridata dridata;
|
||||
|
||||
+343
-147
@@ -19,10 +19,15 @@
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_ttf.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_BITMAP_H
|
||||
#include FT_OUTLINE_H
|
||||
#include FT_TRUETYPE_TABLES_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "win/resources.h"
|
||||
@@ -34,181 +39,366 @@
|
||||
|
||||
#include "portab.h"
|
||||
#include "system.h"
|
||||
#include "cache.h"
|
||||
#include "font.h"
|
||||
#include "gfx.h"
|
||||
#include "hacks.h"
|
||||
#include "utfsjis.h"
|
||||
|
||||
// 26.6 fixed point helpers.
|
||||
#define FT_FLOOR(x) ((x) >> 6)
|
||||
#define FT_CEIL(x) (((x) + 63) >> 6)
|
||||
|
||||
#define GLYPH_CACHE_LIMIT (4 * 1024 * 1024)
|
||||
|
||||
typedef struct {
|
||||
int size;
|
||||
FontType type;
|
||||
TTF_Font *id;
|
||||
} FontTable;
|
||||
FT_Face face;
|
||||
int ascent; // distance from the top of a rendered surface to the baseline
|
||||
int height; // height of a rendered surface
|
||||
} Font;
|
||||
|
||||
#define FONTTABLEMAX 256
|
||||
static FontTable fonttbl[FONTTABLEMAX];
|
||||
static int fontcnt = 0;
|
||||
static FT_Library ft_library;
|
||||
|
||||
typedef struct Glyph {
|
||||
FT_Pos advance;
|
||||
int bitmap_left;
|
||||
int bitmap_top;
|
||||
FT_Bitmap bitmap;
|
||||
} Glyph;
|
||||
|
||||
typedef struct {
|
||||
FontSpec spec;
|
||||
int code;
|
||||
bool antialias;
|
||||
} GlyphKey;
|
||||
|
||||
static Cache *glyph_cache;
|
||||
|
||||
static struct {
|
||||
bool antialiase_on;
|
||||
const char *name[FONTTYPEMAX];
|
||||
int face[FONTTYPEMAX];
|
||||
int index[FONTTYPEMAX];
|
||||
FT_Face face[FONTTYPEMAX]; // opened on first use, shared between all sizes
|
||||
} this;
|
||||
|
||||
static void font_insert(int size, FontType type, TTF_Font *font) {
|
||||
fonttbl[fontcnt].size = size;
|
||||
fonttbl[fontcnt].type = type;
|
||||
fonttbl[fontcnt].id = font;
|
||||
|
||||
if (fontcnt >= (FONTTABLEMAX -1)) {
|
||||
WARNING("Font table is full.");
|
||||
} else {
|
||||
fontcnt++;
|
||||
}
|
||||
}
|
||||
|
||||
static FontTable *font_lookup(int size, FontType type) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fontcnt; i++) {
|
||||
if (fonttbl[i].size == size && fonttbl[i].type == type) {
|
||||
return &fonttbl[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Resolve a (type, size) pair to a TTF_Font, opening and caching it on first
|
||||
// use, and apply the requested weight. Returns NULL for an invalid type.
|
||||
static FontTable *font_resolve(FontSpec font) {
|
||||
FontType type = font.type;
|
||||
int size = font.size;
|
||||
|
||||
if (type >= FONTTYPEMAX) {
|
||||
WARNING("Invalid font type %d", type);
|
||||
#if defined(_WIN32) || defined(__ANDROID__)
|
||||
// Create a face from the whole content of `rw`. The memory holding the font
|
||||
// file must outlive the face, and faces are never closed, so it is never freed.
|
||||
static FT_Face face_from_rwops(SDL_RWops *rw, int index) {
|
||||
Sint64 size = SDL_RWsize(rw);
|
||||
if (size <= 0) {
|
||||
SDL_RWclose(rw);
|
||||
return NULL;
|
||||
}
|
||||
FT_Byte *buf = malloc(size);
|
||||
if (SDL_RWread(rw, buf, 1, size) != (size_t)size) {
|
||||
free(buf);
|
||||
SDL_RWclose(rw);
|
||||
return NULL;
|
||||
}
|
||||
SDL_RWclose(rw);
|
||||
|
||||
FontTable *tbl;
|
||||
if (NULL == (tbl = font_lookup(size, type))) {
|
||||
TTF_Font *fs = TTF_OpenFontIndex(this.name[type], size, this.face[type]);
|
||||
#ifdef __ANDROID__
|
||||
// If `this.name[type]` is a custom font file specified in .xys35rc,
|
||||
// SDL_RWFromFile used by TTF_OpenFontIndex does not work because it
|
||||
// does not resolve relative path with the current directory. On the
|
||||
// other hand, we can't just use fopen because SDL_RWFromFile can open
|
||||
// apk assets and the default fonts are stored as assets.
|
||||
if (!fs) {
|
||||
FILE *fp = fopen(this.name[type], "r");
|
||||
if (fp)
|
||||
fs = TTF_OpenFontIndexRW(SDL_RWFromFP(fp, true), true, size, this.face[type]);
|
||||
}
|
||||
FT_Face face;
|
||||
if (FT_New_Memory_Face(ft_library, buf, size, index, &face)) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
return face;
|
||||
}
|
||||
#endif
|
||||
|
||||
static FT_Face open_face(const char *name, int index) {
|
||||
FT_Face face = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
SDL_RWops *r = open_resource(this.name[type], "fonts");
|
||||
if (r)
|
||||
fs = TTF_OpenFontIndexRW(r, true, size, this.face[type]);
|
||||
#endif
|
||||
if (!fs)
|
||||
SYSERROR("Cannot open font %s", this.name[type]);
|
||||
const char *system_font = NULL;
|
||||
if (!strcmp(name, DEFAULT_GOTHIC_TTF))
|
||||
system_font = "C:/Windows/Fonts/msgothic.ttc";
|
||||
else if (!strcmp(name, DEFAULT_MINCHO_TTF))
|
||||
system_font = "C:/Windows/Fonts/msmincho.ttc";
|
||||
if (system_font && FT_New_Face(ft_library, system_font, index, &face))
|
||||
face = NULL;
|
||||
|
||||
font_insert(size, type, fs);
|
||||
tbl = &fonttbl[fontcnt - 1];
|
||||
if (!face) {
|
||||
SDL_RWops *res = open_resource(name, "fonts");
|
||||
if (res)
|
||||
face = face_from_rwops(res, index);
|
||||
}
|
||||
TTF_SetFontStyle(tbl->id, font.weight == FONT_WEIGHT_BOLD ? TTF_STYLE_BOLD : TTF_STYLE_NORMAL);
|
||||
return tbl;
|
||||
#endif
|
||||
if (!face && FT_New_Face(ft_library, name, index, &face))
|
||||
face = NULL; // FT_New_Face() does not clear `face` on failure.
|
||||
#ifdef __ANDROID__
|
||||
// The default fonts are stored as apk assets, which can only be opened
|
||||
// through SDL_RWFromFile. (It is not used as the first choice because it
|
||||
// does not resolve a relative path against the current directory, which a
|
||||
// custom font specified in .xsys35rc may use.)
|
||||
if (!face) {
|
||||
SDL_RWops *rw = SDL_RWFromFile(name, "rb");
|
||||
if (rw)
|
||||
face = face_from_rwops(rw, index);
|
||||
}
|
||||
#endif
|
||||
if (face) {
|
||||
// Prefer a Unicode charmap; if there is none, keep FreeType's choice.
|
||||
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
}
|
||||
return face;
|
||||
}
|
||||
|
||||
SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color) {
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
// Since a face is shared between all sizes of a FontType, the size has to be
|
||||
// applied on every call.
|
||||
static bool font_select(FontSpec spec, Font *font) {
|
||||
if (spec.type >= FONTTYPEMAX) {
|
||||
WARNING("Invalid font type %d", spec.type);
|
||||
return false;
|
||||
}
|
||||
if (!this.face[spec.type]) {
|
||||
this.face[spec.type] = open_face(this.name[spec.type], this.index[spec.type]);
|
||||
if (!this.face[spec.type])
|
||||
SYSERROR("Cannot open font %s", this.name[spec.type]);
|
||||
}
|
||||
FT_Face face = this.face[spec.type];
|
||||
|
||||
// With a resolution of 0 (i.e. the default 72dpi), 1pt equals 1px.
|
||||
if (FT_Set_Char_Size(face, 0, spec.size * 64, 0, 0) &&
|
||||
FT_Set_Pixel_Sizes(face, 0, spec.size)) {
|
||||
WARNING("Cannot set the size of font %s to %d", this.name[spec.type], spec.size);
|
||||
return false;
|
||||
}
|
||||
font->face = face;
|
||||
font->ascent = FT_CEIL(face->size->metrics.ascender);
|
||||
font->height = font->ascent - FT_FLOOR(face->size->metrics.descender);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Distance from the top of the character cell to the baseline, the same value
|
||||
// GDI reports as TEXTMETRIC::tmAscent. The original engine positions text by
|
||||
// the cell top, so this determines where a glyph lands on the screen.
|
||||
static int cell_ascent(const Font *font) {
|
||||
TT_OS2 *os2 = FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2);
|
||||
if (!os2 || os2->version == 0xffff || !os2->usWinAscent)
|
||||
return font->ascent;
|
||||
int upem = font->face->units_per_EM;
|
||||
return (os2->usWinAscent * font->face->size->metrics.y_ppem + upem / 2) / upem;
|
||||
}
|
||||
|
||||
int font_cell_overhang(FontSpec spec) {
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return 0;
|
||||
return font.ascent - cell_ascent(&font);
|
||||
}
|
||||
|
||||
static bool load_glyph(FontSpec spec, FT_Face face, int code) {
|
||||
if (FT_Load_Char(face, code, FT_LOAD_DEFAULT))
|
||||
return false;
|
||||
if (spec.weight == FONT_WEIGHT_BOLD) {
|
||||
// Approximate GDI's synthetic bold without changing the advance.
|
||||
// Expand outlines before rasterization so antialiased edges are
|
||||
// generated only once.
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
|
||||
// At small sizes, use half-pixel emboldening to better match
|
||||
// GDI's synthetic bold for the thin bitmap glyphs of MS Gothic
|
||||
// and MS Mincho.
|
||||
int xstrength = spec.size <= 22 ? 32 : 64;
|
||||
if (FT_Outline_EmboldenXY(&slot->outline, xstrength, 0))
|
||||
return false;
|
||||
} else if (slot->format == FT_GLYPH_FORMAT_BITMAP) {
|
||||
if (FT_GlyphSlot_Own_Bitmap(slot) ||
|
||||
FT_Bitmap_Embolden(ft_library, &slot->bitmap, 64, 0))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t glyph_hash(const void *p) {
|
||||
const GlyphKey *key = p;
|
||||
uint32_t hash = (uint32_t)key->code;
|
||||
hash = hash * 31 + key->spec.type;
|
||||
hash = hash * 31 + key->spec.weight;
|
||||
hash = hash * 31 + key->spec.size;
|
||||
return hash * 31 + key->antialias;
|
||||
}
|
||||
|
||||
static bool glyph_key_equal(const void *a, const void *b) {
|
||||
const GlyphKey *ka = a;
|
||||
const GlyphKey *kb = b;
|
||||
return ka->spec.type == kb->spec.type &&
|
||||
ka->spec.weight == kb->spec.weight &&
|
||||
ka->spec.size == kb->spec.size &&
|
||||
ka->code == kb->code && ka->antialias == kb->antialias;
|
||||
}
|
||||
|
||||
static void glyph_destroy(void *data) {
|
||||
Glyph *glyph = data;
|
||||
FT_Bitmap_Done(ft_library, &glyph->bitmap);
|
||||
free(glyph);
|
||||
}
|
||||
|
||||
// Load and render a glyph on a cache miss. If allocation fails, return a
|
||||
// temporary view of the face's glyph slot so text rendering can continue.
|
||||
static const Glyph *get_glyph(FontSpec spec, FT_Face face, int code, bool antialias) {
|
||||
GlyphKey key = {spec, code, antialias};
|
||||
const Glyph *cached = cache_get(glyph_cache, &key);
|
||||
if (cached)
|
||||
return cached;
|
||||
|
||||
if (!load_glyph(spec, face, code))
|
||||
return NULL;
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
static Glyph temporary;
|
||||
temporary.advance = slot->advance.x;
|
||||
FT_Bitmap_Init(&temporary.bitmap);
|
||||
if (FT_Render_Glyph(slot, antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO))
|
||||
return &temporary;
|
||||
temporary.bitmap_left = slot->bitmap_left;
|
||||
temporary.bitmap_top = slot->bitmap_top;
|
||||
temporary.bitmap = slot->bitmap;
|
||||
|
||||
SDL_Surface *fs;
|
||||
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, color);
|
||||
if (!fs)
|
||||
WARNING("Text rendering failed: %s", TTF_GetError());
|
||||
size_t bitmap_size = slot->bitmap.rows * (size_t)abs(slot->bitmap.pitch);
|
||||
size_t cache_size = sizeof(Glyph) + bitmap_size;
|
||||
if (cache_size > GLYPH_CACHE_LIMIT)
|
||||
return &temporary;
|
||||
|
||||
return fs;
|
||||
Glyph *glyph = calloc(1, sizeof(Glyph));
|
||||
if (!glyph)
|
||||
return &temporary;
|
||||
FT_Bitmap_Init(&glyph->bitmap);
|
||||
if (FT_Bitmap_Copy(ft_library, &slot->bitmap, &glyph->bitmap)) {
|
||||
FT_Bitmap_Done(ft_library, &glyph->bitmap);
|
||||
free(glyph);
|
||||
return &temporary;
|
||||
}
|
||||
glyph->advance = slot->advance.x;
|
||||
glyph->bitmap_left = slot->bitmap_left;
|
||||
glyph->bitmap_top = slot->bitmap_top;
|
||||
if (cache_insert(glyph_cache, &key, glyph, cache_size) != CACHE_INSERT_OK) {
|
||||
glyph_destroy(glyph);
|
||||
return &temporary;
|
||||
}
|
||||
return glyph;
|
||||
}
|
||||
|
||||
void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h) {
|
||||
// Total advance of `str_utf8`, in pixels. A negative `len` means the whole
|
||||
// string, otherwise only its first `len` bytes are measured.
|
||||
static int text_width(FontSpec spec, FT_Face face, const char *str_utf8, int len,
|
||||
bool antialias) {
|
||||
const char *end = len < 0 ? NULL : str_utf8 + len;
|
||||
int pen = 0; // 26.6
|
||||
|
||||
while (*str_utf8 && (!end || str_utf8 < end)) {
|
||||
const Glyph *glyph = get_glyph(spec, face,
|
||||
utf8_next_codepoint(&str_utf8), antialias);
|
||||
if (glyph)
|
||||
pen += glyph->advance;
|
||||
}
|
||||
return FT_CEIL(pen);
|
||||
}
|
||||
|
||||
// Composite a rendered glyph into `dst`, which is either an ARGB8888 surface
|
||||
// (the glyph is drawn in `color`, its coverage scaled by `color.a` becoming the
|
||||
// alpha value) or an INDEX8 surface (covered pixels are set to the index 1).
|
||||
static void blit_glyph(const FT_Bitmap *bmp, SDL_Surface *dst, int x, int y, SDL_Color color) {
|
||||
int bpp = dst->format->BytesPerPixel;
|
||||
uint32_t rgb = color.r << 16 | color.g << 8 | color.b;
|
||||
|
||||
for (unsigned int row = 0; row < bmp->rows; row++) {
|
||||
int dy = y + row;
|
||||
if (dy < 0 || dy >= dst->h)
|
||||
continue;
|
||||
const uint8_t *src = bmp->buffer + row * bmp->pitch;
|
||||
uint8_t *dst_row = (uint8_t *)dst->pixels + dy * dst->pitch;
|
||||
for (unsigned int col = 0; col < bmp->width; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= dst->w)
|
||||
continue;
|
||||
uint8_t coverage;
|
||||
switch (bmp->pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
coverage = src[col >> 3] & (0x80 >> (col & 7)) ? 255 : 0;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
coverage = src[col];
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (!coverage)
|
||||
continue;
|
||||
uint8_t *dp = dst_row + dx * bpp;
|
||||
if (bpp == 4)
|
||||
*(uint32_t *)dp = (uint32_t)(coverage * color.a / 255) << 24 | rgb;
|
||||
else
|
||||
*dp = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias) {
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return NULL;
|
||||
int width = max(text_width(spec, font.face, str_utf8, -1, antialias), 1);
|
||||
|
||||
SDL_Surface *sf;
|
||||
if (antialias) {
|
||||
sf = SDL_CreateRGBSurfaceWithFormat(0, width, font.height, 32, SDL_PIXELFORMAT_ARGB8888);
|
||||
if (sf)
|
||||
SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_BLEND);
|
||||
} else {
|
||||
sf = SDL_CreateRGBSurfaceWithFormat(0, width, font.height, 8, SDL_PIXELFORMAT_INDEX8);
|
||||
if (sf) {
|
||||
SDL_Color pal[2] = {{0, 0, 0, 0}, {color.r, color.g, color.b, 255}};
|
||||
SDL_SetPaletteColors(sf->format->palette, pal, 0, 2);
|
||||
SDL_SetColorKey(sf, SDL_TRUE, 0);
|
||||
}
|
||||
}
|
||||
if (!sf) {
|
||||
WARNING("Text rendering failed: %s", SDL_GetError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int pen = 0; // 26.6
|
||||
while (*str_utf8) {
|
||||
const Glyph *glyph = get_glyph(spec, font.face,
|
||||
utf8_next_codepoint(&str_utf8), antialias);
|
||||
if (!glyph)
|
||||
continue;
|
||||
blit_glyph(&glyph->bitmap, sf, FT_FLOOR(pen) + glyph->bitmap_left,
|
||||
font.ascent - glyph->bitmap_top, color);
|
||||
pen += glyph->advance;
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
|
||||
void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
|
||||
int *w, int *h) {
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
Font font;
|
||||
if (!font_select(spec, &font))
|
||||
return;
|
||||
if (h)
|
||||
*h = TTF_FontHeight(fontset->id);
|
||||
if (!w)
|
||||
return;
|
||||
|
||||
// A negative length means the whole string.
|
||||
if (len < 0) {
|
||||
TTF_SizeUTF8(fontset->id, str_utf8, w, NULL);
|
||||
} else {
|
||||
char buf[256];
|
||||
if (len > (int)sizeof(buf) - 1)
|
||||
len = sizeof(buf) - 1;
|
||||
memcpy(buf, str_utf8, len);
|
||||
buf[len] = '\0';
|
||||
TTF_SizeUTF8(fontset->id, buf, w, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t cl) {
|
||||
SDL_Surface *fs;
|
||||
SDL_Rect r_src, r_dst = {};
|
||||
int w, h;
|
||||
|
||||
if (!*str_utf8)
|
||||
return r_dst;
|
||||
FontTable *fontset = font_resolve(font);
|
||||
if (!fontset)
|
||||
return r_dst;
|
||||
|
||||
bool antialias = this.antialiase_on;
|
||||
// The post-effect in Rance 3 opening does not work properly if colors other
|
||||
// than the specified text color (32) are used.
|
||||
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
|
||||
// In Rance 3, text color 32 is only used in the opening.
|
||||
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && cl == 32)
|
||||
antialias = false;
|
||||
|
||||
SDL_Color color = gfx_getPaletteColor(cl);
|
||||
if (antialias)
|
||||
fs = TTF_RenderUTF8_Blended(fontset->id, str_utf8, color);
|
||||
else
|
||||
fs = TTF_RenderUTF8_Solid(fontset->id, str_utf8, color);
|
||||
if (!fs) {
|
||||
WARNING("Text rendering failed: %s", TTF_GetError());
|
||||
return r_dst;
|
||||
}
|
||||
|
||||
TTF_SizeUTF8(fontset->id, str_utf8, &w, &h);
|
||||
// Center vertically to the box.
|
||||
y -= (TTF_FontHeight(fontset->id) - fontset->size) / 2;
|
||||
r_dst = (SDL_Rect){x, y, w, h};
|
||||
|
||||
if (main_surface->format->BitsPerPixel == 8 && antialias) {
|
||||
gfx_drawAntiAlias_8bpp(x, y, fs, cl);
|
||||
} else {
|
||||
r_src = (SDL_Rect){0, 0, w, h};
|
||||
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(fs);
|
||||
if (r_dst.y < 0) {
|
||||
r_dst.h += r_dst.y;
|
||||
r_dst.y = 0;
|
||||
}
|
||||
return r_dst;
|
||||
*h = font.height;
|
||||
if (w)
|
||||
*w = text_width(spec, font.face, str_utf8, len, antialias);
|
||||
}
|
||||
|
||||
void font_init(void) {
|
||||
cache_clear(glyph_cache);
|
||||
this.antialiase_on = false;
|
||||
|
||||
if (TTF_Init() == -1)
|
||||
SYSERROR("Failed to intialize SDL_ttf: %s", TTF_GetError());
|
||||
|
||||
if (FT_Init_FreeType(&ft_library))
|
||||
SYSERROR("Failed to initialize FreeType");
|
||||
if (!glyph_cache) {
|
||||
CacheOps ops = {
|
||||
.key_size = sizeof(GlyphKey),
|
||||
.hash = glyph_hash,
|
||||
.equal = glyph_key_equal,
|
||||
.destroy = glyph_destroy,
|
||||
};
|
||||
glyph_cache = cache_new(GLYPH_CACHE_LIMIT, &ops);
|
||||
}
|
||||
}
|
||||
|
||||
void font_set_name_and_index(FontType type, const char *name, int index) {
|
||||
@@ -216,8 +406,10 @@ void font_set_name_and_index(FontType type, const char *name, int index) {
|
||||
WARNING("Invalid font type %d", type);
|
||||
return;
|
||||
}
|
||||
// Cached glyphs include the font type but not the font file name.
|
||||
cache_clear(glyph_cache);
|
||||
this.name[type] = name;
|
||||
this.face[type] = index;
|
||||
this.index[type] = index;
|
||||
}
|
||||
|
||||
void font_set_antialias(bool enable) {
|
||||
@@ -228,6 +420,10 @@ bool font_get_antialias(void) {
|
||||
return this.antialiase_on;
|
||||
}
|
||||
|
||||
CacheStats font_get_cache_stats(void) {
|
||||
return cache_get_stats(glyph_cache);
|
||||
}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASYNC_JS(bool, load_mincho_font, (void), {
|
||||
return await xsystem35.load_mincho_font();
|
||||
|
||||
+8
-3
@@ -25,6 +25,7 @@
|
||||
#define __FONT_H__
|
||||
|
||||
#include <SDL_surface.h>
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "portab.h"
|
||||
|
||||
@@ -49,9 +50,13 @@ extern void font_init(void);
|
||||
extern void font_set_name_and_index(FontType type, const char *name, int index);
|
||||
extern void font_set_antialias(bool enable);
|
||||
extern bool font_get_antialias(void);
|
||||
extern struct SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color);
|
||||
extern SDL_Rect font_draw_text(FontSpec font, int x, int y, const char *str_utf8, uint8_t col);
|
||||
extern void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h);
|
||||
extern struct SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias);
|
||||
extern void font_measure_text(FontSpec spec, const char *str_utf8, int len, bool antialias,
|
||||
int *w, int *h);
|
||||
// Rows by which a rendered surface extends above the character cell. Subtract
|
||||
// it from a Y coordinate that denotes the cell top before blitting.
|
||||
extern int font_cell_overhang(FontSpec spec);
|
||||
extern CacheStats font_get_cache_stats(void);
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
extern bool load_mincho_font(void);
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2026 <KichikuouChrome@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "font.h"
|
||||
#include "unittest.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
static const FontType font_types[] = {FONT_GOTHIC, FONT_MINCHO};
|
||||
#define NTYPES ((int)(sizeof(font_types) / sizeof(font_types[0])))
|
||||
|
||||
static const int sizes[] = {8, 16, 24, 32};
|
||||
#define NSIZES ((int)(sizeof(sizes) / sizeof(sizes[0])))
|
||||
|
||||
static const char *texts[] = {
|
||||
"Hello, world!",
|
||||
" ",
|
||||
"iWiW",
|
||||
"\xe9\xad\x94\xe7\x89\xa9\xe3\x81\xae\xe6\xa3\xae", // 魔物の森
|
||||
"\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86" // あいう
|
||||
"\xe3\x82\xa2\xe3\x82\xa4\xe3\x82\xa6", // アイウ
|
||||
};
|
||||
#define NTEXTS ((int)(sizeof(texts) / sizeof(texts[0])))
|
||||
|
||||
static const SDL_Color white = {255, 255, 255, 255};
|
||||
|
||||
static int measure_w(FontSpec f, const char *s, bool antialias) {
|
||||
int w;
|
||||
font_measure_text(f, s, -1, antialias, &w, NULL);
|
||||
return w;
|
||||
}
|
||||
|
||||
static int measure_h(FontSpec f) {
|
||||
int h;
|
||||
font_measure_text(f, "", -1, false, NULL, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void assert_same_pixels(SDL_Surface *a, SDL_Surface *b) {
|
||||
ASSERT_EQUAL(a->w, b->w);
|
||||
ASSERT_EQUAL(a->h, b->h);
|
||||
ASSERT_EQUAL(a->format->format, b->format->format);
|
||||
int row_bytes = a->w * a->format->BytesPerPixel;
|
||||
for (int y = 0; y < a->h; y++) {
|
||||
const uint8_t *ap = (uint8_t *)a->pixels + y * a->pitch;
|
||||
const uint8_t *bp = (uint8_t *)b->pixels + y * b->pitch;
|
||||
ASSERT_EQUAL(memcmp(ap, bp, row_bytes), 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void glyph_cache_test(void) {
|
||||
for (int ti = 0; ti < NTYPES; ti++) {
|
||||
for (int weight = FONT_WEIGHT_NORMAL; weight <= FONT_WEIGHT_BOLD; weight++) {
|
||||
FontSpec f = {font_types[ti], weight, 24};
|
||||
for (int aa = 0; aa < 2; aa++) {
|
||||
SDL_Surface *first = font_render_text(f, texts[3], white, aa);
|
||||
SDL_Surface *cached = font_render_text(f, texts[3], white, aa);
|
||||
ASSERT_TRUE(first);
|
||||
ASSERT_TRUE(cached);
|
||||
assert_same_pixels(first, cached);
|
||||
SDL_FreeSurface(first);
|
||||
SDL_FreeSurface(cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void invariants_test(void) {
|
||||
for (int ti = 0; ti < NTYPES; ti++) {
|
||||
for (int si = 0; si < NSIZES; si++) {
|
||||
FontSpec f = {font_types[ti], FONT_WEIGHT_NORMAL, sizes[si]};
|
||||
for (int i = 0; i < NTEXTS; i++) {
|
||||
const char *text = texts[i];
|
||||
for (int aa = 0; aa < 2; aa++) {
|
||||
SDL_Surface *s = font_render_text(f, text, white, aa);
|
||||
ASSERT_TRUE(s);
|
||||
// gfx_drawString() blits the surface using the measured
|
||||
// width, so the two must agree.
|
||||
ASSERT_EQUAL(s->w, measure_w(f, text, aa));
|
||||
// microui lays out text rows by the measured height,
|
||||
// so the surface must be exactly that tall.
|
||||
ASSERT_EQUAL(s->h, measure_h(f));
|
||||
// The antialiased surface goes to gfx_drawAntiAlias_8bpp(),
|
||||
// which reads a 32-bit alpha channel. The other one is
|
||||
// blitted onto the indexed screen surface as is.
|
||||
ASSERT_EQUAL(s->format->BitsPerPixel, aa ? 32 : 8);
|
||||
SDL_FreeSurface(s);
|
||||
}
|
||||
}
|
||||
// gfx_drawString() draws text by the top of the character cell,
|
||||
// shifting the surface up by the overhang. The result must cover
|
||||
// the whole cell.
|
||||
ASSERT_TRUE(font_cell_overhang(f) >= 0);
|
||||
ASSERT_TRUE(measure_h(f) - font_cell_overhang(f) >= f.size);
|
||||
|
||||
// microui positions the text cursor by measuring a prefix of the
|
||||
// string, so that must equal measuring a truncated copy.
|
||||
const char *text = texts[0];
|
||||
for (int len = 0; text[len]; len++) {
|
||||
char buf[64];
|
||||
int w;
|
||||
font_measure_text(f, text, len, false, &w, NULL);
|
||||
memcpy(buf, text, len);
|
||||
buf[len] = '\0';
|
||||
ASSERT_EQUAL(w, measure_w(f, buf, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void font_test(void) {
|
||||
font_init();
|
||||
font_set_name_and_index(FONT_GOTHIC, TEST_FONT_DIR "/MTLc3m.ttf", 0);
|
||||
font_set_name_and_index(FONT_MINCHO, TEST_FONT_DIR "/mincho.ttf", 0);
|
||||
|
||||
invariants_test();
|
||||
glyph_cache_test();
|
||||
}
|
||||
+34
-1
@@ -38,6 +38,7 @@
|
||||
#include "gfx_private.h"
|
||||
#include "font.h"
|
||||
#include "ags.h"
|
||||
#include "hacks.h"
|
||||
#include "image.h"
|
||||
#include "nact.h"
|
||||
#include "modal.h"
|
||||
@@ -317,8 +318,40 @@ void gfx_drawAntiAlias_8bpp(int dstx, int dsty, SDL_Surface *src, uint8_t col) {
|
||||
}
|
||||
|
||||
SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpec font) {
|
||||
SDL_Rect r_dst = {};
|
||||
|
||||
gfx_pal_check();
|
||||
return font_draw_text(font, x, y, str_utf8, col);
|
||||
if (!*str_utf8)
|
||||
return r_dst;
|
||||
|
||||
bool antialias = font_get_antialias();
|
||||
// The post-effect in Rance 3 opening does not work properly if colors other
|
||||
// than the specified text color (32) are used.
|
||||
// https://github.com/kichikuou/xsystem35-sdl2/issues/54
|
||||
// In Rance 3, text color 32 is only used in the opening.
|
||||
if ((game_id == GAME_RANCE3 || game_id == GAME_RANCE3_ENG) && col == 32)
|
||||
antialias = false;
|
||||
|
||||
SDL_Surface *fs = font_render_text(font, str_utf8, gfx_getPaletteColor(col), antialias);
|
||||
if (!fs)
|
||||
return r_dst;
|
||||
|
||||
y -= font_cell_overhang(font);
|
||||
r_dst = (SDL_Rect){x, y, fs->w, fs->h};
|
||||
|
||||
if (main_surface->format->BitsPerPixel == 8 && antialias) {
|
||||
gfx_drawAntiAlias_8bpp(x, y, fs, col);
|
||||
} else {
|
||||
SDL_Rect r_src = {0, 0, fs->w, fs->h};
|
||||
SDL_BlitSurface(fs, &r_src, main_surface, &r_dst);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(fs);
|
||||
if (r_dst.y < 0) {
|
||||
r_dst.h += r_dst.y;
|
||||
r_dst.y = 0;
|
||||
}
|
||||
return r_dst;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+2
-5
@@ -109,14 +109,11 @@ static void window_init(const char *render_driver) {
|
||||
#ifdef __ANDROID__
|
||||
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
|
||||
SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight");
|
||||
Uint32 flags = SDL_WINDOW_FULLSCREEN;
|
||||
#else
|
||||
Uint32 flags = SDL_WINDOW_RESIZABLE;
|
||||
#endif
|
||||
|
||||
gfx_window = SDL_CreateWindow(
|
||||
title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
||||
SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, flags);
|
||||
SYS35_DEFAULT_WIDTH, SYS35_DEFAULT_HEIGHT, SDL_WINDOW_RESIZABLE);
|
||||
gfx_renderer = SDL_CreateRenderer(gfx_window, -1, 0);
|
||||
SDL_SetRenderDrawColor(gfx_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
gfx_palette = SDL_AllocPalette(256);
|
||||
|
||||
+47
-125
@@ -148,118 +148,31 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
|
||||
#include "utfsjis.h"
|
||||
|
||||
// The string-input dialog.
|
||||
// The input field is not a microui textbox: to support IME (SDL_TEXTEDITING)
|
||||
// composition we keep our own buffer (str_buf) and feed it from the handler,
|
||||
// mirroring editor.c.
|
||||
struct string_state {
|
||||
modal base;
|
||||
INPUTSTRING_PARAM *param;
|
||||
char composing[SDL_TEXTEDITINGEVENT_TEXT_SIZE]; // IME preedit text
|
||||
bool done;
|
||||
bool accepted;
|
||||
FontSpec font; // inline edit (input_modal_string_inline) only
|
||||
bool focus_init; // focus the text field on the first frame
|
||||
FontSpec font; // inline edit (input_modal_string_inline) only
|
||||
};
|
||||
|
||||
// Heap buffer holding the string dialog's edited text. It must outlive the
|
||||
// modal loop because the caller reads p->newstring after input_modal_string()
|
||||
// returns; it is freed and reallocated on the next call.
|
||||
// Heap buffer holding the edited text. It must outlive the modal loop because
|
||||
// the caller reads p->newstring after input_modal_string() returns; it is freed
|
||||
// and reallocated on the next call.
|
||||
static char *str_buf;
|
||||
static int str_bufsz;
|
||||
|
||||
// Append `add` to str_buf, truncating so the total stays within
|
||||
// st->param->max characters (counted as UTF-8 code points).
|
||||
static void str_append(struct string_state *st, const char *add) {
|
||||
int room = st->param->max;
|
||||
for (const char *p = str_buf; *p; p = advance_char(p, UTF8))
|
||||
room--;
|
||||
const char *end = add;
|
||||
while (room > 0 && *end) {
|
||||
end = advance_char(end, UTF8);
|
||||
room--;
|
||||
}
|
||||
strncat(str_buf, add, end - add);
|
||||
}
|
||||
|
||||
// Delete the last UTF-8 character of str_buf.
|
||||
static void str_backspace(void) {
|
||||
if (!*str_buf)
|
||||
return;
|
||||
char *p = str_buf + strlen(str_buf) - 1;
|
||||
while (p > str_buf && UTF8_TRAIL_BYTE(*p))
|
||||
p--;
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
static bool menu_string_handler(const SDL_Event *e, modal *modal) {
|
||||
struct string_state *st = (struct string_state *)modal;
|
||||
switch (e->type) {
|
||||
case SDL_KEYDOWN:
|
||||
// While an IME composition is active, let it consume the key.
|
||||
if (!*st->composing) {
|
||||
switch (e->key.keysym.sym) {
|
||||
case SDLK_RETURN:
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
break;
|
||||
case SDLK_ESCAPE:
|
||||
st->base.cancelled = true;
|
||||
break;
|
||||
case SDLK_BACKSPACE:
|
||||
str_backspace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true; // never forward keys to microui (there is no textbox)
|
||||
case SDL_TEXTINPUT:
|
||||
str_append(st, e->text.text);
|
||||
st->composing[0] = '\0';
|
||||
return true;
|
||||
case SDL_TEXTEDITING:
|
||||
strncpy(st->composing, e->edit.text, sizeof(st->composing) - 1);
|
||||
st->composing[sizeof(st->composing) - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
return modal_default_handler(e, modal);
|
||||
}
|
||||
|
||||
// Tell SDL where the text input is, so the IME candidate window is positioned
|
||||
// near the field. `box` is in logical (view) coordinates; convert to window
|
||||
// pixels for SDL_SetTextInputRect.
|
||||
static void set_text_input_rect(mu_Rect box) {
|
||||
SDL_Rect r = gfx_viewToWindowRect(
|
||||
(SDL_Rect){box.x, box.y, box.w, box.h});
|
||||
SDL_SetTextInputRect(&r);
|
||||
static void str_buf_init(const char *initial, int max_chars) {
|
||||
free(str_buf);
|
||||
str_bufsz = max_chars * MAX_UTF8_BYTES_PAR_CHAR + 1;
|
||||
str_buf = malloc(str_bufsz);
|
||||
strncpy(str_buf, initial ? initial : "", str_bufsz - 1);
|
||||
str_buf[str_bufsz - 1] = '\0';
|
||||
}
|
||||
|
||||
#ifndef _WIN32 // input_modal_string is provided by win/dialog.c on Windows.
|
||||
|
||||
// Draw the IME-aware text field (committed text, preedit with underline, caret).
|
||||
static void draw_string_field(mu_Context *ctx, mu_Rect box, const struct string_state *st) {
|
||||
mu_draw_rect(ctx, box, ctx->style->colors[MU_COLOR_BASE]);
|
||||
mu_draw_box(ctx, box, ctx->style->colors[MU_COLOR_BORDER]);
|
||||
|
||||
mu_Font font = ctx->style->font;
|
||||
mu_Color col = ctx->style->colors[MU_COLOR_TEXT];
|
||||
int th = ctx->text_height(font);
|
||||
int x = box.x + ctx->style->padding;
|
||||
int y = box.y + (box.h - th) / 2;
|
||||
|
||||
mu_push_clip_rect(ctx, box);
|
||||
if (*str_buf) {
|
||||
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), col);
|
||||
x += ctx->text_width(font, str_buf, -1);
|
||||
}
|
||||
if (*st->composing) {
|
||||
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), col);
|
||||
int cw = ctx->text_width(font, st->composing, -1);
|
||||
mu_draw_rect(ctx, mu_rect(x, y + th, cw, 1), col); // preedit underline
|
||||
x += cw;
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(x, y, 1, th), col); // caret
|
||||
mu_pop_clip_rect(ctx);
|
||||
|
||||
set_text_input_rect(box);
|
||||
}
|
||||
|
||||
static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
struct string_state *st = (struct string_state *)modal;
|
||||
const char *title = (st->param->title && *st->param->title)
|
||||
@@ -278,7 +191,17 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
snprintf(info, sizeof(info), _("Up to %d characters"), st->param->max);
|
||||
mu_label(ctx, info);
|
||||
|
||||
draw_string_field(ctx, mu_layout_next(ctx), st);
|
||||
mu_Id id = mu_get_id(ctx, "value", 5);
|
||||
mu_Rect box = mu_layout_next(ctx);
|
||||
if (st->focus_init) {
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
if (mu_textbox_raw(ctx, str_buf, str_bufsz, st->param->max, id, box, 0)
|
||||
& MU_RES_SUBMIT) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
int content = w - ctx->style->padding * 2;
|
||||
int half = (content - ctx->style->spacing) / 2;
|
||||
@@ -303,16 +226,12 @@ static bool inputstring_build(mu_Context *ctx, modal *modal) {
|
||||
|
||||
bool input_modal_string(INPUTSTRING_PARAM *p) {
|
||||
struct string_state st = {
|
||||
.base = { .build = inputstring_build, .handler = menu_string_handler },
|
||||
.base = { .build = inputstring_build, .handler = modal_default_handler },
|
||||
.param = p,
|
||||
.focus_init = true,
|
||||
};
|
||||
free(str_buf);
|
||||
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
strcpy(str_buf, p->oldstring ? p->oldstring : "");
|
||||
|
||||
SDL_StartTextInput();
|
||||
str_buf_init(p->oldstring, p->max);
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : p->oldstring;
|
||||
return true;
|
||||
@@ -320,8 +239,8 @@ bool input_modal_string(INPUTSTRING_PARAM *p) {
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
// The inline text input (MJ command). It reuses the IME buffer handling
|
||||
// and the event handler of input_modal_string.
|
||||
// The inline text input (MJ command). It shares the textbox input handling but
|
||||
// draws itself with the game's font and palette.
|
||||
|
||||
// Look up an AGS color index in the active palette and return it as a mu_Color.
|
||||
static mu_Color palette_color(int index) {
|
||||
@@ -342,6 +261,17 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
// all drawing below is done manually with the game's colors.
|
||||
if (mu_begin_window_ex(ctx, "editstr", rect,
|
||||
MU_OPT_NOFRAME | MU_OPT_NOTITLE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL)) {
|
||||
mu_Id id = mu_get_id(ctx, "editstr", 7);
|
||||
if (st->focus_init) {
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
if (mu_textbox_input(ctx, str_buf, str_bufsz, st->param->max, id, rect, 0)
|
||||
& MU_RES_SUBMIT) {
|
||||
st->accepted = true;
|
||||
st->done = true;
|
||||
}
|
||||
|
||||
mu_Color bg = palette_color(nact->msg.WinBackgroundColor);
|
||||
mu_Color fg = palette_color(nact->msg.MsgFontColor);
|
||||
mu_Font font = (mu_Font)&st->font;
|
||||
@@ -354,14 +284,13 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
mu_draw_text(ctx, font, str_buf, -1, mu_vec2(x, y), fg);
|
||||
x += ctx->text_width(font, str_buf, -1);
|
||||
}
|
||||
if (*st->composing) {
|
||||
mu_draw_text(ctx, font, st->composing, -1, mu_vec2(x, y), fg);
|
||||
int cw = ctx->text_width(font, st->composing, -1);
|
||||
if (*ctx->preedit) {
|
||||
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(x, y), fg);
|
||||
int cw = ctx->text_width(font, ctx->preedit, -1);
|
||||
mu_draw_rect(ctx, mu_rect(x, y + fh, cw, 2), fg); // preedit underline
|
||||
x += cw;
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(x, y, 2, fh), fg); // caret
|
||||
set_text_input_rect(rect);
|
||||
mu_end_window(ctx);
|
||||
}
|
||||
|
||||
@@ -372,18 +301,14 @@ static bool inline_edit_build(mu_Context *ctx, modal *modal) {
|
||||
|
||||
bool input_modal_string_inline(INPUTSTRING_PARAM *p) {
|
||||
struct string_state st = {
|
||||
.base = { .build = inline_edit_build, .handler = menu_string_handler,
|
||||
.base = { .build = inline_edit_build, .handler = modal_default_handler,
|
||||
.no_dim = true },
|
||||
.param = p,
|
||||
.focus_init = true,
|
||||
.font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, p->h },
|
||||
};
|
||||
free(str_buf);
|
||||
str_buf = malloc(p->max * MAX_UTF8_BYTES_PAR_CHAR + 1);
|
||||
strcpy(str_buf, p->oldstring ? p->oldstring : "");
|
||||
|
||||
SDL_StartTextInput();
|
||||
str_buf_init(p->oldstring, p->max);
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
p->newstring = st.accepted ? str_buf : NULL;
|
||||
return true;
|
||||
@@ -453,10 +378,10 @@ static bool inputnumber_build(mu_Context *ctx, modal *modal) {
|
||||
mu_Id id = mu_get_id(ctx, "value", 5);
|
||||
mu_Rect box = mu_layout_next(ctx);
|
||||
if (st->focus_init) {
|
||||
mu_set_focus(ctx, id);
|
||||
mu_set_kb_focus(ctx, id);
|
||||
st->focus_init = false;
|
||||
}
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), id, box, 0);
|
||||
int res = mu_textbox_raw(ctx, st->buf, sizeof(st->buf), 0, id, box, 0);
|
||||
if (mu_button(ctx, "-"))
|
||||
num_adjust(st, -1);
|
||||
if (mu_button(ctx, "+"))
|
||||
@@ -501,10 +426,7 @@ bool input_modal_number(INPUTNUM_PARAM *p) {
|
||||
.focus_init = true,
|
||||
};
|
||||
snprintf(st.buf, sizeof(st.buf), "%d", p->def);
|
||||
|
||||
SDL_StartTextInput();
|
||||
modal_run(&st.base);
|
||||
SDL_StopTextInput();
|
||||
|
||||
if (!st.accepted)
|
||||
return false;
|
||||
|
||||
+171
-53
@@ -68,7 +68,8 @@ static mu_Style default_style = {
|
||||
{ 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */
|
||||
{ 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */
|
||||
{ 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */
|
||||
{ 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */
|
||||
{ 30, 30, 30, 255 }, /* MU_COLOR_SCROLLTHUMB */
|
||||
{ 120, 170, 255, 255 } /* MU_COLOR_FOCUSRING */
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,6 +145,11 @@ void mu_begin(mu_Context *ctx) {
|
||||
ctx->next_hover_root = NULL;
|
||||
ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x;
|
||||
ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y;
|
||||
/* restart the keyboard-navigation scan */
|
||||
ctx->nav_first = ctx->nav_last = 0;
|
||||
ctx->nav_prev = ctx->nav_next = ctx->nav_cursor = 0;
|
||||
ctx->kb_focus_seen = 0;
|
||||
ctx->text_input = 0;
|
||||
ctx->frame++;
|
||||
}
|
||||
|
||||
@@ -171,6 +177,23 @@ void mu_end(mu_Context *ctx) {
|
||||
if (!ctx->updated_focus) { ctx->focus = 0; }
|
||||
ctx->updated_focus = 0;
|
||||
|
||||
/* move the keyboard focus. The new focus takes effect on the next frame,
|
||||
** which is fine for a UI that is rebuilt every frame. */
|
||||
if (!ctx->kb_focus_seen) { ctx->kb_focus = 0; }
|
||||
if (ctx->key_pressed & (MU_KEY_TAB | MU_KEY_UP | MU_KEY_DOWN)) {
|
||||
int back = (ctx->key_pressed & MU_KEY_UP) ||
|
||||
((ctx->key_pressed & MU_KEY_TAB) && (ctx->key_down & MU_KEY_SHIFT));
|
||||
if (!ctx->kb_focus) {
|
||||
ctx->kb_focus = back ? ctx->nav_last : ctx->nav_first;
|
||||
} else {
|
||||
ctx->kb_focus = back ? (ctx->nav_prev ? ctx->nav_prev : ctx->nav_last)
|
||||
: (ctx->nav_next ? ctx->nav_next : ctx->nav_first);
|
||||
}
|
||||
}
|
||||
|
||||
/* drop a composition left over from a textbox that lost the focus */
|
||||
if (!ctx->text_input) { ctx->preedit[0] = '\0'; }
|
||||
|
||||
/* bring hover root to front if mouse was pressed */
|
||||
if (ctx->mouse_pressed && ctx->next_hover_root &&
|
||||
ctx->next_hover_root->zindex < ctx->last_zindex &&
|
||||
@@ -216,6 +239,12 @@ void mu_set_focus(mu_Context *ctx, mu_Id id) {
|
||||
}
|
||||
|
||||
|
||||
void mu_set_kb_focus(mu_Context *ctx, mu_Id id) {
|
||||
ctx->kb_focus = id;
|
||||
ctx->kb_focus_seen = 1;
|
||||
}
|
||||
|
||||
|
||||
/* 32bit fnv-1a hash */
|
||||
#define HASH_INITIAL 2166136261
|
||||
|
||||
@@ -402,6 +431,8 @@ void mu_input_scroll(mu_Context *ctx, int x, int y) {
|
||||
|
||||
|
||||
void mu_input_keydown(mu_Context *ctx, int key) {
|
||||
/* while an IME composition is active the keys belong to the IME */
|
||||
if (*ctx->preedit) { return; }
|
||||
ctx->key_pressed |= key;
|
||||
ctx->key_down |= key;
|
||||
}
|
||||
@@ -413,10 +444,22 @@ void mu_input_keyup(mu_Context *ctx, int key) {
|
||||
|
||||
|
||||
void mu_input_text(mu_Context *ctx, const char *text) {
|
||||
/* SDL splits a long commit into several events, which all arrive within one
|
||||
** frame and can overflow the buffer. Drop the excess instead of failing. */
|
||||
int len = strlen(ctx->input_text);
|
||||
int size = strlen(text) + 1;
|
||||
expect(len + size <= (int) sizeof(ctx->input_text));
|
||||
memcpy(ctx->input_text + len, text, size);
|
||||
int n = mu_min((int) strlen(text), (int) sizeof(ctx->input_text) - 1 - len);
|
||||
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
|
||||
memcpy(ctx->input_text + len, text, n);
|
||||
ctx->input_text[len + n] = '\0';
|
||||
ctx->preedit[0] = '\0'; /* committing ends the composition */
|
||||
}
|
||||
|
||||
|
||||
void mu_input_preedit(mu_Context *ctx, const char *text) {
|
||||
int n = mu_min((int) strlen(text), (int) sizeof(ctx->preedit) - 1);
|
||||
while (n > 0 && (text[n] & 0xc0) == 0x80) { n--; } /* keep utf-8 whole */
|
||||
memcpy(ctx->preedit, text, n);
|
||||
ctx->preedit[n] = '\0';
|
||||
}
|
||||
|
||||
|
||||
@@ -652,6 +695,17 @@ void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect,
|
||||
if (opt & MU_OPT_NOFRAME) { return; }
|
||||
colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0;
|
||||
ctx->draw_frame(ctx, rect, colorid);
|
||||
if (ctx->kb_focus == id) {
|
||||
mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_FOCUSRING]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int control_activated(mu_Context *ctx, mu_Id id) {
|
||||
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { return 1; }
|
||||
if (ctx->kb_focus == id &&
|
||||
ctx->key_pressed & (MU_KEY_RETURN | MU_KEY_SPACE)) { return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -689,6 +743,22 @@ void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
|
||||
if (opt & MU_OPT_NOINTERACT) { return; }
|
||||
if (mouseover && !ctx->mouse_down) { ctx->hover = id; }
|
||||
|
||||
/* record this control in the keyboard focus cycle. Controls are visited in
|
||||
** submission order, so tracking the previous one is enough to know both
|
||||
** neighbours of kb_focus. */
|
||||
if (~opt & MU_OPT_NOKBNAV && mu_check_clip(ctx, rect) != MU_CLIP_ALL) {
|
||||
if (!ctx->nav_first) { ctx->nav_first = id; }
|
||||
ctx->nav_last = id;
|
||||
if (id == ctx->kb_focus) {
|
||||
ctx->kb_focus_seen = 1;
|
||||
ctx->nav_prev = ctx->nav_cursor;
|
||||
} else if (ctx->kb_focus && !ctx->nav_next &&
|
||||
ctx->nav_cursor == ctx->kb_focus) {
|
||||
ctx->nav_next = id;
|
||||
}
|
||||
ctx->nav_cursor = id;
|
||||
}
|
||||
|
||||
if (ctx->focus == id) {
|
||||
if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); }
|
||||
if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); }
|
||||
@@ -697,6 +767,8 @@ void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
|
||||
if (ctx->hover == id) {
|
||||
if (ctx->mouse_pressed) {
|
||||
mu_set_focus(ctx, id);
|
||||
ctx->kb_focus = id; /* clicking moves the keyboard focus too */
|
||||
ctx->kb_focus_seen = 1;
|
||||
} else if (!mouseover) {
|
||||
ctx->hover = 0;
|
||||
}
|
||||
@@ -742,7 +814,7 @@ int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) {
|
||||
mu_Rect r = mu_layout_next(ctx);
|
||||
mu_update_control(ctx, id, r, opt);
|
||||
/* handle click */
|
||||
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
|
||||
if (control_activated(ctx, id)) {
|
||||
res |= MU_RES_SUBMIT;
|
||||
}
|
||||
/* draw */
|
||||
@@ -760,7 +832,7 @@ int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
|
||||
mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
|
||||
mu_update_control(ctx, id, r, 0);
|
||||
/* handle click */
|
||||
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
|
||||
if (control_activated(ctx, id)) {
|
||||
res |= MU_RES_CHANGE;
|
||||
*state = !*state;
|
||||
}
|
||||
@@ -775,54 +847,91 @@ int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r,
|
||||
int opt)
|
||||
/* number of characters in a utf-8 string */
|
||||
static int utf8_strlen(const char *s) {
|
||||
int n = 0;
|
||||
for (; *s; s++) { if ((*s & 0xc0) != 0x80) { n++; } }
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars,
|
||||
mu_Id id, mu_Rect r, int opt)
|
||||
{
|
||||
int res = 0;
|
||||
mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
|
||||
/* the keyboard focus decides which textbox is editable, so that Tab moves
|
||||
** the caret out of it. A click reaches this through mu_update_control(). */
|
||||
mu_update_control(ctx, id, r, opt);
|
||||
if (ctx->kb_focus != id) { return 0; }
|
||||
|
||||
if (ctx->focus == id) {
|
||||
/* handle text input */
|
||||
int len = strlen(buf);
|
||||
int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
|
||||
if (n > 0) {
|
||||
memcpy(buf + len, ctx->input_text, n);
|
||||
len += n;
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle backspace */
|
||||
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
|
||||
/* skip utf-8 continuation bytes */
|
||||
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle return */
|
||||
if (ctx->key_pressed & MU_KEY_RETURN) {
|
||||
mu_set_focus(ctx, 0);
|
||||
res |= MU_RES_SUBMIT;
|
||||
}
|
||||
/* let the host turn the IME on and place its candidate window */
|
||||
ctx->text_input = 1;
|
||||
ctx->text_input_rect = r;
|
||||
|
||||
/* handle text input, honoring both the byte and the character limit */
|
||||
int len = strlen(buf);
|
||||
int room = bufsz - len - 1;
|
||||
int chars = max_chars ? max_chars - utf8_strlen(buf) : bufsz;
|
||||
int n = 0;
|
||||
while (ctx->input_text[n] && chars > 0) {
|
||||
int e = n + 1;
|
||||
while ((ctx->input_text[e] & 0xc0) == 0x80) { e++; }
|
||||
if (e > room) { break; }
|
||||
n = e;
|
||||
chars--;
|
||||
}
|
||||
if (n > 0) {
|
||||
memcpy(buf + len, ctx->input_text, n);
|
||||
len += n;
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle backspace */
|
||||
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
|
||||
/* skip utf-8 continuation bytes */
|
||||
while ((buf[--len] & 0xc0) == 0x80 && len > 0);
|
||||
buf[len] = '\0';
|
||||
res |= MU_RES_CHANGE;
|
||||
}
|
||||
/* handle return */
|
||||
if (ctx->key_pressed & MU_KEY_RETURN) { res |= MU_RES_SUBMIT; }
|
||||
|
||||
/* draw */
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r,
|
||||
int opt)
|
||||
{
|
||||
mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
|
||||
if (ctx->focus == id) {
|
||||
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
|
||||
mu_Font font = ctx->style->font;
|
||||
int textw = ctx->text_width(font, buf, -1);
|
||||
int texth = ctx->text_height(font);
|
||||
int ofx = r.w - ctx->style->padding - textw - 1;
|
||||
int textx = r.x + mu_min(ofx, ctx->style->padding);
|
||||
int texty = r.y + (r.h - texth) / 2;
|
||||
mu_push_clip_rect(ctx, r);
|
||||
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
|
||||
mu_pop_clip_rect(ctx);
|
||||
} else {
|
||||
if (ctx->kb_focus != id) {
|
||||
mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
|
||||
return;
|
||||
}
|
||||
mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
|
||||
mu_Font font = ctx->style->font;
|
||||
int textw = ctx->text_width(font, buf, -1);
|
||||
int prew = *ctx->preedit ? ctx->text_width(font, ctx->preedit, -1) : 0;
|
||||
int texth = ctx->text_height(font);
|
||||
int ofx = r.w - ctx->style->padding - textw - prew - 1;
|
||||
int textx = r.x + mu_min(ofx, ctx->style->padding);
|
||||
int texty = r.y + (r.h - texth) / 2;
|
||||
mu_push_clip_rect(ctx, r);
|
||||
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
|
||||
if (prew) {
|
||||
mu_draw_text(ctx, font, ctx->preedit, -1, mu_vec2(textx + textw, texty), color);
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw, texty + texth, prew, 1), color);
|
||||
}
|
||||
mu_draw_rect(ctx, mu_rect(textx + textw + prew, texty, 1, texth), color);
|
||||
mu_pop_clip_rect(ctx);
|
||||
}
|
||||
|
||||
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars,
|
||||
mu_Id id, mu_Rect r, int opt)
|
||||
{
|
||||
int res = mu_textbox_input(ctx, buf, bufsz, max_chars, id, r, opt);
|
||||
mu_draw_textbox(ctx, buf, id, r, opt);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -836,8 +945,8 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
|
||||
}
|
||||
if (ctx->number_edit == id) {
|
||||
int res = mu_textbox_raw(
|
||||
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
|
||||
if (res & MU_RES_SUBMIT || ctx->focus != id) {
|
||||
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), 0, id, r, 0);
|
||||
if (res & MU_RES_SUBMIT || ctx->kb_focus != id) {
|
||||
*value = strtod(ctx->number_edit_buf, NULL);
|
||||
ctx->number_edit = 0;
|
||||
} else {
|
||||
@@ -851,7 +960,7 @@ static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id)
|
||||
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) {
|
||||
mu_Id id = mu_get_id(ctx, &buf, sizeof(buf));
|
||||
mu_Rect r = mu_layout_next(ctx);
|
||||
return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
|
||||
return mu_textbox_raw(ctx, buf, bufsz, 0, id, r, opt);
|
||||
}
|
||||
|
||||
|
||||
@@ -878,6 +987,12 @@ int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high,
|
||||
v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
|
||||
if (step) { v = ((long long)((v + step / 2) / step)) * step; }
|
||||
}
|
||||
/* left/right are not navigation keys, so they need no arbitration with the
|
||||
** focus movement in mu_end() */
|
||||
if (ctx->kb_focus == id && ctx->key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) {
|
||||
mu_Real d = step ? step : (high - low) / 100;
|
||||
v += (ctx->key_pressed & MU_KEY_RIGHT) ? d : -d;
|
||||
}
|
||||
/* clamp and store value, update res */
|
||||
*value = v = mu_clamp(v, low, high);
|
||||
if (last != v) { res |= MU_RES_CHANGE; }
|
||||
@@ -916,6 +1031,9 @@ int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step,
|
||||
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) {
|
||||
*value += ctx->mouse_delta.x * step;
|
||||
}
|
||||
if (ctx->kb_focus == id && ctx->key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) {
|
||||
*value += (ctx->key_pressed & MU_KEY_RIGHT) ? step : -step;
|
||||
}
|
||||
/* set flag if value changed */
|
||||
if (*value != last) { res |= MU_RES_CHANGE; }
|
||||
|
||||
@@ -943,7 +1061,7 @@ static int header(mu_Context *ctx, const char *label, int istreenode, int opt) {
|
||||
mu_update_control(ctx, id, r, 0);
|
||||
|
||||
/* handle click */
|
||||
active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id);
|
||||
active ^= control_activated(ctx, id);
|
||||
|
||||
/* update pool ref */
|
||||
if (idx >= 0) {
|
||||
@@ -1006,7 +1124,7 @@ void mu_end_treenode(mu_Context *ctx) {
|
||||
base.w = ctx->style->scrollbar_size; \
|
||||
\
|
||||
/* handle input */ \
|
||||
mu_update_control(ctx, id, base, 0); \
|
||||
mu_update_control(ctx, id, base, MU_OPT_NOKBNAV); \
|
||||
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \
|
||||
cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \
|
||||
} \
|
||||
@@ -1111,7 +1229,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
|
||||
/* do title text */
|
||||
if (~opt & MU_OPT_NOTITLE) {
|
||||
mu_Id id = mu_get_id(ctx, "!title", 6);
|
||||
mu_update_control(ctx, id, tr, opt);
|
||||
mu_update_control(ctx, id, tr, opt | MU_OPT_NOKBNAV);
|
||||
mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt);
|
||||
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
|
||||
cnt->rect.x += ctx->mouse_delta.x;
|
||||
@@ -1127,7 +1245,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
|
||||
mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
|
||||
tr.w -= r.w;
|
||||
mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]);
|
||||
mu_update_control(ctx, id, r, opt);
|
||||
mu_update_control(ctx, id, r, opt | MU_OPT_NOKBNAV);
|
||||
if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) {
|
||||
cnt->open = 0;
|
||||
}
|
||||
@@ -1141,7 +1259,7 @@ int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt
|
||||
int sz = ctx->style->title_height;
|
||||
mu_Id id = mu_get_id(ctx, "!resize", 7);
|
||||
mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
|
||||
mu_update_control(ctx, id, r, opt);
|
||||
mu_update_control(ctx, id, r, opt | MU_OPT_NOKBNAV);
|
||||
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
|
||||
cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x);
|
||||
cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y);
|
||||
|
||||
+34
-3
@@ -58,6 +58,7 @@ enum {
|
||||
MU_COLOR_BASEFOCUS,
|
||||
MU_COLOR_SCROLLBASE,
|
||||
MU_COLOR_SCROLLTHUMB,
|
||||
MU_COLOR_FOCUSRING,
|
||||
MU_COLOR_MAX
|
||||
};
|
||||
|
||||
@@ -88,7 +89,8 @@ enum {
|
||||
MU_OPT_AUTOSIZE = (1 << 9),
|
||||
MU_OPT_POPUP = (1 << 10),
|
||||
MU_OPT_CLOSED = (1 << 11),
|
||||
MU_OPT_EXPANDED = (1 << 12)
|
||||
MU_OPT_EXPANDED = (1 << 12),
|
||||
MU_OPT_NOKBNAV = (1 << 13) /* exclude from the keyboard focus cycle */
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -102,7 +104,13 @@ enum {
|
||||
MU_KEY_CTRL = (1 << 1),
|
||||
MU_KEY_ALT = (1 << 2),
|
||||
MU_KEY_BACKSPACE = (1 << 3),
|
||||
MU_KEY_RETURN = (1 << 4)
|
||||
MU_KEY_RETURN = (1 << 4),
|
||||
MU_KEY_TAB = (1 << 5),
|
||||
MU_KEY_UP = (1 << 6),
|
||||
MU_KEY_DOWN = (1 << 7),
|
||||
MU_KEY_LEFT = (1 << 8),
|
||||
MU_KEY_RIGHT = (1 << 9),
|
||||
MU_KEY_SPACE = (1 << 10)
|
||||
};
|
||||
|
||||
|
||||
@@ -179,6 +187,17 @@ struct mu_Context {
|
||||
mu_Style *style;
|
||||
mu_Id hover;
|
||||
mu_Id focus;
|
||||
/* Unlike `focus`, which only lives for as long as a mouse button is held,
|
||||
** `kb_focus` persists across frames and is moved by Tab / arrow keys. The
|
||||
** nav_* fields are rebuilt every frame as mu_update_control() walks the
|
||||
** controls in submission order. */
|
||||
mu_Id kb_focus;
|
||||
mu_Id nav_first; /* first navigable control of this frame */
|
||||
mu_Id nav_last; /* last navigable control of this frame */
|
||||
mu_Id nav_prev; /* control registered just before kb_focus */
|
||||
mu_Id nav_next; /* control registered just after kb_focus */
|
||||
mu_Id nav_cursor; /* scan state: last registered control */
|
||||
int kb_focus_seen; /* kb_focus was registered this frame */
|
||||
mu_Id last_id;
|
||||
mu_Rect last_rect;
|
||||
int last_zindex;
|
||||
@@ -210,6 +229,11 @@ struct mu_Context {
|
||||
int key_down;
|
||||
int key_pressed;
|
||||
char input_text[32];
|
||||
char preedit[32]; /* IME composition text, not yet committed */
|
||||
/* Set by mu_textbox_input() while a textbox has the keyboard focus, so the
|
||||
** host can enable the IME and place its candidate window. */
|
||||
int text_input;
|
||||
mu_Rect text_input_rect;
|
||||
};
|
||||
|
||||
|
||||
@@ -221,6 +245,7 @@ void mu_init(mu_Context *ctx);
|
||||
void mu_begin(mu_Context *ctx);
|
||||
void mu_end(mu_Context *ctx);
|
||||
void mu_set_focus(mu_Context *ctx, mu_Id id);
|
||||
void mu_set_kb_focus(mu_Context *ctx, mu_Id id);
|
||||
mu_Id mu_get_id(mu_Context *ctx, const void *data, int size);
|
||||
void mu_push_id(mu_Context *ctx, const void *data, int size);
|
||||
void mu_pop_id(mu_Context *ctx);
|
||||
@@ -243,6 +268,7 @@ void mu_input_scroll(mu_Context *ctx, int x, int y);
|
||||
void mu_input_keydown(mu_Context *ctx, int key);
|
||||
void mu_input_keyup(mu_Context *ctx, int key);
|
||||
void mu_input_text(mu_Context *ctx, const char *text);
|
||||
void mu_input_preedit(mu_Context *ctx, const char *text);
|
||||
|
||||
mu_Command* mu_push_command(mu_Context *ctx, int type, int size);
|
||||
int mu_next_command(mu_Context *ctx, mu_Command **cmd);
|
||||
@@ -278,7 +304,12 @@ void mu_text(mu_Context *ctx, const char *text);
|
||||
void mu_label(mu_Context *ctx, const char *text);
|
||||
int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt);
|
||||
int mu_checkbox(mu_Context *ctx, const char *label, int *state);
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt);
|
||||
/* Edit `buf` in place, without drawing. `max_chars` limits the length in UTF-8
|
||||
** characters (0 for no limit beyond `bufsz`). Use this with mu_draw_textbox(),
|
||||
** or with custom drawing when the default appearance does not fit. */
|
||||
int mu_textbox_input(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
|
||||
void mu_draw_textbox(mu_Context *ctx, const char *buf, mu_Id id, mu_Rect r, int opt);
|
||||
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, int max_chars, mu_Id id, mu_Rect r, int opt);
|
||||
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt);
|
||||
int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt);
|
||||
int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt);
|
||||
|
||||
+45
-9
@@ -61,13 +61,13 @@ static const FontSpec menu_font = { FONT_GOTHIC, FONT_WEIGHT_NORMAL, MODAL_FONT_
|
||||
|
||||
static int text_width_cb(mu_Font font, const char *text, int len) {
|
||||
int w;
|
||||
font_measure_text(*(const FontSpec *)font, text, len, &w, NULL);
|
||||
font_measure_text(*(const FontSpec *)font, text, len, true, &w, NULL);
|
||||
return w;
|
||||
}
|
||||
|
||||
static int text_height_cb(mu_Font font) {
|
||||
int h;
|
||||
font_measure_text(*(const FontSpec *)font, "", -1, NULL, &h);
|
||||
font_measure_text(*(const FontSpec *)font, "", -1, true, NULL, &h);
|
||||
return h;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ static void set_light_style(mu_Style *style) {
|
||||
[MU_COLOR_BASEFOCUS] = { 235, 235, 242, 255 },
|
||||
[MU_COLOR_SCROLLBASE] = { 200, 200, 200, 255 },
|
||||
[MU_COLOR_SCROLLTHUMB] = { 170, 170, 170, 255 },
|
||||
[MU_COLOR_FOCUSRING] = { 40, 90, 200, 255 },
|
||||
};
|
||||
memcpy(style->colors, light, sizeof(light));
|
||||
}
|
||||
@@ -119,6 +120,9 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
|
||||
case SDL_TEXTINPUT:
|
||||
mu_input_text(ctx, e->text.text);
|
||||
break;
|
||||
case SDL_TEXTEDITING:
|
||||
mu_input_preedit(ctx, e->edit.text);
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
int b = 0;
|
||||
@@ -160,15 +164,23 @@ bool modal_default_handler(const SDL_Event *e, modal *modal) {
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE)
|
||||
// Esc cancels an IME composition first, not the modal.
|
||||
if (e->type == SDL_KEYDOWN && e->key.keysym.sym == SDLK_ESCAPE &&
|
||||
!*ctx->preedit)
|
||||
modal->cancelled = true;
|
||||
int c = 0;
|
||||
switch (e->key.keysym.sym) {
|
||||
case SDLK_LSHIFT: case SDLK_RSHIFT: c = MU_KEY_SHIFT; break;
|
||||
case SDLK_LCTRL: case SDLK_RCTRL: c = MU_KEY_CTRL; break;
|
||||
case SDLK_LALT: case SDLK_RALT: c = MU_KEY_ALT; break;
|
||||
case SDLK_RETURN: c = MU_KEY_RETURN; break;
|
||||
case SDLK_BACKSPACE: c = MU_KEY_BACKSPACE; break;
|
||||
case SDLK_LSHIFT: case SDLK_RSHIFT: c = MU_KEY_SHIFT; break;
|
||||
case SDLK_LCTRL: case SDLK_RCTRL: c = MU_KEY_CTRL; break;
|
||||
case SDLK_LALT: case SDLK_RALT: c = MU_KEY_ALT; break;
|
||||
case SDLK_RETURN: case SDLK_KP_ENTER: c = MU_KEY_RETURN; break;
|
||||
case SDLK_BACKSPACE: c = MU_KEY_BACKSPACE; break;
|
||||
case SDLK_TAB: c = MU_KEY_TAB; break;
|
||||
case SDLK_UP: c = MU_KEY_UP; break;
|
||||
case SDLK_DOWN: c = MU_KEY_DOWN; break;
|
||||
case SDLK_LEFT: c = MU_KEY_LEFT; break;
|
||||
case SDLK_RIGHT: c = MU_KEY_RIGHT; break;
|
||||
case SDLK_SPACE: c = MU_KEY_SPACE; break;
|
||||
}
|
||||
if (!c)
|
||||
break;
|
||||
@@ -186,7 +198,7 @@ static void render_text(mu_Font font, const char *str, mu_Vec2 pos, mu_Color col
|
||||
if (!*str)
|
||||
return;
|
||||
SDL_Color col = { color.r, color.g, color.b, color.a };
|
||||
SDL_Surface *s = font_render_text(*(const FontSpec *)font, str, col);
|
||||
SDL_Surface *s = font_render_text(*(const FontSpec *)font, str, col, true);
|
||||
if (!s)
|
||||
return;
|
||||
SDL_Texture *t = SDL_CreateTextureFromSurface(gfx_renderer, s);
|
||||
@@ -286,6 +298,27 @@ static void modal_render(void) {
|
||||
SDL_SetRenderDrawBlendMode(gfx_renderer, SDL_BLENDMODE_NONE);
|
||||
}
|
||||
|
||||
// Turn SDL's text input on while a textbox has the keyboard focus, and keep the
|
||||
// IME candidate window near it. The rect is in logical (view) coordinates.
|
||||
static void update_text_input(void) {
|
||||
static SDL_Rect last_rect;
|
||||
if (!ctx->text_input) {
|
||||
if (SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
return;
|
||||
}
|
||||
if (!SDL_IsTextInputActive()) {
|
||||
SDL_StartTextInput();
|
||||
last_rect = (SDL_Rect){0, 0, 0, 0};
|
||||
}
|
||||
mu_Rect r = ctx->text_input_rect;
|
||||
SDL_Rect wr = gfx_viewToWindowRect((SDL_Rect){r.x, r.y, r.w, r.h});
|
||||
if (memcmp(&wr, &last_rect, sizeof(wr))) {
|
||||
last_rect = wr;
|
||||
SDL_SetTextInputRect(&wr);
|
||||
}
|
||||
}
|
||||
|
||||
bool modal_handle_event(const SDL_Event *e) {
|
||||
if (!current_modal)
|
||||
return false;
|
||||
@@ -334,6 +367,7 @@ void modal_run(modal *m) {
|
||||
mu_begin(ctx);
|
||||
open = m->build(ctx, m);
|
||||
mu_end(ctx);
|
||||
update_text_input();
|
||||
|
||||
// Present only when the overlay changed.
|
||||
mu_Id hash = mu_get_id(ctx, ctx->command_list.items, ctx->command_list.idx);
|
||||
@@ -348,6 +382,8 @@ void modal_run(modal *m) {
|
||||
current_modal = NULL;
|
||||
free(ctx);
|
||||
ctx = NULL;
|
||||
if (SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
gfx_requestRedraw(); // repaint once more to clear the overlay
|
||||
|
||||
// The gesture that opened/dismissed the dialog must not leave the engine
|
||||
|
||||
+5
-3
@@ -167,9 +167,11 @@ void nact_init() {
|
||||
|
||||
sel_init();
|
||||
|
||||
nact->patch_ec = 1; // TODO: revisit
|
||||
nact->patch_emen = 0;
|
||||
nact->patch_g0 = 0;
|
||||
// The EC bug was introduced in System 3.8. Older games expect the
|
||||
// patch_ec=true behavior.
|
||||
nact->patch_ec = !sl_is_s380;
|
||||
nact->patch_emen = false;
|
||||
nact->patch_g0 = false;
|
||||
|
||||
msg_msgHankakuMode = 0;
|
||||
}
|
||||
|
||||
+4
-4
@@ -97,10 +97,10 @@ typedef struct {
|
||||
/* 選択肢関連 */
|
||||
sel_t sel;
|
||||
|
||||
/* patch 関連 */
|
||||
int patch_ec; /* see patch_ec command */
|
||||
int patch_emen; /* see patch_emen command */
|
||||
int patch_g0; /* see patch g0 command */
|
||||
/* patch flags */
|
||||
bool patch_ec;
|
||||
bool patch_emen;
|
||||
bool patch_g0;
|
||||
|
||||
/* ain 関連 */
|
||||
S39AIN ain;
|
||||
|
||||
@@ -182,15 +182,13 @@ static void extract_alpha(qnt_header *qnt, uint8_t *pic, uint8_t *b) {
|
||||
w = qnt->width;
|
||||
h = qnt->height;
|
||||
|
||||
i = 1;
|
||||
if (w > 1) {
|
||||
pic[0] = raw[0];
|
||||
for (x = 1; x < w; x++) {
|
||||
pic[x] = pic[x-1] - raw[i];
|
||||
i++;
|
||||
}
|
||||
if (w%2) i++;
|
||||
i = 0;
|
||||
pic[0] = raw[i++];
|
||||
for (x = 1; x < w; x++) {
|
||||
pic[x] = pic[x-1] - raw[i];
|
||||
i++;
|
||||
}
|
||||
if (w%2) i++;
|
||||
|
||||
if (h > 1) {
|
||||
for (y = 1; y < h; y++) {
|
||||
|
||||
+32
-2
@@ -22,8 +22,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static void odd_size_test(void) {
|
||||
FILE *fp = fopen("testdata/odd-size.qnt", "rb");
|
||||
static uint8_t *read_file(const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
ASSERT_TRUE(fp);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
@@ -32,6 +32,11 @@ static void odd_size_test(void) {
|
||||
uint8_t *data = malloc(size);
|
||||
ASSERT_EQUAL(fread(data, 1, size, fp), size);
|
||||
fclose(fp);
|
||||
return data;
|
||||
}
|
||||
|
||||
static void odd_size_test(void) {
|
||||
uint8_t *data = read_file("testdata/odd-size.qnt");
|
||||
|
||||
cgdata *cg = qnt_extract(data);
|
||||
ASSERT_EQUAL(cg->width, 3);
|
||||
@@ -58,6 +63,31 @@ static void odd_size_test(void) {
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void width1_test(void) {
|
||||
uint8_t *data = read_file("testdata/width1.qnt");
|
||||
|
||||
cgdata *cg = qnt_extract(data);
|
||||
ASSERT_EQUAL(cg->width, 1);
|
||||
ASSERT_EQUAL(cg->height, 3);
|
||||
ASSERT_TRUE(cg->alpha);
|
||||
|
||||
const uint8_t expected[] = {
|
||||
0x10, 0x20, 0x30,
|
||||
0x40, 0x50, 0x60,
|
||||
0x70, 0x80, 0x90,
|
||||
};
|
||||
ASSERT_TRUE(!memcmp(cg->pic, expected, sizeof(expected)));
|
||||
|
||||
const uint8_t expected_alpha[] = { 0xff, 0x80, 0x40 };
|
||||
ASSERT_TRUE(!memcmp(cg->alpha, expected_alpha, sizeof(expected_alpha)));
|
||||
|
||||
free(cg->pic);
|
||||
free(cg->alpha);
|
||||
free(cg);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void qnt_test(void) {
|
||||
odd_size_test();
|
||||
width1_test();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
const uint8_t *sl_sco;
|
||||
int sl_page;
|
||||
int sl_index;
|
||||
bool sl_is_s380;
|
||||
|
||||
static void pop_state(uint8_t tag);
|
||||
|
||||
@@ -102,6 +103,7 @@ bool sl_init(void) {
|
||||
|
||||
stack_top = stack_buf;
|
||||
sl_jmpFar(0);
|
||||
sl_is_s380 = !memcmp(sl_sco, "S380", 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ struct stack_frame_info {
|
||||
uint8_t *p; // for internal use
|
||||
};
|
||||
|
||||
extern bool sl_is_s380;
|
||||
|
||||
// Use functions below instead of accessing these variables directly.
|
||||
extern const uint8_t *sl_sco; // scenario page buffer
|
||||
extern int sl_page; // current scenario page (0-based)
|
||||
|
||||
+8
-27
@@ -303,7 +303,7 @@ static void init_selwindow() {
|
||||
}
|
||||
for (i = 0; i < regnum; i++) {
|
||||
TRACE_MESSAGE("%d:%s\n", i +1, elm[i]);
|
||||
ags_drawString(r.x +2, r.y + i * (sel.MsgFontSize +2) +1, elm[i], sel.MsgFontColor, sel.MsgFontSize, NULL);
|
||||
ags_drawString(r.x +2, r.y + i * (sel.MsgFontSize +2) +2, elm[i], sel.MsgFontColor, sel.MsgFontSize, NULL);
|
||||
}
|
||||
ags_updateArea(saveArea.x, saveArea.y, saveArea.w, saveArea.h);
|
||||
|
||||
@@ -376,31 +376,12 @@ static void lineEncloseElement(SDL_Rect *r, int col, bool thick) {
|
||||
static void encloseElement(int sw, int no) {
|
||||
SDL_Rect *r = &workR[no];
|
||||
|
||||
if (sw == 0) { /* off */
|
||||
if (sel.WinBackgroundTransparent != 255) {
|
||||
ags_restoreRegion(saveimg2, r->x, r->y);
|
||||
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
|
||||
saveimg2 = NULL;
|
||||
} else {
|
||||
switch(sel.EncloseType) {
|
||||
case 0:
|
||||
lineEncloseElement(r, sel.WinBackgroundColor, true); break;
|
||||
case 1:
|
||||
lineEncloseElement(r, sel.WinBackgroundColor, false); break;
|
||||
case 2:
|
||||
ags_fillRectangle(r->x, r->y, r->w +2, r->h +2, sel.WinBackgroundColor);
|
||||
ags_drawString(r->x +2, r->y +1, elm[no], sel.MsgFontColor, sel.MsgFontSize, NULL);
|
||||
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else { /* on */
|
||||
if (sel.WinBackgroundTransparent != 255) {
|
||||
saveimg2 = ags_saveRegion(r->x, r->y, r->w +2, r->h +2);
|
||||
}
|
||||
if (sw == 0) {
|
||||
ags_restoreRegion(saveimg2, r->x, r->y);
|
||||
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
|
||||
saveimg2 = NULL;
|
||||
} else {
|
||||
saveimg2 = ags_saveRegion(r->x, r->y, r->w +2, r->h +2);
|
||||
switch(sel.EncloseType) {
|
||||
case 0:
|
||||
lineEncloseElement(r, sel.WinFrameColor, true); break;
|
||||
@@ -408,7 +389,7 @@ static void encloseElement(int sw, int no) {
|
||||
lineEncloseElement(r, 255, false); break;
|
||||
case 2:
|
||||
ags_fillRectangle(r->x, r->y, r->w +2, r->h +2, sel.MsgFontColor);
|
||||
ags_drawString(r->x +2, r->y +1, elm[no], sel.WinBackgroundColor, sel.MsgFontSize, NULL);
|
||||
ags_drawString(r->x +2, r->y +2, elm[no], sel.WinBackgroundColor, sel.MsgFontSize, NULL);
|
||||
ags_updateArea(r->x, r->y, r->w +2, r->h +2);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void font_test(void);
|
||||
void cache_test(void);
|
||||
void gameresource_test(void);
|
||||
void hankaku_test(void);
|
||||
void qnt_test(void);
|
||||
@@ -41,6 +43,8 @@ void sys_message(int level, char *format, ...) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
cache_test();
|
||||
font_test();
|
||||
gameresource_test();
|
||||
variable_test();
|
||||
hankaku_test();
|
||||
|
||||
@@ -83,12 +83,7 @@ void sys_error(char *format, ...) {
|
||||
|
||||
void sys_exit(int code) {
|
||||
sys35_remove();
|
||||
#ifdef __EMSCRIPTEN__
|
||||
EM_ASM( xsystem35.shell.quit(); );
|
||||
sys_sleep(1000000000);
|
||||
#else
|
||||
exit(code);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t sys_get_ticks(void) {
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+3
-9
@@ -87,7 +87,6 @@ static char fontface[FONTTYPEMAX];
|
||||
|
||||
static const char *savedir;
|
||||
static bool font_noantialias;
|
||||
static bool enable_zb = false;
|
||||
static bool integer_scaling = false;
|
||||
/* fullscreen on from command line */
|
||||
static bool fs_on;
|
||||
@@ -147,13 +146,13 @@ static void sys35_usage(bool verbose) {
|
||||
static void sys35_init() {
|
||||
int i;
|
||||
|
||||
nact_init();
|
||||
|
||||
sl_init();
|
||||
|
||||
nact_init();
|
||||
|
||||
v_init();
|
||||
|
||||
ags_init(render_driver, enable_zb);
|
||||
ags_init(render_driver);
|
||||
|
||||
for (i = 0; i < FONTTYPEMAX; i++)
|
||||
font_set_name_and_index(i, fontname_tt[i], fontface[i]);
|
||||
@@ -264,8 +263,6 @@ static void sys35_ParseOption(int *argc, char **argv) {
|
||||
fs_on = true;
|
||||
} else if (0 == strcmp(argv[i], "-noantialias")) {
|
||||
font_noantialias = true;
|
||||
} else if (0 == strcmp(argv[i], "-enable_zb")) {
|
||||
enable_zb = true;
|
||||
} else if (0 == strcmp(argv[i], "-ttfont_gothic")) {
|
||||
if (argv[i + 1] != NULL) {
|
||||
fontname_tt[FONT_GOTHIC] = argv[i + 1];
|
||||
@@ -332,9 +329,6 @@ static void check_profile() {
|
||||
font_noantialias = !antialias;
|
||||
}
|
||||
|
||||
/* Enable ZB command (font weight) */
|
||||
get_boolean_profile("enable_zb", &enable_zb);
|
||||
|
||||
/* Audio buffer size */
|
||||
param = get_profile("audio_buffer_size");
|
||||
if (param) {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
MS FILE,"patchec.adv":
|
||||
|
||||
; --- EC with a registered rectangle ---
|
||||
; patchEC defaults to off, so EC clears a region 1px narrower and
|
||||
; shorter than the rectangle registered by ES.
|
||||
CF 100,100,40,30,7:
|
||||
ES 1,3,100,100,40,30:
|
||||
EC 1:
|
||||
PT0 RND,120,115: ~AssertEquals RND,3,__LINE__:
|
||||
PT0 RND,138,128: ~AssertEquals RND,3,__LINE__:
|
||||
PT0 RND,139,115: ~AssertEquals RND,7,__LINE__:
|
||||
PT0 RND,120,129: ~AssertEquals RND,7,__LINE__:
|
||||
|
||||
; patchEC 1 disables the adjustment, so EC clears the whole rectangle.
|
||||
CF 100,100,40,30,7:
|
||||
patchEC 1:
|
||||
EC 1:
|
||||
PT0 RND,139,115: ~AssertEquals RND,3,__LINE__:
|
||||
PT0 RND,120,129: ~AssertEquals RND,3,__LINE__:
|
||||
|
||||
; Only eFlag == 1 turns the patch on; any other value restores the bug.
|
||||
CF 100,100,40,30,7:
|
||||
patchEC 2:
|
||||
EC 1:
|
||||
PT0 RND,139,115: ~AssertEquals RND,7,__LINE__:
|
||||
patchEC 0:
|
||||
|
||||
; --- EC 0 clears the current view area, now with the same adjustment ---
|
||||
ZZ5 D18:
|
||||
WV 100,100,50,40:
|
||||
CF 100,100,50,40,7:
|
||||
EC 0:
|
||||
PT0 RND,120,120: ~AssertEquals RND,0,__LINE__:
|
||||
PT0 RND,148,138: ~AssertEquals RND,0,__LINE__:
|
||||
PT0 RND,149,120: ~AssertEquals RND,7,__LINE__:
|
||||
PT0 RND,120,139: ~AssertEquals RND,7,__LINE__:
|
||||
|
||||
CF 100,100,50,40,7:
|
||||
patchEC 1:
|
||||
EC 0:
|
||||
PT0 RND,149,120: ~AssertEquals RND,0,__LINE__:
|
||||
PT0 RND,120,139: ~AssertEquals RND,0,__LINE__:
|
||||
patchEC 0:
|
||||
WV 0,0,D18,D19:
|
||||
|
||||
%0:
|
||||
@@ -0,0 +1,36 @@
|
||||
MS FILE,"patchemen.adv":
|
||||
|
||||
ES 1,0,10,10,20,20:
|
||||
ES 2,0,20,20,20,20:
|
||||
|
||||
; --- EM: point-in-rectangle test ---
|
||||
; patchEMEN defaults to off, so the rectangle is treated as 1px
|
||||
; narrower and shorter than registered by ES.
|
||||
EM 1,RND,10,10: ~AssertEquals RND,1,__LINE__:
|
||||
EM 1,RND,28,28: ~AssertEquals RND,1,__LINE__:
|
||||
EM 1,RND,29,20: ~AssertEquals RND,0,__LINE__:
|
||||
EM 1,RND,20,29: ~AssertEquals RND,0,__LINE__:
|
||||
EM 1,RND,9,10: ~AssertEquals RND,0,__LINE__:
|
||||
|
||||
; patchEMEN 1 disables the adjustment.
|
||||
patchEMEN 1:
|
||||
EM 1,RND,29,29: ~AssertEquals RND,1,__LINE__:
|
||||
EM 1,RND,30,20: ~AssertEquals RND,0,__LINE__:
|
||||
|
||||
; Only eFlag == 1 turns the patch on; any other value restores the bug.
|
||||
patchEMEN 2:
|
||||
EM 1,RND,29,29: ~AssertEquals RND,0,__LINE__:
|
||||
patchEMEN 0:
|
||||
|
||||
; --- EN: number of the rectangle containing the point ---
|
||||
EN RND,1,2,15,15: ~AssertEquals RND,1,__LINE__:
|
||||
EN RND,1,2,25,25: ~AssertEquals RND,2,__LINE__:
|
||||
EN RND,1,2,5,5: ~AssertEquals RND,0,__LINE__:
|
||||
|
||||
; The patch flag also applies to EN's edge handling.
|
||||
EN RND,1,1,29,29: ~AssertEquals RND,0,__LINE__:
|
||||
patchEMEN 1:
|
||||
EN RND,1,1,29,29: ~AssertEquals RND,1,__LINE__:
|
||||
patchEMEN 0:
|
||||
|
||||
%0:
|
||||
@@ -16,6 +16,8 @@
|
||||
%#math.adv:
|
||||
%#variable.adv:
|
||||
%#unitmap.adv:
|
||||
%#patchec.adv:
|
||||
%#patchemen.adv:
|
||||
|
||||
HH0,tests_passed: ' tests passed' R
|
||||
HH0,tests_failed: ' tests failed' R
|
||||
|
||||
@@ -8,6 +8,8 @@ stack.adv
|
||||
math.adv
|
||||
variable.adv
|
||||
unitmap.adv
|
||||
patchec.adv
|
||||
patchemen.adv
|
||||
|
||||
#DLLHeader
|
||||
ShString.HEL
|
||||
|
||||
Binary file not shown.
@@ -115,10 +115,3 @@
|
||||
# per line, with '#' treated as comments.
|
||||
|
||||
#censor: censor_list.txt
|
||||
|
||||
# Enables the ZB command (change the font weight). It is disabled by default
|
||||
# because it makes the text too bold with our default fonts.
|
||||
# This option is experimental and may be removed in the future. See
|
||||
# https://github.com/kichikuou/xsystem35-sdl2/issues/44 for details.
|
||||
|
||||
#enable_zb: Yes
|
||||
|
||||
Reference in New Issue
Block a user