ruby2d 0.12.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.md +7 -0
- data/README.md +141 -0
- data/USAGE.md +2435 -0
- data/assets/Rakefile +637 -0
- data/assets/build_support/CMakeLists.txt +94 -0
- data/assets/build_support/mruby/build_config.rb +34 -0
- data/assets/build_support/mruby/build_config_wasm.rb +30 -0
- data/assets/build_support/sdl_test.c +174 -0
- data/assets/deps.yaml +16 -0
- data/assets/platform/include/SDL3/SDL.h +91 -0
- data/assets/platform/include/SDL3/SDL_assert.h +705 -0
- data/assets/platform/include/SDL3/SDL_asyncio.h +550 -0
- data/assets/platform/include/SDL3/SDL_atomic.h +692 -0
- data/assets/platform/include/SDL3/SDL_audio.h +2370 -0
- data/assets/platform/include/SDL3/SDL_begin_code.h +553 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_bits.h +47 -27
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_blendmode.h +59 -55
- data/assets/platform/include/SDL3/SDL_camera.h +535 -0
- data/assets/platform/include/SDL3/SDL_clipboard.h +331 -0
- data/assets/{include/SDL2/close_code.h → platform/include/SDL3/SDL_close_code.h} +9 -8
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_copying.h +3 -1
- data/assets/platform/include/SDL3/SDL_cpuinfo.h +374 -0
- data/assets/platform/include/SDL3/SDL_dialog.h +343 -0
- data/assets/platform/include/SDL3/SDL_dlopennote.h +234 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_egl.h +11 -8
- data/assets/platform/include/SDL3/SDL_endian.h +645 -0
- data/assets/platform/include/SDL3/SDL_error.h +226 -0
- data/assets/platform/include/SDL3/SDL_events.h +1645 -0
- data/assets/platform/include/SDL3/SDL_filesystem.h +537 -0
- data/assets/platform/include/SDL3/SDL_gamepad.h +1656 -0
- data/assets/platform/include/SDL3/SDL_gpu.h +4608 -0
- data/assets/platform/include/SDL3/SDL_guid.h +106 -0
- data/assets/platform/include/SDL3/SDL_haptic.h +1461 -0
- data/assets/platform/include/SDL3/SDL_hidapi.h +571 -0
- data/assets/platform/include/SDL3/SDL_hints.h +5001 -0
- data/assets/platform/include/SDL3/SDL_init.h +507 -0
- data/assets/platform/include/SDL3/SDL_intrin.h +410 -0
- data/assets/platform/include/SDL3/SDL_iostream.h +1379 -0
- data/assets/platform/include/SDL3/SDL_joystick.h +1385 -0
- data/assets/platform/include/SDL3/SDL_keyboard.h +608 -0
- data/assets/platform/include/SDL3/SDL_keycode.h +347 -0
- data/assets/platform/include/SDL3/SDL_loadso.h +145 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_locale.h +40 -24
- data/assets/platform/include/SDL3/SDL_log.h +541 -0
- data/assets/platform/include/SDL3/SDL_main.h +704 -0
- data/assets/platform/include/SDL3/SDL_main_impl.h +151 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_messagebox.h +85 -48
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_metal.h +29 -29
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_misc.h +13 -12
- data/assets/platform/include/SDL3/SDL_mouse.h +813 -0
- data/assets/platform/include/SDL3/SDL_mutex.h +1117 -0
- data/assets/platform/include/SDL3/SDL_oldnames.h +1339 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl.h +1247 -278
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengl_glext.h +5 -1
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles.h +6 -7
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2.h +10 -11
- data/assets/platform/include/SDL3/SDL_pen.h +198 -0
- data/assets/platform/include/SDL3/SDL_pixels.h +1441 -0
- data/assets/{include/SDL2/SDL_test_log.h → platform/include/SDL3/SDL_platform.h} +25 -26
- data/assets/platform/include/SDL3/SDL_platform_defines.h +497 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_power.h +40 -20
- data/assets/platform/include/SDL3/SDL_process.h +441 -0
- data/assets/platform/include/SDL3/SDL_properties.h +572 -0
- data/assets/platform/include/SDL3/SDL_rect.h +528 -0
- data/assets/platform/include/SDL3/SDL_render.h +3028 -0
- data/assets/platform/include/SDL3/SDL_revision.h +59 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_scancode.h +59 -68
- data/assets/platform/include/SDL3/SDL_sensor.h +321 -0
- data/assets/platform/include/SDL3/SDL_stdinc.h +6186 -0
- data/assets/platform/include/SDL3/SDL_storage.h +686 -0
- data/assets/platform/include/SDL3/SDL_surface.h +1769 -0
- data/assets/platform/include/SDL3/SDL_system.h +840 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test.h +16 -22
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_assert.h +27 -34
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_common.h +109 -52
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_compare.h +22 -14
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_crc32.h +27 -30
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_font.h +50 -49
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_fuzzer.h +64 -79
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_harness.h +47 -30
- data/assets/platform/include/SDL3/SDL_test_log.h +83 -0
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_md5.h +21 -28
- data/assets/{include/SDL2 → platform/include/SDL3}/SDL_test_memory.h +19 -16
- data/assets/platform/include/SDL3/SDL_thread.h +602 -0
- data/assets/platform/include/SDL3/SDL_time.h +249 -0
- data/assets/platform/include/SDL3/SDL_timer.h +454 -0
- data/assets/platform/include/SDL3/SDL_touch.h +184 -0
- data/assets/platform/include/SDL3/SDL_tray.h +544 -0
- data/assets/platform/include/SDL3/SDL_version.h +198 -0
- data/assets/platform/include/SDL3/SDL_video.h +3497 -0
- data/assets/platform/include/SDL3/SDL_vulkan.h +287 -0
- data/assets/platform/include/SDL3_image/SDL_image.h +3328 -0
- data/assets/platform/include/SDL3_mixer/SDL_mixer.h +3369 -0
- data/assets/platform/include/SDL3_ttf/SDL_textengine.h +181 -0
- data/assets/platform/include/SDL3_ttf/SDL_ttf.h +2833 -0
- data/assets/{include → platform/include}/mrbconf.h +42 -31
- data/assets/{include → platform/include}/mruby/array.h +3 -4
- data/assets/{include → platform/include}/mruby/boxing_nan.h +10 -0
- data/assets/platform/include/mruby/boxing_no.h +59 -0
- data/assets/{include → platform/include}/mruby/boxing_word.h +72 -26
- data/assets/platform/include/mruby/class.h +157 -0
- data/assets/{include → platform/include}/mruby/common.h +30 -1
- data/assets/{include → platform/include}/mruby/compile.h +52 -74
- data/assets/{include → platform/include}/mruby/data.h +2 -2
- data/assets/{include → platform/include}/mruby/debug.h +12 -5
- data/assets/{include → platform/include}/mruby/dump.h +7 -9
- data/assets/{include → platform/include}/mruby/endian.h +1 -1
- data/assets/platform/include/mruby/error.h +173 -0
- data/assets/platform/include/mruby/gc.h +79 -0
- data/assets/{include → platform/include}/mruby/hash.h +1 -7
- data/assets/platform/include/mruby/internal.h +282 -0
- data/assets/platform/include/mruby/irep.h +143 -0
- data/assets/{include → platform/include}/mruby/istruct.h +1 -1
- data/assets/platform/include/mruby/khash.h +455 -0
- data/assets/platform/include/mruby/mempool.h +19 -0
- data/assets/{include → platform/include}/mruby/numeric.h +7 -10
- data/assets/platform/include/mruby/object.h +51 -0
- data/assets/{include → platform/include}/mruby/opcode.h +5 -5
- data/assets/{include → platform/include}/mruby/ops.h +19 -6
- data/assets/platform/include/mruby/presym/id.h +1447 -0
- data/assets/{include → platform/include}/mruby/presym/scanning.h +10 -7
- data/assets/platform/include/mruby/presym/table.h +2893 -0
- data/assets/platform/include/mruby/presym.h +65 -0
- data/assets/platform/include/mruby/proc.h +184 -0
- data/assets/{include → platform/include}/mruby/range.h +0 -2
- data/assets/{include → platform/include}/mruby/string.h +33 -53
- data/assets/platform/include/mruby/throw.h +81 -0
- data/assets/{include → platform/include}/mruby/value.h +64 -29
- data/assets/{include → platform/include}/mruby/variable.h +6 -23
- data/assets/{include → platform/include}/mruby/version.h +7 -7
- data/assets/{include → platform/include}/mruby.h +238 -116
- data/assets/platform/macos-arm64/bin/mrbc +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_image.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/macos-arm64/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/macos-arm64/lib/libmruby.a +0 -0
- data/assets/platform/wasm/lib/libFLAC.a +0 -0
- data/assets/platform/wasm/lib/libSDL3.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_image.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/wasm/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/wasm/lib/libfreetype.a +0 -0
- data/assets/platform/wasm/lib/libharfbuzz.a +0 -0
- data/assets/platform/wasm/lib/libmruby.a +0 -0
- data/assets/platform/wasm/lib/libogg.a +0 -0
- data/assets/platform/wasm/lib/libplutosvg.a +0 -0
- data/assets/platform/wasm/lib/libplutovg.a +0 -0
- data/assets/platform/wasm/lib/libpng.a +0 -0
- data/assets/platform/wasm/lib/libpng16.a +0 -0
- data/assets/platform/wasm/lib/libvorbis.a +0 -0
- data/assets/platform/wasm/lib/libvorbisfile.a +0 -0
- data/assets/platform/wasm/lib/libzlibstatic.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/bin/mrbc.exe +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_image.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/windows-arm64-mingw-ucrt/lib/libmruby.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/bin/mrbc.exe +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_image.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_mixer.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libSDL3_ttf.a +0 -0
- data/assets/platform/windows-x86_64-mingw-ucrt/lib/libmruby.a +0 -0
- data/assets/resources/fonts/outfit/OFL.txt +93 -0
- data/assets/resources/fonts/outfit/outfit.ttf +0 -0
- data/assets/resources/fonts/roboto_mono/OFL.txt +91 -0
- data/assets/resources/fonts/roboto_mono/roboto_mono.ttf +0 -0
- data/assets/resources/icons/icon.icns +0 -0
- data/assets/resources/icons/icon.png +0 -0
- data/assets/resources/spritesheets/License.txt +28 -0
- data/assets/resources/spritesheets/spritesheet-backgrounds.png +0 -0
- data/assets/resources/spritesheets/spritesheet-backgrounds.xml +16 -0
- data/assets/resources/spritesheets/spritesheet-characters.png +0 -0
- data/assets/resources/spritesheets/spritesheet-characters.xml +47 -0
- data/assets/resources/spritesheets/spritesheet-enemies.png +0 -0
- data/assets/resources/spritesheets/spritesheet-enemies.xml +62 -0
- data/assets/resources/spritesheets/spritesheet-tiles.png +0 -0
- data/assets/resources/spritesheets/spritesheet-tiles.xml +316 -0
- data/assets/{wasm → resources/web}/template.html +1 -1
- data/assets/target.rb +131 -0
- data/bin/ruby2d +281 -82
- data/examples/astar.rb +269 -0
- data/examples/asteroids.rb +403 -0
- data/examples/bezier_editor.rb +96 -0
- data/examples/boids.rb +191 -0
- data/examples/bouncing_balls.rb +119 -0
- data/examples/breakout.rb +186 -0
- data/examples/constellations.rb +100 -0
- data/examples/dashboard.rb +133 -0
- data/examples/double_pendulum.rb +254 -0
- data/examples/falling_sand.rb +291 -0
- data/examples/fireworks.rb +110 -0
- data/examples/fluid_dynamics.rb +340 -0
- data/examples/fourier_epicycles.rb +142 -0
- data/examples/fractal_tree.rb +326 -0
- data/examples/game_of_life.rb +148 -0
- data/examples/gamepads.rb +333 -0
- data/examples/hill_driver.rb +613 -0
- data/examples/logo_animation.rb +251 -0
- data/examples/lorenz_attractor.rb +115 -0
- data/examples/mandelbrot.rb +207 -0
- data/examples/marching_squares.rb +200 -0
- data/examples/maze.rb +323 -0
- data/examples/nbody.rb +338 -0
- data/examples/outrun.rb +391 -0
- data/examples/pixel_paint.rb +313 -0
- data/examples/platformer.rb +335 -0
- data/examples/ray_casting_maze.rb +175 -0
- data/examples/shadow_casting.rb +151 -0
- data/examples/shape_recognizer.rb +908 -0
- data/examples/snake.rb +144 -0
- data/examples/sprite_sheets.rb +246 -0
- data/examples/swarm.rb +206 -0
- data/examples/tsp_annealing.rb +104 -0
- data/examples/ui_controls.rb +163 -0
- data/examples/wireframe_3d.rb +139 -0
- data/ext/ruby2d/audio.c +324 -0
- data/ext/ruby2d/canvas.c +1715 -494
- data/ext/ruby2d/ext.c +541 -0
- data/ext/ruby2d/extconf.rb +122 -145
- data/ext/ruby2d/font.c +566 -20
- data/ext/ruby2d/fps.c +92 -0
- data/ext/ruby2d/image.c +466 -41
- data/ext/ruby2d/ruby2d.c +374 -1890
- data/ext/ruby2d/ruby2d.h +840 -460
- data/ext/ruby2d/shapes.c +931 -101
- data/ext/ruby2d/text.c +586 -21
- data/ext/ruby2d/window.c +1693 -283
- data/lib/ruby2d/atlas_parser.rb +194 -0
- data/lib/ruby2d/audio.rb +73 -0
- data/lib/ruby2d/benchmark.rb +259 -0
- data/lib/ruby2d/bitmap_text.rb +155 -0
- data/lib/ruby2d/button.rb +402 -0
- data/lib/ruby2d/canvas.rb +703 -279
- data/lib/ruby2d/circle.rb +206 -29
- data/lib/ruby2d/cli/browser.rb +314 -0
- data/lib/ruby2d/cli/build.rb +471 -257
- data/lib/ruby2d/cli/colorize.rb +7 -5
- data/lib/ruby2d/cli/examples.rb +127 -0
- data/lib/ruby2d/cli/launch.rb +24 -39
- data/lib/ruby2d/cli/lib_files.rb +48 -0
- data/lib/ruby2d/cli/messages.rb +40 -0
- data/lib/ruby2d/cli/setup.rb +273 -0
- data/lib/ruby2d/cli/static_server.rb +160 -0
- data/lib/ruby2d/cli/usage.rb +255 -0
- data/lib/ruby2d/color.rb +191 -69
- data/lib/ruby2d/core.rb +37 -19
- data/lib/ruby2d/deps_help.rb +89 -0
- data/lib/ruby2d/dsl.rb +51 -12
- data/lib/ruby2d/ellipse.rb +228 -0
- data/lib/ruby2d/exceptions.rb +0 -2
- data/lib/ruby2d/font.rb +34 -74
- data/lib/ruby2d/gamepad.rb +323 -0
- data/lib/ruby2d/gem_paths.rb +19 -0
- data/lib/ruby2d/image.rb +155 -52
- data/lib/ruby2d/interactive.rb +103 -0
- data/lib/ruby2d/json_parser.rb +268 -0
- data/lib/ruby2d/line.rb +194 -85
- data/lib/ruby2d/mruby_compat.rb +12 -0
- data/lib/ruby2d/polygon.rb +297 -0
- data/lib/ruby2d/polyline.rb +368 -0
- data/lib/ruby2d/quad.rb +358 -94
- data/lib/ruby2d/rectangle.rb +77 -19
- data/lib/ruby2d/renderable.rb +411 -11
- data/lib/ruby2d/sprite.rb +471 -180
- data/lib/ruby2d/sprite_sheet.rb +64 -0
- data/lib/ruby2d/square.rb +29 -17
- data/lib/ruby2d/text.rb +196 -65
- data/lib/ruby2d/tileset.rb +139 -81
- data/lib/ruby2d/triangle.rb +324 -89
- data/lib/ruby2d/version.rb +1 -3
- data/lib/ruby2d/vertices.rb +62 -85
- data/lib/ruby2d/warnings.rb +29 -0
- data/lib/ruby2d/window/class_methods.rb +218 -0
- data/lib/ruby2d/window/gamepad_events.rb +141 -0
- data/lib/ruby2d/window/key_events.rb +74 -0
- data/lib/ruby2d/window/mouse_events.rb +171 -0
- data/lib/ruby2d/window/object_events.rb +168 -0
- data/lib/ruby2d/window.rb +632 -585
- data/lib/ruby2d.rb +14 -19
- metadata +275 -428
- data/assets/app.icns +0 -0
- data/assets/include/GLES2/gl2.h +0 -656
- data/assets/include/GLES2/gl2ext.h +0 -3949
- data/assets/include/GLES2/gl2ext_angle.h +0 -701
- data/assets/include/GLES2/gl2platform.h +0 -27
- data/assets/include/GLES3/gl3.h +0 -1192
- data/assets/include/GLES3/gl31.h +0 -1507
- data/assets/include/GLES3/gl32.h +0 -1808
- data/assets/include/GLES3/gl3platform.h +0 -27
- data/assets/include/KHR/khrplatform.h +0 -290
- data/assets/include/SDL2/SDL.h +0 -233
- data/assets/include/SDL2/SDL_assert.h +0 -326
- data/assets/include/SDL2/SDL_atomic.h +0 -415
- data/assets/include/SDL2/SDL_audio.h +0 -1500
- data/assets/include/SDL2/SDL_clipboard.h +0 -141
- data/assets/include/SDL2/SDL_config.h +0 -61
- data/assets/include/SDL2/SDL_config_android.h +0 -194
- data/assets/include/SDL2/SDL_config_emscripten.h +0 -218
- data/assets/include/SDL2/SDL_config_iphoneos.h +0 -217
- data/assets/include/SDL2/SDL_config_macosx.h +0 -277
- data/assets/include/SDL2/SDL_config_minimal.h +0 -95
- data/assets/include/SDL2/SDL_config_ngage.h +0 -89
- data/assets/include/SDL2/SDL_config_os2.h +0 -207
- data/assets/include/SDL2/SDL_config_pandora.h +0 -141
- data/assets/include/SDL2/SDL_config_windows.h +0 -331
- data/assets/include/SDL2/SDL_config_wingdk.h +0 -253
- data/assets/include/SDL2/SDL_config_winrt.h +0 -220
- data/assets/include/SDL2/SDL_config_xbox.h +0 -235
- data/assets/include/SDL2/SDL_cpuinfo.h +0 -594
- data/assets/include/SDL2/SDL_endian.h +0 -348
- data/assets/include/SDL2/SDL_error.h +0 -163
- data/assets/include/SDL2/SDL_events.h +0 -1166
- data/assets/include/SDL2/SDL_filesystem.h +0 -149
- data/assets/include/SDL2/SDL_gamecontroller.h +0 -1074
- data/assets/include/SDL2/SDL_gesture.h +0 -117
- data/assets/include/SDL2/SDL_guid.h +0 -100
- data/assets/include/SDL2/SDL_haptic.h +0 -1341
- data/assets/include/SDL2/SDL_hidapi.h +0 -451
- data/assets/include/SDL2/SDL_hints.h +0 -2569
- data/assets/include/SDL2/SDL_image.h +0 -2173
- data/assets/include/SDL2/SDL_joystick.h +0 -1066
- data/assets/include/SDL2/SDL_keyboard.h +0 -353
- data/assets/include/SDL2/SDL_keycode.h +0 -358
- data/assets/include/SDL2/SDL_loadso.h +0 -115
- data/assets/include/SDL2/SDL_log.h +0 -404
- data/assets/include/SDL2/SDL_main.h +0 -275
- data/assets/include/SDL2/SDL_mixer.h +0 -2784
- data/assets/include/SDL2/SDL_mouse.h +0 -465
- data/assets/include/SDL2/SDL_mutex.h +0 -471
- data/assets/include/SDL2/SDL_name.h +0 -33
- data/assets/include/SDL2/SDL_pixels.h +0 -644
- data/assets/include/SDL2/SDL_platform.h +0 -261
- data/assets/include/SDL2/SDL_quit.h +0 -58
- data/assets/include/SDL2/SDL_rect.h +0 -376
- data/assets/include/SDL2/SDL_render.h +0 -1919
- data/assets/include/SDL2/SDL_revision.h +0 -6
- data/assets/include/SDL2/SDL_rwops.h +0 -841
- data/assets/include/SDL2/SDL_sensor.h +0 -322
- data/assets/include/SDL2/SDL_shape.h +0 -155
- data/assets/include/SDL2/SDL_stdinc.h +0 -830
- data/assets/include/SDL2/SDL_surface.h +0 -997
- data/assets/include/SDL2/SDL_system.h +0 -623
- data/assets/include/SDL2/SDL_syswm.h +0 -386
- data/assets/include/SDL2/SDL_test_images.h +0 -78
- data/assets/include/SDL2/SDL_test_random.h +0 -115
- data/assets/include/SDL2/SDL_thread.h +0 -464
- data/assets/include/SDL2/SDL_timer.h +0 -222
- data/assets/include/SDL2/SDL_touch.h +0 -150
- data/assets/include/SDL2/SDL_ttf.h +0 -2316
- data/assets/include/SDL2/SDL_types.h +0 -29
- data/assets/include/SDL2/SDL_version.h +0 -204
- data/assets/include/SDL2/SDL_video.h +0 -2150
- data/assets/include/SDL2/SDL_vulkan.h +0 -215
- data/assets/include/SDL2/begin_code.h +0 -187
- data/assets/include/mruby/boxing_no.h +0 -58
- data/assets/include/mruby/class.h +0 -108
- data/assets/include/mruby/error.h +0 -147
- data/assets/include/mruby/gc.h +0 -94
- data/assets/include/mruby/irep.h +0 -147
- data/assets/include/mruby/khash.h +0 -284
- data/assets/include/mruby/object.h +0 -43
- data/assets/include/mruby/presym/disable.h +0 -70
- data/assets/include/mruby/presym/enable.h +0 -37
- data/assets/include/mruby/presym.h +0 -40
- data/assets/include/mruby/proc.h +0 -211
- data/assets/include/mruby/throw.h +0 -66
- data/assets/macos/universal/bin/mrbc +0 -0
- data/assets/macos/universal/lib/libFLAC.a +0 -0
- data/assets/macos/universal/lib/libSDL2.a +0 -0
- data/assets/macos/universal/lib/libSDL2_image.a +0 -0
- data/assets/macos/universal/lib/libSDL2_mixer.a +0 -0
- data/assets/macos/universal/lib/libSDL2_ttf.a +0 -0
- data/assets/macos/universal/lib/libavif.a +0 -0
- data/assets/macos/universal/lib/libbrotlicommon-static.a +0 -0
- data/assets/macos/universal/lib/libbrotlidec-static.a +0 -0
- data/assets/macos/universal/lib/libfreetype.a +0 -0
- data/assets/macos/universal/lib/libgraphite2.a +0 -0
- data/assets/macos/universal/lib/libharfbuzz.a +0 -0
- data/assets/macos/universal/lib/libhwy.a +0 -0
- data/assets/macos/universal/lib/libjpeg.a +0 -0
- data/assets/macos/universal/lib/libjxl.a +0 -0
- data/assets/macos/universal/lib/libmodplug.a +0 -0
- data/assets/macos/universal/lib/libmpg123.a +0 -0
- data/assets/macos/universal/lib/libmruby.a +0 -0
- data/assets/macos/universal/lib/libogg.a +0 -0
- data/assets/macos/universal/lib/libpng.a +0 -0
- data/assets/macos/universal/lib/libtiff.a +0 -0
- data/assets/macos/universal/lib/libvorbis.a +0 -0
- data/assets/macos/universal/lib/libvorbisfile.a +0 -0
- data/assets/macos/universal/lib/libwebp.a +0 -0
- data/assets/macos/universal/lib/libzstd.a +0 -0
- data/assets/test_media/README.md +0 -3
- data/assets/test_media/bitstream_vera/COPYRIGHT.txt +0 -124
- data/assets/test_media/bitstream_vera/vera.ttf +0 -0
- data/assets/test_media/boom.png +0 -0
- data/assets/test_media/coin.png +0 -0
- data/assets/test_media/colors.png +0 -0
- data/assets/test_media/controller.png +0 -0
- data/assets/test_media/dial.wav +0 -0
- data/assets/test_media/hero.png +0 -0
- data/assets/test_media/image.bmp +0 -0
- data/assets/test_media/image.jpg +0 -0
- data/assets/test_media/image.png +0 -0
- data/assets/test_media/music.flac +0 -0
- data/assets/test_media/music.mp3 +0 -0
- data/assets/test_media/music.ogg +0 -0
- data/assets/test_media/music.wav +0 -0
- data/assets/test_media/originals/boom.pxm +0 -0
- data/assets/test_media/originals/coin.pxm +0 -0
- data/assets/test_media/originals/controller.sketch +0 -0
- data/assets/test_media/originals/hero.pxm +0 -0
- data/assets/test_media/originals/image.pxm +0 -0
- data/assets/test_media/originals/music.caf +0 -0
- data/assets/test_media/originals/texture_atlas.pxm +0 -0
- data/assets/test_media/rondo_alla_turka.ogg +0 -0
- data/assets/test_media/sound.flac +0 -0
- data/assets/test_media/sound.mp3 +0 -0
- data/assets/test_media/sound.ogg +0 -0
- data/assets/test_media/sound.wav +0 -0
- data/assets/test_media/sprite_sheet.png +0 -0
- data/assets/test_media/texture_atlas.png +0 -0
- data/assets/wasm/libmruby.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/bin/mrbc.exe +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libLerc.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_image.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libSDL2_ttf.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlicommon.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbrotlidec.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libbz2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libdeflate.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libfreetype.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libglew32.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libgraphite2.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libharfbuzz.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libhwy.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjbig.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjpeg.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libjxl.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/liblzma.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libmruby.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libpng.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libssp.a +0 -1
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libtiff.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libwebp.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libz.a +0 -0
- data/assets/windows/mingw-w64-ucrt-x86_64/lib/libzstd.a +0 -0
- data/assets/windows/mingw-w64-x86_64/bin/mrbc.exe +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libFLAC.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libLerc.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_image.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_mixer.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libSDL2_ttf.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbrotlicommon.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbrotlidec.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libbz2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libdeflate.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libfreetype.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libglew32.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libgraphite2.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libharfbuzz.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libhwy.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjbig.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjpeg.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libjxl.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/liblzma.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmodplug.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmpg123.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libmruby.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libogg.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopus.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libopusfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libpng.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libsndfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libssp.a +0 -1
- data/assets/windows/mingw-w64-x86_64/lib/libstdc++.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libtiff.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbis.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libvorbisfile.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libwebp.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libz.a +0 -0
- data/assets/windows/mingw-w64-x86_64/lib/libzstd.a +0 -0
- data/assets/xcode/ios/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
- data/assets/xcode/ios/Assets.xcassets/Contents.json +0 -6
- data/assets/xcode/ios/Base.lproj/LaunchScreen.storyboard +0 -27
- data/assets/xcode/ios/Info.plist +0 -43
- data/assets/xcode/ios/MRuby.framework/Headers/mrbconf.h +0 -143
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/array.h +0 -280
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/class.h +0 -97
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/common.h +0 -77
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/compile.h +0 -195
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/data.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/debug.h +0 -67
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/dump.h +0 -196
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/error.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/gc.h +0 -91
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/hash.h +0 -215
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/irep.h +0 -75
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/istruct.h +0 -47
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/khash.h +0 -274
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/numeric.h +0 -161
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/object.h +0 -44
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/opcode.h +0 -69
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/ops.h +0 -117
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/proc.h +0 -131
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/range.h +0 -49
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/re.h +0 -16
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/string.h +0 -444
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/throw.h +0 -55
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/value.h +0 -309
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/variable.h +0 -136
- data/assets/xcode/ios/MRuby.framework/Headers/mruby/version.h +0 -110
- data/assets/xcode/ios/MRuby.framework/Headers/mruby.h +0 -1282
- data/assets/xcode/ios/MRuby.framework/Info.plist +0 -20
- data/assets/xcode/ios/MRuby.framework/MRuby +0 -0
- data/assets/xcode/ios/MyApp.xcodeproj/project.pbxproj +0 -368
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- data/assets/xcode/ios/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
- data/assets/xcode/ios/main.c +0 -1
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json +0 -17
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json +0 -17
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json +0 -32
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json +0 -16
- data/assets/xcode/tvos/Assets.xcassets/Contents.json +0 -6
- data/assets/xcode/tvos/Assets.xcassets/LaunchImage.launchimage/Contents.json +0 -22
- data/assets/xcode/tvos/Info.plist +0 -30
- data/assets/xcode/tvos/MRuby.framework/Headers/mrbconf.h +0 -143
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/array.h +0 -280
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_nan.h +0 -102
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_no.h +0 -56
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/boxing_word.h +0 -144
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/class.h +0 -97
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/common.h +0 -77
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/compile.h +0 -195
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/data.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/debug.h +0 -67
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/dump.h +0 -196
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/error.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/gc.h +0 -91
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/hash.h +0 -215
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/irep.h +0 -75
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/istruct.h +0 -47
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/khash.h +0 -274
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/numeric.h +0 -161
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/object.h +0 -44
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/opcode.h +0 -69
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/ops.h +0 -117
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/proc.h +0 -131
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/range.h +0 -49
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/re.h +0 -16
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/string.h +0 -444
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/throw.h +0 -55
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/value.h +0 -309
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/variable.h +0 -136
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby/version.h +0 -110
- data/assets/xcode/tvos/MRuby.framework/Headers/mruby.h +0 -1282
- data/assets/xcode/tvos/MRuby.framework/Info.plist +0 -20
- data/assets/xcode/tvos/MRuby.framework/MRuby +0 -0
- data/assets/xcode/tvos/MyApp.xcodeproj/project.pbxproj +0 -350
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- data/assets/xcode/tvos/MyApp.xcodeproj/project.xcworkspace/xcshareddata/MyApp.xcscmblueprint +0 -30
- data/assets/xcode/tvos/main.c +0 -1
- data/ext/ruby2d/common.c +0 -185
- data/ext/ruby2d/controllers.c +0 -110
- data/ext/ruby2d/gl.c +0 -398
- data/ext/ruby2d/gl2.c +0 -86
- data/ext/ruby2d/gl3.c +0 -304
- data/ext/ruby2d/gles.c +0 -302
- data/ext/ruby2d/input.c +0 -48
- data/ext/ruby2d/music.c +0 -127
- data/ext/ruby2d/sound.c +0 -132
- data/lib/ruby2d/cli/console.rb +0 -65
- data/lib/ruby2d/cli/enable_console.rb +0 -7
- data/lib/ruby2d/cli/platform.rb +0 -17
- data/lib/ruby2d/music.rb +0 -77
- data/lib/ruby2d/pixel.rb +0 -18
- data/lib/ruby2d/pixmap.rb +0 -39
- data/lib/ruby2d/pixmap_atlas.rb +0 -56
- data/lib/ruby2d/sound.rb +0 -61
- data/lib/ruby2d/texture.rb +0 -38
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2ext.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_gl2platform.h +0 -0
- /data/assets/{include/SDL2 → platform/include/SDL3}/SDL_opengles2_khrplatform.h +0 -0
- /data/assets/{include → platform/include}/mruby/re.h +0 -0
data/ext/ruby2d/window.c
CHANGED
|
@@ -1,385 +1,1099 @@
|
|
|
1
1
|
// window.c
|
|
2
2
|
|
|
3
3
|
#include "ruby2d.h"
|
|
4
|
+
#include <math.h> // isinf, for the Float::INFINITY (uncapped) fps_cap path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Global Variables
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
R2D_DEFINE_DATA_TYPE(R2D_Window);
|
|
12
|
+
|
|
13
|
+
// The single active window instance. Other files access it via R2D_Get*() functions.
|
|
14
|
+
static R2D_Window *r2d_window = NULL;
|
|
15
|
+
|
|
16
|
+
// Ruby VALUE for the Window object — used by the mruby tick trampoline.
|
|
17
|
+
R_VAL ruby2d_window;
|
|
18
|
+
|
|
19
|
+
R2D_Window *R2D_GetWindow(void) { return r2d_window; }
|
|
20
|
+
SDL_Renderer *R2D_GetRenderer(void) { return r2d_window ? r2d_window->sdl_renderer : NULL; }
|
|
21
|
+
float R2D_GetAssetScale(void) { return r2d_window ? r2d_window->asset_scale : 1.0f; }
|
|
22
|
+
float R2D_GetDisplayScale(void) { return r2d_window ? r2d_window->display_scale : 1.0f; }
|
|
23
|
+
void R2D_SetWindow(R2D_Window *w) { r2d_window = w; }
|
|
24
|
+
|
|
25
|
+
// Window-specific ivar IDs. Shared id_width / id_height live in ext.c.
|
|
26
|
+
static R_ID id_title, id_icon, id_ext_window;
|
|
27
|
+
static R_ID id_resizable, id_highdpi, id_pixel_scale;
|
|
28
|
+
static R_ID id_viewport_mode;
|
|
29
|
+
static R_ID id_fps_cap, id_render_mode;
|
|
30
|
+
static R_ID id_background, id_mouse_x, id_mouse_y;
|
|
31
|
+
static R_ID id_frames, id_fps, id_close;
|
|
4
32
|
|
|
5
33
|
|
|
6
34
|
/*
|
|
7
|
-
*
|
|
35
|
+
* Initialize
|
|
8
36
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
37
|
+
void R2D_Window_Init() {
|
|
38
|
+
id_title = r_id("@title");
|
|
39
|
+
id_icon = r_id("@icon");
|
|
40
|
+
id_ext_window = r_id("@ext_window");
|
|
41
|
+
id_resizable = r_id("@resizable");
|
|
42
|
+
id_highdpi = r_id("@highdpi");
|
|
43
|
+
id_pixel_scale = r_id("@pixel_scale");
|
|
44
|
+
id_viewport_mode = r_id("@viewport_mode");
|
|
45
|
+
id_fps_cap = r_id("@fps_cap");
|
|
46
|
+
id_render_mode = r_id("@render_mode");
|
|
47
|
+
id_background = r_id("@background");
|
|
48
|
+
id_mouse_x = r_id("@mouse_x");
|
|
49
|
+
id_mouse_y = r_id("@mouse_y");
|
|
50
|
+
id_frames = r_id("@frames");
|
|
51
|
+
id_fps = r_id("@fps");
|
|
52
|
+
id_close = r_id("@close");
|
|
53
|
+
|
|
54
|
+
r_define_class_method(ruby2d_ext_module, "window_create", ruby2d_ext_window_create, r_args_variadic);
|
|
55
|
+
r_define_class_method(ruby2d_ext_module, "window_show", ruby2d_ext_window_show, r_args_variadic);
|
|
56
|
+
r_define_class_method(ruby2d_ext_module, "poll_events", ruby2d_ext_window_poll_events, r_args_variadic);
|
|
57
|
+
r_define_class_method(ruby2d_ext_module, "drain_events", ruby2d_ext_window_drain_events, r_args_variadic);
|
|
58
|
+
r_define_class_method(ruby2d_ext_module, "scancode_name", ruby2d_ext_window_scancode_name, r_args_variadic);
|
|
59
|
+
r_define_class_method(ruby2d_ext_module, "begin_frame", ruby2d_ext_window_begin_frame, r_args_variadic);
|
|
60
|
+
r_define_class_method(ruby2d_ext_module, "end_frame", ruby2d_ext_window_end_frame, r_args_variadic);
|
|
61
|
+
r_define_class_method(ruby2d_ext_module, "now", ruby2d_ext_now, r_args_variadic);
|
|
62
|
+
r_define_class_method(ruby2d_ext_module, "window_get_display_dimensions", ruby2d_ext_window_get_display_dimensions, r_args_variadic);
|
|
63
|
+
r_define_class_method(ruby2d_ext_module, "window_set_viewport_mode", ruby2d_ext_window_set_viewport_mode, r_args_variadic);
|
|
64
|
+
r_define_class_method(ruby2d_ext_module, "window_set_icon", ruby2d_ext_window_set_icon, r_args_variadic);
|
|
65
|
+
r_define_class_method(ruby2d_ext_module, "window_set_title", ruby2d_ext_window_set_title, r_args_variadic);
|
|
66
|
+
r_define_class_method(ruby2d_ext_module, "window_set_resizable", ruby2d_ext_window_set_resizable, r_args_variadic);
|
|
67
|
+
r_define_class_method(ruby2d_ext_module, "window_set_size", ruby2d_ext_window_set_size, r_args_variadic);
|
|
68
|
+
r_define_class_method(ruby2d_ext_module, "window_diagnostics", ruby2d_ext_window_diagnostics, r_args_variadic);
|
|
69
|
+
r_define_class_method(ruby2d_ext_module, "window_set_fps_cap", ruby2d_ext_window_set_fps_cap, r_args_variadic);
|
|
70
|
+
r_define_class_method(ruby2d_ext_module, "window_show_fps", ruby2d_ext_window_show_fps, r_args_variadic);
|
|
71
|
+
r_define_class_method(ruby2d_ext_module, "window_load_gamepad_mappings_file", ruby2d_ext_window_load_gamepad_mappings_file, r_args_variadic);
|
|
72
|
+
r_define_class_method(ruby2d_ext_module, "window_add_gamepad_mapping", ruby2d_ext_window_add_gamepad_mapping, r_args_variadic);
|
|
73
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_rumble", ruby2d_ext_window_gamepad_rumble, r_args_variadic);
|
|
74
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_rumble_triggers", ruby2d_ext_window_gamepad_rumble_triggers, r_args_variadic);
|
|
75
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_set_led", ruby2d_ext_window_gamepad_set_led, r_args_variadic);
|
|
76
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_type", ruby2d_ext_window_gamepad_type, r_args_variadic);
|
|
77
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_battery", ruby2d_ext_window_gamepad_battery, r_args_variadic);
|
|
78
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_has_button", ruby2d_ext_window_gamepad_has_button, r_args_variadic);
|
|
79
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_has_axis", ruby2d_ext_window_gamepad_has_axis, r_args_variadic);
|
|
80
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_caps", ruby2d_ext_window_gamepad_caps, r_args_variadic);
|
|
81
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_debug_info", ruby2d_ext_window_gamepad_debug_info, r_args_variadic);
|
|
82
|
+
r_define_class_method(ruby2d_ext_module, "window_gamepad_joystick_state", ruby2d_ext_window_gamepad_joystick_state, r_args_variadic);
|
|
83
|
+
r_define_class_method(ruby2d_ext_module, "window_screenshot", ruby2d_ext_window_screenshot, r_args_variadic);
|
|
84
|
+
r_define_class_method(ruby2d_ext_module, "window_show_cursor", ruby2d_ext_window_show_cursor, r_args_variadic);
|
|
85
|
+
r_define_class_method(ruby2d_ext_module, "window_hide_cursor", ruby2d_ext_window_hide_cursor, r_args_variadic);
|
|
86
|
+
r_define_class_method(ruby2d_ext_module, "window_cursor_visible", ruby2d_ext_window_cursor_visible, r_args_variadic);
|
|
87
|
+
r_define_class_method(ruby2d_ext_module, "window_set_system_cursor", ruby2d_ext_window_set_system_cursor, r_args_variadic);
|
|
88
|
+
r_define_class_method(ruby2d_ext_module, "window_close", ruby2d_ext_window_close, r_args_variadic);
|
|
89
|
+
r_define_class_method(ruby2d_ext_module, "window_set_render_mode", ruby2d_ext_window_set_render_mode, r_args_variadic);
|
|
90
|
+
r_define_class_method(ruby2d_ext_module, "window_request_render", ruby2d_ext_window_request_render, r_args_variadic);
|
|
91
|
+
}
|
|
11
92
|
|
|
12
|
-
R2D_Init();
|
|
13
93
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Viewport Mode
|
|
96
|
+
// =============================================================================
|
|
17
97
|
|
|
18
|
-
|
|
19
|
-
|
|
98
|
+
/*
|
|
99
|
+
* Parse a viewport mode symbol from Ruby into an R2D_VIEWPORT_* constant.
|
|
100
|
+
* Returns R2D_VIEWPORT_LETTERBOX if the value is nil or unrecognized.
|
|
101
|
+
*/
|
|
102
|
+
static int R2D_ParseViewportMode(R_VAL vm_val) {
|
|
103
|
+
if (r_test(vm_val)) {
|
|
104
|
+
R_ID vm_id = r_sym_to_id(vm_val);
|
|
105
|
+
if (vm_id == r_id("letterbox")) return R2D_VIEWPORT_LETTERBOX;
|
|
106
|
+
else if (vm_id == r_id("stretch")) return R2D_VIEWPORT_STRETCH;
|
|
107
|
+
else if (vm_id == r_id("integer")) return R2D_VIEWPORT_INTEGER;
|
|
108
|
+
else if (vm_id == r_id("overscan")) return R2D_VIEWPORT_OVERSCAN;
|
|
109
|
+
else if (vm_id == r_id("expand")) return R2D_VIEWPORT_EXPAND;
|
|
110
|
+
else if (vm_id == r_id("fixed")) return R2D_VIEWPORT_FIXED;
|
|
111
|
+
}
|
|
112
|
+
return R2D_VIEWPORT_LETTERBOX;
|
|
113
|
+
}
|
|
20
114
|
|
|
21
|
-
// Allocate window and set default values
|
|
22
|
-
R2D_Window *window = (R2D_Window *) malloc(sizeof(R2D_Window));
|
|
23
|
-
window->sdl = NULL;
|
|
24
|
-
window->glcontext = NULL;
|
|
25
|
-
window->title = title;
|
|
26
|
-
window->width = width;
|
|
27
|
-
window->height = height;
|
|
28
|
-
window->orig_width = width;
|
|
29
|
-
window->orig_height = height;
|
|
30
|
-
window->viewport.width = width;
|
|
31
|
-
window->viewport.height = height;
|
|
32
|
-
window->viewport.mode = R2D_SCALE;
|
|
33
|
-
window->update = update;
|
|
34
|
-
window->render = render;
|
|
35
|
-
window->flags = flags;
|
|
36
|
-
window->on_key = NULL;
|
|
37
|
-
window->on_mouse = NULL;
|
|
38
|
-
window->on_controller = NULL;
|
|
39
|
-
window->vsync = true;
|
|
40
|
-
window->fps_cap = 60;
|
|
41
|
-
window->background.r = 0.0;
|
|
42
|
-
window->background.g = 0.0;
|
|
43
|
-
window->background.b = 0.0;
|
|
44
|
-
window->background.a = 1.0;
|
|
45
|
-
window->icon = NULL;
|
|
46
|
-
window->close = true;
|
|
47
115
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
116
|
+
/*
|
|
117
|
+
* Compute and apply the fixed-mode viewport rect. Centers the viewport at 1:1
|
|
118
|
+
* scale within the window, with no content scaling. The rect is in logical
|
|
119
|
+
* (point) coordinates because SDL_SetRenderViewport interprets its rect in the
|
|
120
|
+
* current scaled coordinate space.
|
|
121
|
+
*/
|
|
122
|
+
static void R2D_ApplyFixedViewport(R2D_Window *window) {
|
|
123
|
+
float ds = (window->pixel_scale && (window->flags & R2D_HIGHDPI))
|
|
124
|
+
? 1.0f : window->display_scale;
|
|
125
|
+
SDL_SetRenderScale(window->sdl_renderer, ds, ds);
|
|
51
126
|
|
|
127
|
+
int vp_w = window->viewport.width;
|
|
128
|
+
int vp_h = window->viewport.height;
|
|
52
129
|
|
|
53
|
-
|
|
130
|
+
window->viewport.fixed_rect.x = (window->width - vp_w) / 2;
|
|
131
|
+
window->viewport.fixed_rect.y = (window->height - vp_h) / 2;
|
|
132
|
+
window->viewport.fixed_rect.w = vp_w;
|
|
133
|
+
window->viewport.fixed_rect.h = vp_h;
|
|
54
134
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
Uint32 start_ms;
|
|
58
|
-
Uint32 next_second_ms;
|
|
59
|
-
Uint32 begin_ms;
|
|
60
|
-
Uint32 end_ms; // Time at end of loop
|
|
61
|
-
Uint32 elapsed_ms; // Total elapsed time
|
|
62
|
-
Uint32 loop_ms; // Elapsed time of current loop
|
|
63
|
-
int delay_ms; // Amount of delay to achieve desired frame rate
|
|
64
|
-
double decay_rate;
|
|
65
|
-
double fps;
|
|
135
|
+
SDL_SetRenderViewport(window->sdl_renderer, &window->viewport.fixed_rect);
|
|
136
|
+
}
|
|
66
137
|
|
|
67
|
-
R2D_Window *window;
|
|
68
138
|
|
|
139
|
+
/*
|
|
140
|
+
* Apply viewport scaling mode via SDL3 logical presentation.
|
|
141
|
+
* Maps the R2D_VIEWPORT_* constants to SDL_RendererLogicalPresentation values
|
|
142
|
+
* and configures the renderer accordingly. Fixed mode bypasses logical
|
|
143
|
+
* presentation entirely and uses a manually centered viewport at 1:1 scale.
|
|
144
|
+
*/
|
|
145
|
+
void R2D_ApplyViewportMode(R2D_Window *window) {
|
|
69
146
|
|
|
147
|
+
// Fixed mode: disable logical presentation, manage viewport manually
|
|
148
|
+
if (window->viewport.mode == R2D_VIEWPORT_FIXED) {
|
|
149
|
+
SDL_SetRenderLogicalPresentation(
|
|
150
|
+
window->sdl_renderer, 0, 0, SDL_LOGICAL_PRESENTATION_DISABLED
|
|
151
|
+
);
|
|
152
|
+
R2D_ApplyFixedViewport(window);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
70
155
|
|
|
156
|
+
// All other modes use SDL logical presentation — reset any manual viewport
|
|
157
|
+
// and scale that fixed mode may have set.
|
|
158
|
+
SDL_SetRenderViewport(window->sdl_renderer, NULL);
|
|
159
|
+
SDL_SetRenderScale(window->sdl_renderer, 1.0f, 1.0f);
|
|
160
|
+
|
|
161
|
+
SDL_RendererLogicalPresentation sdl_mode;
|
|
162
|
+
|
|
163
|
+
switch (window->viewport.mode) {
|
|
164
|
+
case R2D_VIEWPORT_STRETCH:
|
|
165
|
+
sdl_mode = SDL_LOGICAL_PRESENTATION_STRETCH;
|
|
166
|
+
break;
|
|
167
|
+
case R2D_VIEWPORT_INTEGER:
|
|
168
|
+
sdl_mode = SDL_LOGICAL_PRESENTATION_INTEGER_SCALE;
|
|
169
|
+
break;
|
|
170
|
+
case R2D_VIEWPORT_OVERSCAN:
|
|
171
|
+
sdl_mode = SDL_LOGICAL_PRESENTATION_OVERSCAN;
|
|
172
|
+
break;
|
|
173
|
+
case R2D_VIEWPORT_EXPAND:
|
|
174
|
+
case R2D_VIEWPORT_LETTERBOX:
|
|
175
|
+
default:
|
|
176
|
+
sdl_mode = SDL_LOGICAL_PRESENTATION_LETTERBOX;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
71
179
|
|
|
180
|
+
if (!SDL_SetRenderLogicalPresentation(
|
|
181
|
+
window->sdl_renderer,
|
|
182
|
+
window->viewport.width,
|
|
183
|
+
window->viewport.height,
|
|
184
|
+
sdl_mode
|
|
185
|
+
)) {
|
|
186
|
+
R2D_Error("R2D_ApplyViewportMode", "SDL_SetRenderLogicalPresentation failed: %s", SDL_GetError());
|
|
187
|
+
}
|
|
188
|
+
}
|
|
72
189
|
|
|
73
|
-
void main_loop() {
|
|
74
190
|
|
|
75
|
-
|
|
191
|
+
/*
|
|
192
|
+
* Derive the render (physical) dimensions from the logical, user-intended
|
|
193
|
+
* sizes: window `orig_width`/`orig_height` and `viewport.base_width`/`base_height`.
|
|
194
|
+
* When pixel_scale is active on a HiDPI window, scale by display_scale once so
|
|
195
|
+
* coordinates map 1:1 with physical pixels (and asset_scale drops to 1.0, since
|
|
196
|
+
* assets are already authored in physical pixels); otherwise assets carry the
|
|
197
|
+
* display scale. Idempotent by construction — it always recomputes from the
|
|
198
|
+
* logical sizes, never from the current (possibly already-scaled) values — so it
|
|
199
|
+
* can be re-applied on every set()/resize without compounding. Expects
|
|
200
|
+
* `viewport.mode` and `pixel_scale` to be current.
|
|
201
|
+
*/
|
|
202
|
+
static void R2D_ApplyPixelScale(R2D_Window *window) {
|
|
203
|
+
bool ps_active = window->pixel_scale && (window->flags & R2D_HIGHDPI);
|
|
204
|
+
float ds = ps_active ? window->display_scale : 1.0f;
|
|
205
|
+
|
|
206
|
+
window->width = (int)(window->orig_width * ds);
|
|
207
|
+
window->height = (int)(window->orig_height * ds);
|
|
208
|
+
|
|
209
|
+
// In expand mode the viewport always matches the window; other modes keep a
|
|
210
|
+
// fixed logical canvas (base_*) that scales with pixel_scale.
|
|
211
|
+
if (window->viewport.mode == R2D_VIEWPORT_EXPAND) {
|
|
212
|
+
window->viewport.width = window->width;
|
|
213
|
+
window->viewport.height = window->height;
|
|
214
|
+
} else {
|
|
215
|
+
window->viewport.width = (int)(window->viewport.base_width * ds);
|
|
216
|
+
window->viewport.height = (int)(window->viewport.base_height * ds);
|
|
217
|
+
}
|
|
76
218
|
|
|
77
|
-
|
|
219
|
+
window->asset_scale = ps_active ? 1.0f : window->display_scale;
|
|
220
|
+
}
|
|
78
221
|
|
|
79
|
-
// Set FPS /////////////////////////////////////////////////////////////////
|
|
80
222
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
elapsed_ms = end_ms - start_ms;
|
|
223
|
+
// =============================================================================
|
|
224
|
+
// Render Mode
|
|
225
|
+
// =============================================================================
|
|
85
226
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
227
|
+
/*
|
|
228
|
+
* Parse a render mode symbol from Ruby into an R2D_RENDER_* constant.
|
|
229
|
+
* Returns R2D_RENDER_CONTINUOUS if the value is nil or unrecognized.
|
|
230
|
+
*/
|
|
231
|
+
static int R2D_ParseRenderMode(R_VAL rm_val) {
|
|
232
|
+
if (r_test(rm_val)) {
|
|
233
|
+
R_ID rm_id = r_sym_to_id(rm_val);
|
|
234
|
+
if (rm_id == r_id("on_demand")) return R2D_RENDER_ON_DEMAND;
|
|
91
235
|
}
|
|
236
|
+
return R2D_RENDER_CONTINUOUS;
|
|
237
|
+
}
|
|
92
238
|
|
|
93
|
-
loop_ms = end_ms - begin_ms;
|
|
94
|
-
delay_ms = (1000 / window->fps_cap) - loop_ms;
|
|
95
239
|
|
|
96
|
-
|
|
240
|
+
// =============================================================================
|
|
241
|
+
// Window Creation and Setup
|
|
242
|
+
// =============================================================================
|
|
97
243
|
|
|
98
|
-
|
|
244
|
+
/*
|
|
245
|
+
* Ruby2D::Window#ext_create
|
|
246
|
+
*/
|
|
247
|
+
R_VAL ruby2d_ext_window_create(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
248
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
249
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_create expects 1 arg (window), got %d", (int)argc);
|
|
250
|
+
R_VAL obj = argv[0];
|
|
251
|
+
|
|
252
|
+
if (!R2D_Init()) r_raise("Ruby2D: failed to initialize: %s", SDL_GetError());
|
|
253
|
+
|
|
254
|
+
int width = obj_int(obj, id_width);
|
|
255
|
+
int height = obj_int(obj, id_height);
|
|
256
|
+
|
|
257
|
+
// Use display dimensions from init-time query if requested
|
|
258
|
+
if (width == R2D_DISPLAY_WIDTH) width = R2D_GetInitDisplayWidth();
|
|
259
|
+
if (height == R2D_DISPLAY_HEIGHT) height = R2D_GetInitDisplayHeight();
|
|
260
|
+
|
|
261
|
+
// Allocate window and set default values. Use calloc so every field starts
|
|
262
|
+
// zeroed — defends against reads of any field not explicitly set below (e.g.
|
|
263
|
+
// viewport.fixed_rect) and against future field additions.
|
|
264
|
+
R2D_Window *window = (R2D_Window *) calloc(1, sizeof(R2D_Window));
|
|
265
|
+
if (!window) r_raise("Ruby2D: failed to allocate window");
|
|
266
|
+
// Set the title from the Ruby instance variable so the native window
|
|
267
|
+
// gets the requested title.
|
|
268
|
+
window->title = obj_str(obj, id_title);
|
|
269
|
+
// Determine initial window flags from Ruby instance variables (resizable,
|
|
270
|
+
// highdpi) and store them on the native window.
|
|
271
|
+
int init_flags = 0;
|
|
272
|
+
if (r_test(r_ivar_get(obj, id_resizable))) init_flags |= R2D_RESIZABLE;
|
|
273
|
+
if (r_test(r_ivar_get(obj, id_highdpi))) init_flags |= R2D_HIGHDPI;
|
|
274
|
+
window->flags = init_flags;
|
|
99
275
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
276
|
+
window->width = width;
|
|
277
|
+
window->height = height;
|
|
278
|
+
window->orig_width = width;
|
|
279
|
+
window->orig_height = height;
|
|
280
|
+
// Read viewport dimensions from Ruby (fall back to width/height)
|
|
281
|
+
R_VAL vw_val = r_ivar_get(obj, id_viewport_width);
|
|
282
|
+
R_VAL vh_val = r_ivar_get(obj, id_viewport_height);
|
|
283
|
+
window->viewport.width = r_test(vw_val) ? NUM2INT(vw_val) : width;
|
|
284
|
+
window->viewport.height = r_test(vh_val) ? NUM2INT(vh_val) : height;
|
|
285
|
+
window->viewport.base_width = window->viewport.width;
|
|
286
|
+
window->viewport.base_height = window->viewport.height;
|
|
287
|
+
|
|
288
|
+
// Parse viewport mode symbol from Ruby
|
|
289
|
+
window->viewport.mode = R2D_ParseViewportMode(r_ivar_get(obj, id_viewport_mode));
|
|
290
|
+
|
|
291
|
+
window->fps_cap = (int)R2D_GetInitRefreshRate();
|
|
292
|
+
window->background.r = 0.0;
|
|
293
|
+
window->background.g = 0.0;
|
|
294
|
+
window->background.b = 0.0;
|
|
295
|
+
window->background.a = 1.0;
|
|
296
|
+
window->icon = NULL;
|
|
297
|
+
window->close = true;
|
|
298
|
+
window->pixel_scale = obj_bool(obj, id_pixel_scale);
|
|
299
|
+
window->render_mode = R2D_ParseRenderMode(r_ivar_get(obj, id_render_mode));
|
|
300
|
+
|
|
301
|
+
// Start with render_pending set so the first tick always presents a frame,
|
|
302
|
+
// even in on-demand mode before the app calls request_render.
|
|
303
|
+
SDL_SetAtomicInt(&window->render_pending, 1);
|
|
304
|
+
|
|
305
|
+
// Copy display info from pre-window statics (populated by R2D_Init)
|
|
306
|
+
int dw = R2D_GetInitDisplayWidth();
|
|
307
|
+
int dh = R2D_GetInitDisplayHeight();
|
|
308
|
+
window->display_width = dw ? dw : width;
|
|
309
|
+
window->display_height = dh ? dh : height;
|
|
310
|
+
window->display_content_scale = R2D_GetInitContentScale();
|
|
311
|
+
window->display_scale = R2D_GetInitDisplayScale();
|
|
312
|
+
window->display_refresh_rate = R2D_GetInitRefreshRate();
|
|
313
|
+
window->asset_scale = R2D_GetInitDisplayScale();
|
|
314
|
+
window->display_pixel_width = R2D_GetInitDisplayPixelWidth();
|
|
315
|
+
window->display_pixel_height = R2D_GetInitDisplayPixelHeight();
|
|
316
|
+
window->diagnostics = false;
|
|
317
|
+
window->show_fps = false;
|
|
318
|
+
window->screenshot_path = NULL;
|
|
319
|
+
|
|
320
|
+
// SDL window and renderer are created later, in R2D_Show.
|
|
321
|
+
r2d_window = window;
|
|
322
|
+
|
|
323
|
+
obj_set_struct(obj, id_ext_window, R2D_Window, window);
|
|
324
|
+
|
|
325
|
+
return R_TRUE;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
// =============================================================================
|
|
330
|
+
// Per-Frame Ext Bindings
|
|
331
|
+
// =============================================================================
|
|
332
|
+
//
|
|
333
|
+
// Ruby owns frame sequencing via its `tick` method, which calls these Ext
|
|
334
|
+
// primitives each frame: poll_events → drain_events → begin_frame → end_frame.
|
|
335
|
+
// Events are queued into a C-side buffer and returned to Ruby as a flat array.
|
|
336
|
+
|
|
337
|
+
// Frame timing statics
|
|
338
|
+
Uint64 frames;
|
|
339
|
+
Uint64 perf_freq;
|
|
340
|
+
double target_frame;
|
|
341
|
+
static Uint64 frame_start;
|
|
342
|
+
static bool current_frame_rendered;
|
|
343
|
+
|
|
344
|
+
// Event buffer — grows as needed, reset each frame, never freed.
|
|
345
|
+
static R2D_QueuedEvent *event_buf = NULL;
|
|
346
|
+
static int event_count = 0;
|
|
347
|
+
static int event_capacity = 0;
|
|
348
|
+
|
|
349
|
+
static void event_buf_push(R2D_QueuedEvent ev) {
|
|
350
|
+
if (event_count == event_capacity) {
|
|
351
|
+
int new_capacity = event_capacity ? event_capacity * 2 : 64;
|
|
352
|
+
R2D_QueuedEvent *grown = (R2D_QueuedEvent *)realloc(event_buf,
|
|
353
|
+
new_capacity * sizeof(R2D_QueuedEvent));
|
|
354
|
+
// On failure realloc leaves the old buffer intact; drop this event rather
|
|
355
|
+
// than overwrite event_buf with NULL and crash on the next write.
|
|
356
|
+
if (!grown) return;
|
|
357
|
+
event_buf = grown;
|
|
358
|
+
event_capacity = new_capacity;
|
|
359
|
+
}
|
|
360
|
+
event_buf[event_count++] = ev;
|
|
361
|
+
}
|
|
103
362
|
|
|
104
|
-
|
|
363
|
+
// Initial gamepad scan — runs once on the first poll_events call so that
|
|
364
|
+
// connect events for already-plugged-in pads appear in the first drain.
|
|
365
|
+
static bool first_poll = true;
|
|
105
366
|
|
|
106
|
-
// Handle Input and Window Events //////////////////////////////////////////
|
|
107
367
|
|
|
108
|
-
|
|
368
|
+
/*
|
|
369
|
+
* Ext.poll_events(window)
|
|
370
|
+
*
|
|
371
|
+
* Poll SDL events and held-input state, queue into the event buffer,
|
|
372
|
+
* update timing, and sync C state to Ruby ivars.
|
|
373
|
+
*/
|
|
374
|
+
R_VAL ruby2d_ext_window_poll_events(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
375
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
376
|
+
if (argc != 1) r_raise("Ruby2D::Ext.poll_events expects 1 arg (window), got %d", (int)argc);
|
|
377
|
+
R_VAL obj = argv[0];
|
|
378
|
+
|
|
379
|
+
frame_start = SDL_GetPerformanceCounter();
|
|
380
|
+
event_count = 0;
|
|
381
|
+
|
|
382
|
+
// On the first call, push connect events for already-plugged-in gamepads
|
|
383
|
+
if (first_poll) {
|
|
384
|
+
first_poll = false;
|
|
385
|
+
int num_pads = 0;
|
|
386
|
+
SDL_JoystickID *pads = SDL_GetGamepads(&num_pads);
|
|
387
|
+
if (pads) {
|
|
388
|
+
for (int i = 0; i < num_pads; i++) {
|
|
389
|
+
SDL_JoystickID gid = pads[i];
|
|
390
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID(gid);
|
|
391
|
+
if (!pad) pad = SDL_OpenGamepad(gid);
|
|
392
|
+
if (!pad) continue;
|
|
393
|
+
// Own the name: drain frees it. SDL_GetGamepadName returns a borrowed
|
|
394
|
+
// pointer that SDL_CloseGamepad frees, so copy it now (see drain).
|
|
395
|
+
const char *name = SDL_GetGamepadName(pad);
|
|
396
|
+
event_buf_push((R2D_QueuedEvent){
|
|
397
|
+
.category = R2D_EVT_GAMEPAD, .type = R2D_GAMEPAD_CONNECT,
|
|
398
|
+
.id = gid, .str = name ? SDL_strdup(name) : NULL
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
SDL_free(pads);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Poll SDL events ///////////////////////////////////////////////////////////
|
|
109
406
|
|
|
110
407
|
SDL_Event e;
|
|
111
408
|
while (SDL_PollEvent(&e)) {
|
|
112
|
-
|
|
409
|
+
SDL_ConvertEventToRenderCoordinates(r2d_window->sdl_renderer, &e);
|
|
113
410
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
411
|
+
switch (e.type) {
|
|
412
|
+
case SDL_EVENT_KEY_DOWN:
|
|
413
|
+
if (e.key.repeat == 0) {
|
|
414
|
+
event_buf_push((R2D_QueuedEvent){
|
|
415
|
+
.category = R2D_EVT_KEY, .type = R2D_KEY_DOWN,
|
|
416
|
+
.id = e.key.scancode
|
|
417
|
+
});
|
|
120
418
|
}
|
|
121
419
|
break;
|
|
122
420
|
|
|
123
|
-
case
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
window->on_key(event);
|
|
129
|
-
}
|
|
421
|
+
case SDL_EVENT_KEY_UP:
|
|
422
|
+
event_buf_push((R2D_QueuedEvent){
|
|
423
|
+
.category = R2D_EVT_KEY, .type = R2D_KEY_UP,
|
|
424
|
+
.id = e.key.scancode
|
|
425
|
+
});
|
|
130
426
|
break;
|
|
131
427
|
|
|
132
|
-
case
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
window->on_mouse(event);
|
|
141
|
-
}
|
|
428
|
+
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
429
|
+
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
430
|
+
event_buf_push((R2D_QueuedEvent){
|
|
431
|
+
.category = R2D_EVT_MOUSE,
|
|
432
|
+
.type = e.type == SDL_EVENT_MOUSE_BUTTON_DOWN ? R2D_MOUSE_DOWN : R2D_MOUSE_UP,
|
|
433
|
+
.button = e.button.button,
|
|
434
|
+
.x = (int)e.button.x, .y = (int)e.button.y
|
|
435
|
+
});
|
|
142
436
|
break;
|
|
143
437
|
|
|
144
|
-
case
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
window->on_mouse(event);
|
|
151
|
-
}
|
|
438
|
+
case SDL_EVENT_MOUSE_WHEEL:
|
|
439
|
+
event_buf_push((R2D_QueuedEvent){
|
|
440
|
+
.category = R2D_EVT_MOUSE, .type = R2D_MOUSE_SCROLL,
|
|
441
|
+
.direction = e.wheel.direction,
|
|
442
|
+
.delta_x = e.wheel.x, .delta_y = -e.wheel.y
|
|
443
|
+
});
|
|
152
444
|
break;
|
|
153
445
|
|
|
154
|
-
case
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
window->on_mouse(event);
|
|
162
|
-
}
|
|
446
|
+
case SDL_EVENT_MOUSE_MOTION:
|
|
447
|
+
event_buf_push((R2D_QueuedEvent){
|
|
448
|
+
.category = R2D_EVT_MOUSE, .type = R2D_MOUSE_MOVE,
|
|
449
|
+
.x = (int)e.motion.x, .y = (int)e.motion.y,
|
|
450
|
+
.delta_x = e.motion.xrel, .delta_y = e.motion.yrel
|
|
451
|
+
});
|
|
163
452
|
break;
|
|
164
453
|
|
|
165
|
-
case
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
-
window->on_controller(event);
|
|
172
|
-
}
|
|
454
|
+
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
|
455
|
+
event_buf_push((R2D_QueuedEvent){
|
|
456
|
+
.category = R2D_EVT_GAMEPAD, .type = R2D_GAMEPAD_AXIS,
|
|
457
|
+
.id = e.gaxis.which, .axis = e.gaxis.axis, .value = e.gaxis.value
|
|
458
|
+
});
|
|
173
459
|
break;
|
|
174
460
|
|
|
175
|
-
case
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
461
|
+
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
|
462
|
+
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
|
463
|
+
event_buf_push((R2D_QueuedEvent){
|
|
464
|
+
.category = R2D_EVT_GAMEPAD,
|
|
465
|
+
.type = e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN
|
|
466
|
+
? R2D_GAMEPAD_BUTTON_DOWN : R2D_GAMEPAD_BUTTON_UP,
|
|
467
|
+
.id = e.gbutton.which, .button = e.gbutton.button
|
|
468
|
+
});
|
|
183
469
|
break;
|
|
184
470
|
|
|
185
|
-
case
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
471
|
+
case SDL_EVENT_GAMEPAD_ADDED: {
|
|
472
|
+
SDL_JoystickID gid = e.gdevice.which;
|
|
473
|
+
SDL_Gamepad *pad = SDL_OpenGamepad(gid);
|
|
474
|
+
if (!pad) {
|
|
475
|
+
R2D_Error("SDL_OpenGamepad", "SDL_OpenGamepad(%u) failed: %s", (unsigned)gid, SDL_GetError());
|
|
476
|
+
} else {
|
|
477
|
+
// Own the name: drain frees it. SDL_GetGamepadName returns a borrowed
|
|
478
|
+
// pointer that SDL_CloseGamepad frees, so copy it now (see drain).
|
|
479
|
+
const char *name = SDL_GetGamepadName(pad);
|
|
480
|
+
R2D_Log(R2D_INFO, "Gamepad added: id=%u, name=%s", (unsigned)gid, name);
|
|
481
|
+
event_buf_push((R2D_QueuedEvent){
|
|
482
|
+
.category = R2D_EVT_GAMEPAD, .type = R2D_GAMEPAD_CONNECT,
|
|
483
|
+
.id = gid, .str = name ? SDL_strdup(name) : NULL
|
|
484
|
+
});
|
|
192
485
|
}
|
|
193
486
|
break;
|
|
487
|
+
}
|
|
194
488
|
|
|
195
|
-
case
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
489
|
+
case SDL_EVENT_GAMEPAD_REMOVED: {
|
|
490
|
+
SDL_JoystickID gid = e.gdevice.which;
|
|
491
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID(gid);
|
|
492
|
+
R2D_Log(R2D_INFO, "Gamepad removed: id=%u", (unsigned)gid);
|
|
493
|
+
event_buf_push((R2D_QueuedEvent){
|
|
494
|
+
.category = R2D_EVT_GAMEPAD, .type = R2D_GAMEPAD_DISCONNECT,
|
|
495
|
+
.id = gid
|
|
496
|
+
});
|
|
497
|
+
if (pad) SDL_CloseGamepad(pad);
|
|
203
498
|
break;
|
|
499
|
+
}
|
|
204
500
|
|
|
205
|
-
case
|
|
206
|
-
|
|
207
|
-
|
|
501
|
+
case SDL_EVENT_WINDOW_RESIZED:
|
|
502
|
+
// New logical (user-intended) window size from the OS event, before any
|
|
503
|
+
// pixel_scale multiplier. R2D_ApplyPixelScale derives the render size.
|
|
504
|
+
r2d_window->orig_width = (int)(e.window.data1 / r2d_window->display_content_scale);
|
|
505
|
+
r2d_window->orig_height = (int)(e.window.data2 / r2d_window->display_content_scale);
|
|
506
|
+
|
|
507
|
+
// In expand mode the viewport tracks the window, so its logical base
|
|
508
|
+
// follows the new size; fixed/scaled modes keep their base canvas.
|
|
509
|
+
if (r2d_window->viewport.mode == R2D_VIEWPORT_EXPAND) {
|
|
510
|
+
r2d_window->viewport.base_width = r2d_window->orig_width;
|
|
511
|
+
r2d_window->viewport.base_height = r2d_window->orig_height;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
R2D_ApplyPixelScale(r2d_window);
|
|
515
|
+
R2D_ApplyViewportMode(r2d_window);
|
|
516
|
+
|
|
517
|
+
SDL_SetAtomicInt(&r2d_window->render_pending, 1);
|
|
208
518
|
break;
|
|
209
519
|
|
|
210
|
-
case
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
} else {
|
|
215
|
-
R2D_Log(R2D_INFO, "Controller #%i: %s removed (%i remaining)", e.jdevice.which, SDL_JoystickName(SDL_JoystickFromInstanceID(e.jdevice.which)), SDL_NumJoysticks());
|
|
216
|
-
SDL_JoystickClose(SDL_JoystickFromInstanceID(e.jdevice.which));
|
|
217
|
-
}
|
|
520
|
+
case SDL_EVENT_WINDOW_EXPOSED:
|
|
521
|
+
case SDL_EVENT_WINDOW_SHOWN:
|
|
522
|
+
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
|
|
523
|
+
SDL_SetAtomicInt(&r2d_window->render_pending, 1);
|
|
218
524
|
break;
|
|
219
525
|
|
|
220
|
-
case
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
526
|
+
case SDL_EVENT_WINDOW_MOUSE_ENTER:
|
|
527
|
+
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
|
|
528
|
+
event_buf_push((R2D_QueuedEvent){
|
|
529
|
+
.category = R2D_EVT_MOUSE,
|
|
530
|
+
.type = e.type == SDL_EVENT_WINDOW_MOUSE_ENTER
|
|
531
|
+
? R2D_MOUSE_ENTER : R2D_MOUSE_LEAVE
|
|
532
|
+
});
|
|
229
533
|
break;
|
|
230
534
|
|
|
231
|
-
case
|
|
232
|
-
|
|
535
|
+
case SDL_EVENT_QUIT:
|
|
536
|
+
event_buf_push((R2D_QueuedEvent){ .category = R2D_EVT_CLOSE });
|
|
233
537
|
break;
|
|
234
538
|
}
|
|
235
539
|
}
|
|
236
540
|
|
|
237
|
-
// Detect keys held down
|
|
541
|
+
// Detect keys held down. Each held key carries only its integer scancode —
|
|
542
|
+
// Ruby resolves and caches the name once per distinct key, so the per-frame
|
|
543
|
+
// path allocates no name string.
|
|
238
544
|
int num_keys;
|
|
239
|
-
key_state = SDL_GetKeyboardState(&num_keys);
|
|
240
|
-
|
|
545
|
+
const bool *key_state = SDL_GetKeyboardState(&num_keys);
|
|
241
546
|
for (int i = 0; i < num_keys; i++) {
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
547
|
+
if (key_state[i]) {
|
|
548
|
+
event_buf_push((R2D_QueuedEvent){
|
|
549
|
+
.category = R2D_EVT_KEY, .type = R2D_KEY_HELD,
|
|
550
|
+
.id = i
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Get and store mouse position in logical renderer coordinates
|
|
556
|
+
float ms_x, ms_y;
|
|
557
|
+
Uint32 mouse_buttons = SDL_GetMouseState(&ms_x, &ms_y);
|
|
558
|
+
float logical_x, logical_y;
|
|
559
|
+
SDL_RenderCoordinatesFromWindow(r2d_window->sdl_renderer, ms_x, ms_y, &logical_x, &logical_y);
|
|
560
|
+
r2d_window->mouse.x = (int)logical_x;
|
|
561
|
+
r2d_window->mouse.y = (int)logical_y;
|
|
562
|
+
|
|
563
|
+
// Detect mouse buttons held down
|
|
564
|
+
static const int mouse_held_buttons[] = {
|
|
565
|
+
R2D_MOUSE_LEFT, R2D_MOUSE_MIDDLE, R2D_MOUSE_RIGHT, R2D_MOUSE_X1, R2D_MOUSE_X2
|
|
566
|
+
};
|
|
567
|
+
for (int i = 0; i < 5; i++) {
|
|
568
|
+
int btn = mouse_held_buttons[i];
|
|
569
|
+
if (mouse_buttons & SDL_BUTTON_MASK(btn)) {
|
|
570
|
+
event_buf_push((R2D_QueuedEvent){
|
|
571
|
+
.category = R2D_EVT_MOUSE, .type = R2D_MOUSE_HELD,
|
|
572
|
+
.button = btn, .x = (int)logical_x, .y = (int)logical_y
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Detect gamepad buttons held down. Gated on SDL_HasGamepad(): with no pad
|
|
578
|
+
// connected — the common case — this skips SDL_GetGamepads, whose returned
|
|
579
|
+
// ID array is a per-frame malloc/free that an empty window shouldn't pay.
|
|
580
|
+
if (SDL_HasGamepad()) {
|
|
581
|
+
int num_pads = 0;
|
|
582
|
+
SDL_JoystickID *pads = SDL_GetGamepads(&num_pads);
|
|
583
|
+
if (pads) {
|
|
584
|
+
for (int i = 0; i < num_pads; i++) {
|
|
585
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID(pads[i]);
|
|
586
|
+
if (!pad) continue;
|
|
587
|
+
for (int b = 0; b < R2D_BUTTON_COUNT; b++) {
|
|
588
|
+
if (SDL_GetGamepadButton(pad, b)) {
|
|
589
|
+
event_buf_push((R2D_QueuedEvent){
|
|
590
|
+
.category = R2D_EVT_GAMEPAD, .type = R2D_GAMEPAD_BUTTON_HELD,
|
|
591
|
+
.id = pads[i], .button = b
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
248
595
|
}
|
|
596
|
+
SDL_free(pads);
|
|
249
597
|
}
|
|
250
598
|
}
|
|
251
599
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
600
|
+
// Sync C state to Ruby ivars. Frame count and fps are deferred to begin_frame
|
|
601
|
+
// so they track frames actually presented, not ticks — in :on_demand mode most
|
|
602
|
+
// ticks present nothing.
|
|
603
|
+
r_ivar_set(obj, id_mouse_x, INT2NUM(r2d_window->mouse.x));
|
|
604
|
+
r_ivar_set(obj, id_mouse_y, INT2NUM(r2d_window->mouse.y));
|
|
605
|
+
// width/height are the logical (user-intended) size per USAGE.md — sync
|
|
606
|
+
// orig_width/orig_height, not window->width/height, which carry the physical
|
|
607
|
+
// render size under pixel_scale. viewport_width/height expose the physical size.
|
|
608
|
+
r_ivar_set(obj, id_width, INT2NUM(r2d_window->orig_width));
|
|
609
|
+
r_ivar_set(obj, id_height, INT2NUM(r2d_window->orig_height));
|
|
610
|
+
r_ivar_set(obj, id_viewport_width, INT2NUM(r2d_window->viewport.width));
|
|
611
|
+
r_ivar_set(obj, id_viewport_height, INT2NUM(r2d_window->viewport.height));
|
|
612
|
+
|
|
613
|
+
if (r2d_window->close) r_ivar_set(obj, id_close, R_TRUE);
|
|
614
|
+
|
|
615
|
+
return R_NIL;
|
|
616
|
+
}
|
|
256
617
|
|
|
257
|
-
// Update Window State /////////////////////////////////////////////////////
|
|
258
618
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
619
|
+
/*
|
|
620
|
+
* Ext.drain_events(window) → flat Array or nil
|
|
621
|
+
*
|
|
622
|
+
* Return queued events as a flat array with R2D_EVT_STRIDE elements per event:
|
|
623
|
+
* [category, type, id, button, direction, axis, x, y, delta_x, delta_y, value, str]
|
|
624
|
+
* Returns nil when no events are queued (the common case, every frame).
|
|
625
|
+
*/
|
|
626
|
+
R_VAL ruby2d_ext_window_drain_events(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
627
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
628
|
+
(void)argc; (void)argv;
|
|
629
|
+
|
|
630
|
+
// Most frames have no events — return nil instead of allocating an empty
|
|
631
|
+
// array 60–120 times a second. The Ruby side skips dispatch on nil.
|
|
632
|
+
if (event_count == 0) return R_NIL;
|
|
633
|
+
|
|
634
|
+
R_VAL ary = r_ary_new();
|
|
635
|
+
for (int i = 0; i < event_count; i++) {
|
|
636
|
+
R2D_QueuedEvent *ev = &event_buf[i];
|
|
637
|
+
r_ary_push(ary, INT2NUM(ev->category));
|
|
638
|
+
r_ary_push(ary, INT2NUM(ev->type));
|
|
639
|
+
r_ary_push(ary, INT2NUM(ev->id));
|
|
640
|
+
r_ary_push(ary, INT2NUM(ev->button));
|
|
641
|
+
r_ary_push(ary, INT2NUM(ev->direction));
|
|
642
|
+
r_ary_push(ary, INT2NUM(ev->axis));
|
|
643
|
+
r_ary_push(ary, INT2NUM(ev->x));
|
|
644
|
+
r_ary_push(ary, INT2NUM(ev->y));
|
|
645
|
+
r_ary_push(ary, DBL2NUM(ev->delta_x));
|
|
646
|
+
r_ary_push(ary, DBL2NUM(ev->delta_y));
|
|
647
|
+
r_ary_push(ary, INT2NUM(ev->value));
|
|
648
|
+
r_ary_push(ary, ev->str ? r_str_new(ev->str) : R_NIL);
|
|
649
|
+
// Ownership contract: poll strdups gamepad-connect names into ev->str, drain
|
|
650
|
+
// frees them here. All other events leave ev->str NULL via designated init,
|
|
651
|
+
// so freeing every non-NULL ev->str is correct.
|
|
652
|
+
if (ev->str) {
|
|
653
|
+
SDL_free((void *)ev->str);
|
|
654
|
+
ev->str = NULL;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
event_count = 0;
|
|
658
|
+
return ary;
|
|
659
|
+
}
|
|
265
660
|
|
|
266
|
-
// Call update and render callbacks
|
|
267
|
-
if (window->update) window->update();
|
|
268
|
-
if (window->render) window->render();
|
|
269
661
|
|
|
270
|
-
|
|
662
|
+
/*
|
|
663
|
+
* Ext.scancode_name(code) → String
|
|
664
|
+
*
|
|
665
|
+
* Human-readable name for an SDL scancode (e.g. "Space", "Left Shift"), or ""
|
|
666
|
+
* if the scancode has no name. The per-frame held-key path carries only the
|
|
667
|
+
* integer scancode; Ruby calls this at most once per distinct key and caches
|
|
668
|
+
* the result, so no key-name string is allocated each frame.
|
|
669
|
+
*/
|
|
670
|
+
R_VAL ruby2d_ext_window_scancode_name(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
671
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
672
|
+
if (argc != 1) r_raise("Ruby2D::Ext.scancode_name expects 1 arg (scancode), got %d", (int)argc);
|
|
673
|
+
const char *name = SDL_GetScancodeName((SDL_Scancode)NUM2INT(argv[0]));
|
|
674
|
+
return r_str_new(name ? name : "");
|
|
675
|
+
}
|
|
676
|
+
|
|
271
677
|
|
|
272
|
-
|
|
273
|
-
|
|
678
|
+
/*
|
|
679
|
+
* Ext.begin_frame(window) → true/false
|
|
680
|
+
*
|
|
681
|
+
* Sync background color from Ruby, decide whether to render, and clear.
|
|
682
|
+
* Returns true if Ruby should call render_objects + render_callback.
|
|
683
|
+
*/
|
|
684
|
+
R_VAL ruby2d_ext_window_begin_frame(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
685
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
686
|
+
if (argc != 1) r_raise("Ruby2D::Ext.begin_frame expects 1 arg (window), got %d", (int)argc);
|
|
687
|
+
R_VAL obj = argv[0];
|
|
688
|
+
|
|
689
|
+
// Sync background color from Ruby to C
|
|
690
|
+
R_VAL bc = r_ivar_get(obj, id_background);
|
|
691
|
+
r2d_window->background.r = NUM2DBL(r_ivar_get(bc, id_r));
|
|
692
|
+
r2d_window->background.g = NUM2DBL(r_ivar_get(bc, id_g));
|
|
693
|
+
r2d_window->background.b = NUM2DBL(r_ivar_get(bc, id_b));
|
|
694
|
+
r2d_window->background.a = NUM2DBL(r_ivar_get(bc, id_a));
|
|
695
|
+
|
|
696
|
+
int was_pending = SDL_SetAtomicInt(&r2d_window->render_pending, 0);
|
|
697
|
+
current_frame_rendered =
|
|
698
|
+
r2d_window->render_mode == R2D_RENDER_CONTINUOUS ||
|
|
699
|
+
was_pending != 0 ||
|
|
700
|
+
r2d_window->screenshot_path != NULL;
|
|
701
|
+
|
|
702
|
+
if (current_frame_rendered) {
|
|
703
|
+
// Count and time only frames actually drawn. In :on_demand mode the tick
|
|
704
|
+
// loop still runs every cycle for input and pacing but presents only when a
|
|
705
|
+
// frame is pending, so frames/fps must advance per present, not per tick.
|
|
706
|
+
// (In :continuous mode every tick renders, so this is unchanged.)
|
|
707
|
+
frames++;
|
|
708
|
+
r2d_window->frames = frames;
|
|
709
|
+
r2d_window->fps = R2D_GetFrameRate();
|
|
710
|
+
r_ivar_set(obj, id_frames, ULL2NUM(r2d_window->frames));
|
|
711
|
+
r_ivar_set(obj, id_fps, DBL2NUM(r2d_window->fps));
|
|
712
|
+
|
|
713
|
+
if (r2d_window->viewport.mode == R2D_VIEWPORT_FIXED) {
|
|
714
|
+
SDL_SetRenderViewport(r2d_window->sdl_renderer, NULL);
|
|
715
|
+
SDL_SetRenderDrawColorFloat(r2d_window->sdl_renderer, 0, 0, 0, 1);
|
|
716
|
+
SDL_RenderClear(r2d_window->sdl_renderer);
|
|
717
|
+
SDL_SetRenderViewport(
|
|
718
|
+
r2d_window->sdl_renderer, &r2d_window->viewport.fixed_rect
|
|
719
|
+
);
|
|
720
|
+
SDL_SetRenderDrawColorFloat(
|
|
721
|
+
r2d_window->sdl_renderer,
|
|
722
|
+
r2d_window->background.r,
|
|
723
|
+
r2d_window->background.g,
|
|
724
|
+
r2d_window->background.b,
|
|
725
|
+
r2d_window->background.a
|
|
726
|
+
);
|
|
727
|
+
SDL_RenderFillRect(r2d_window->sdl_renderer, NULL);
|
|
728
|
+
} else {
|
|
729
|
+
SDL_SetRenderDrawColorFloat(
|
|
730
|
+
r2d_window->sdl_renderer,
|
|
731
|
+
r2d_window->background.r,
|
|
732
|
+
r2d_window->background.g,
|
|
733
|
+
r2d_window->background.b,
|
|
734
|
+
r2d_window->background.a
|
|
735
|
+
);
|
|
736
|
+
SDL_RenderClear(r2d_window->sdl_renderer);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
274
739
|
|
|
275
|
-
|
|
276
|
-
SDL_GL_SwapWindow(window->sdl);
|
|
740
|
+
return current_frame_rendered ? R_TRUE : R_FALSE;
|
|
277
741
|
}
|
|
278
742
|
|
|
279
743
|
|
|
744
|
+
/*
|
|
745
|
+
* Ext.end_frame(window)
|
|
746
|
+
*
|
|
747
|
+
* FPS overlay, deferred screenshot, present (if rendered), and frame cap.
|
|
748
|
+
*/
|
|
749
|
+
R_VAL ruby2d_ext_window_end_frame(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
750
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
751
|
+
(void)argc; (void)argv;
|
|
280
752
|
|
|
753
|
+
if (current_frame_rendered) {
|
|
754
|
+
if (r2d_window->diagnostics || r2d_window->show_fps) {
|
|
755
|
+
R2D_DrawFrameRate(r2d_window);
|
|
756
|
+
}
|
|
281
757
|
|
|
758
|
+
if (r2d_window->screenshot_path) {
|
|
759
|
+
R2D_Screenshot(r2d_window, r2d_window->screenshot_path);
|
|
760
|
+
free(r2d_window->screenshot_path);
|
|
761
|
+
r2d_window->screenshot_path = NULL;
|
|
762
|
+
}
|
|
282
763
|
|
|
764
|
+
if (r2d_window->sdl_renderer) {
|
|
765
|
+
SDL_RenderPresent(r2d_window->sdl_renderer);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
283
768
|
|
|
769
|
+
#ifndef __EMSCRIPTEN__
|
|
770
|
+
if (target_frame > 0.0) {
|
|
771
|
+
Uint64 frame_end = SDL_GetPerformanceCounter();
|
|
772
|
+
double elapsed = (double)(frame_end - frame_start) / perf_freq;
|
|
773
|
+
double remaining = target_frame - elapsed;
|
|
284
774
|
|
|
775
|
+
if (remaining > 0.0) {
|
|
776
|
+
SDL_DelayPrecise((Uint64)(remaining * 1e9));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
#endif
|
|
285
780
|
|
|
781
|
+
return R_NIL;
|
|
782
|
+
}
|
|
286
783
|
|
|
287
784
|
|
|
785
|
+
// Origin the Ext.now clock is measured from, so it reads zero when a program
|
|
786
|
+
// starts rather than when SDL did. Natively the two amount to the same thing —
|
|
787
|
+
// the engine comes up on first use and lives as long as the process does — so
|
|
788
|
+
// the origin stays 0 and nothing rebases it. The browser is the exception: SDL
|
|
789
|
+
// is initialized once per page load and never quits, while try_run_code starts
|
|
790
|
+
// a new program on every Run. Rebasing there keeps `elapsed` meaning "since
|
|
791
|
+
// this program started" on both, so motion derived from it begins at the
|
|
792
|
+
// beginning rather than picking up wherever the page's uptime had reached.
|
|
793
|
+
static Uint64 r2d_time_origin_ns = 0;
|
|
288
794
|
|
|
289
795
|
|
|
290
796
|
/*
|
|
291
|
-
*
|
|
797
|
+
* Ext.now
|
|
798
|
+
*
|
|
799
|
+
* Monotonic, high-resolution time in seconds since the program started. This is
|
|
800
|
+
* the clock the per-frame delta time is measured from. On Emscripten
|
|
801
|
+
* SDL_GetTicksNS maps to performance.now() — the same monotonic high-resolution
|
|
802
|
+
* source the requestAnimationFrame timestamp is drawn from — which is what makes
|
|
803
|
+
* web motion smooth. Time.now (gettimeofday → Date.now on the web) is only ~1 ms
|
|
804
|
+
* and is wall-clock (non-monotonic), so it quantizes and can tick backward,
|
|
805
|
+
* jittering frame pacing. This is the dt clock on mruby (native and web); CRuby
|
|
806
|
+
* uses Process.clock_gettime(CLOCK_MONOTONIC), which is already monotonic.
|
|
292
807
|
*/
|
|
293
|
-
|
|
808
|
+
R_VAL ruby2d_ext_now(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
809
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
810
|
+
(void)argc; (void)argv;
|
|
811
|
+
return DBL2NUM((double)(SDL_GetTicksNS() - r2d_time_origin_ns) / 1.0e9);
|
|
812
|
+
}
|
|
294
813
|
|
|
295
|
-
window = win;
|
|
296
814
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
815
|
+
// =============================================================================
|
|
816
|
+
// Window Show and Initialization
|
|
817
|
+
// =============================================================================
|
|
818
|
+
|
|
819
|
+
#ifdef __EMSCRIPTEN__
|
|
820
|
+
// Web frame pacing. The browser owns the loop via requestAnimationFrame, which
|
|
821
|
+
// always fires at the display refresh rate, so a numeric fps_cap is honored by
|
|
822
|
+
// decimation: EM_TIMING_RAF with interval N runs the tick on every Nth vsync
|
|
823
|
+
// (Emscripten's runner skips the frames in between), giving refresh/N — only
|
|
824
|
+
// divisors of the refresh rate are achievable (120Hz → 60, 40, 30…). N derives
|
|
825
|
+
// from a measured refresh estimate because SDL's Emscripten backend never
|
|
826
|
+
// reports the display's refresh rate. `:infinity` switches to free-running
|
|
827
|
+
// setTimeout(0), the browser's only faster-than-vsync loop, for benchmarking.
|
|
828
|
+
typedef enum {
|
|
829
|
+
WASM_PACE_REFRESH, // no user cap: tick every animation frame (interval 1)
|
|
830
|
+
WASM_PACE_CAPPED, // numeric cap: decimated rAF (interval N)
|
|
831
|
+
WASM_PACE_FREE // :infinity: free-running setTimeout(0)
|
|
832
|
+
} WasmPaceMode;
|
|
833
|
+
|
|
834
|
+
static WasmPaceMode wasm_pace_mode = WASM_PACE_REFRESH;
|
|
835
|
+
static double wasm_pace_cap = 0.0; // target fps when WASM_PACE_CAPPED
|
|
836
|
+
static double wasm_last_tick_time = 0.0; // seconds; 0 = no sample yet
|
|
837
|
+
static double wasm_refresh_est = 0.0; // smoothed rAF rate (Hz); 0 = unknown
|
|
838
|
+
static int wasm_interval = 1; // current decimation interval N
|
|
839
|
+
|
|
840
|
+
// Map the Ruby fps_cap value onto a web pace mode: nil → display refresh,
|
|
841
|
+
// Float::INFINITY → free-running, a number → capped. Called from ext_show and
|
|
842
|
+
// ext_set_fps_cap; the trampoline applies the mode after every tick.
|
|
843
|
+
static void wasm_set_pace_mode(R_VAL fps_cap_val) {
|
|
844
|
+
if (!r_test(fps_cap_val)) {
|
|
845
|
+
wasm_pace_mode = WASM_PACE_REFRESH;
|
|
846
|
+
} else if (isinf(NUM2DBL(fps_cap_val))) {
|
|
847
|
+
wasm_pace_mode = WASM_PACE_FREE;
|
|
848
|
+
} else {
|
|
849
|
+
wasm_pace_mode = WASM_PACE_CAPPED;
|
|
850
|
+
wasm_pace_cap = NUM2DBL(fps_cap_val);
|
|
300
851
|
}
|
|
852
|
+
}
|
|
301
853
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
);
|
|
854
|
+
static void wasm_tick_trampoline(void) {
|
|
855
|
+
// Release the tick's return value from the GC arena — this call runs outside
|
|
856
|
+
// the VM once per frame, and mrb_funcall arena-protects what it returns, so
|
|
857
|
+
// a heap-object return would otherwise accumulate one pinned object per
|
|
858
|
+
// frame, forever.
|
|
859
|
+
int ai = r_gc_arena_save();
|
|
860
|
+
r_funcall(ruby2d_window, "tick", 0);
|
|
861
|
+
r_gc_arena_restore(ai);
|
|
862
|
+
|
|
863
|
+
// Re-assert the loop timing after every tick, not once: SDL's Emscripten
|
|
864
|
+
// backend defers the renderer's creation-time vsync-0 swap interval
|
|
865
|
+
// (`pending_swap_interval`) and applies it during the first SDL_PumpEvents —
|
|
866
|
+
// mid-tick, after any one-shot pin — silently flipping the loop to
|
|
867
|
+
// free-running setTimeout(0). Pinning after the tick always gets the last
|
|
868
|
+
// word. (It can't run at setup time anyway: emscripten_set_main_loop_timing
|
|
869
|
+
// only takes effect once a main loop is registered.)
|
|
870
|
+
|
|
871
|
+
if (wasm_pace_mode == WASM_PACE_FREE) {
|
|
872
|
+
wasm_last_tick_time = 0.0; // rAF rate can't be sampled from setTimeout ticks
|
|
873
|
+
emscripten_set_main_loop_timing(EM_TIMING_SETTIMEOUT, 0);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Estimate the raw rAF (display refresh) rate from tick spacing: ticks arrive
|
|
878
|
+
// every `wasm_interval` animation frames, so raw = interval / dt. Samples
|
|
879
|
+
// outside plausible display rates (20–400 Hz) are dropped — the first tick,
|
|
880
|
+
// hidden-tab gaps, and mode switches all land there.
|
|
881
|
+
double now = (double)SDL_GetTicksNS() / 1.0e9;
|
|
882
|
+
if (wasm_last_tick_time > 0.0) {
|
|
883
|
+
double dt = now - wasm_last_tick_time;
|
|
884
|
+
double raw = dt > 0.0 ? (double)wasm_interval / dt : 0.0;
|
|
885
|
+
if (raw >= 20.0 && raw <= 400.0) {
|
|
886
|
+
wasm_refresh_est = wasm_refresh_est > 0.0
|
|
887
|
+
? wasm_refresh_est * 0.9 + raw * 0.1
|
|
888
|
+
: raw;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
wasm_last_tick_time = now;
|
|
892
|
+
|
|
893
|
+
int interval = 1;
|
|
894
|
+
if (wasm_pace_mode == WASM_PACE_CAPPED &&
|
|
895
|
+
wasm_refresh_est > 0.0 && wasm_pace_cap > 0.0) {
|
|
896
|
+
// Keep the current interval while the ideal ratio stays within ±0.55 of
|
|
897
|
+
// it — hysteresis so estimator noise at a rounding boundary can't
|
|
898
|
+
// oscillate N (visibly flipping the frame rate) between adjacent values.
|
|
899
|
+
double ratio = wasm_refresh_est / wasm_pace_cap;
|
|
900
|
+
interval = wasm_interval;
|
|
901
|
+
if (fabs(ratio - (double)wasm_interval) > 0.55) {
|
|
902
|
+
interval = (int)SDL_round(ratio);
|
|
903
|
+
if (interval < 1) interval = 1;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
wasm_interval = interval;
|
|
907
|
+
emscripten_set_main_loop_timing(EM_TIMING_RAF, interval);
|
|
908
|
+
}
|
|
909
|
+
#endif
|
|
309
910
|
|
|
310
|
-
if (!window->sdl) R2D_Error("SDL_CreateWindow", SDL_GetError());
|
|
311
|
-
if (window->icon) R2D_SetIcon(window, window->icon);
|
|
312
911
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
912
|
+
/*
|
|
913
|
+
* Ruby2D::Window#ext_show
|
|
914
|
+
*/
|
|
915
|
+
R_VAL ruby2d_ext_window_show(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
916
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
917
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_show expects 1 arg (window), got %d", (int)argc);
|
|
918
|
+
R_VAL obj = argv[0];
|
|
317
919
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
);
|
|
323
|
-
window->width = actual_width;
|
|
324
|
-
window->height = actual_height;
|
|
325
|
-
window->orig_width = actual_width;
|
|
326
|
-
window->orig_height = actual_height;
|
|
920
|
+
ruby2d_window = obj;
|
|
921
|
+
|
|
922
|
+
if (!r2d_window) {
|
|
923
|
+
r_raise("Ruby2D: window cannot be shown (native window is NULL)");
|
|
327
924
|
}
|
|
328
925
|
|
|
329
|
-
//
|
|
926
|
+
// Re-read window flags from Ruby (user may have changed @resizable,
|
|
927
|
+
// @highdpi via set() after ext_create but before show)
|
|
928
|
+
int flags = 0;
|
|
929
|
+
if (r_test(r_ivar_get(obj, id_resizable))) flags |= R2D_RESIZABLE;
|
|
930
|
+
if (r_test(r_ivar_get(obj, id_highdpi))) flags |= R2D_HIGHDPI;
|
|
931
|
+
r2d_window->flags = flags;
|
|
932
|
+
|
|
933
|
+
// Scale window dimensions by content_scale for correct visual size on all
|
|
934
|
+
// platforms. On macOS content_scale is 1.0 (coordinates are already logical
|
|
935
|
+
// points); on Windows with DPI scaling it compensates for physical pixels.
|
|
936
|
+
float cs = r2d_window->display_content_scale;
|
|
937
|
+
int win_w = (int)(obj_int(obj, id_width) * cs);
|
|
938
|
+
int win_h = (int)(obj_int(obj, id_height) * cs);
|
|
939
|
+
|
|
940
|
+
// Create the window hidden on native platforms so it can be centered before
|
|
941
|
+
// its first paint; repositioning an already-visible window makes it visibly
|
|
942
|
+
// jump. On the web the "window" is the page canvas — there is nothing to
|
|
943
|
+
// position — so it is created shown, as before.
|
|
944
|
+
int create_flags = r2d_window->flags;
|
|
945
|
+
#ifndef __EMSCRIPTEN__
|
|
946
|
+
create_flags |= SDL_WINDOW_HIDDEN;
|
|
947
|
+
#endif
|
|
330
948
|
|
|
331
|
-
|
|
949
|
+
// Create SDL window and renderer
|
|
950
|
+
if (!SDL_CreateWindowAndRenderer(
|
|
951
|
+
obj_str(obj, id_title), win_w, win_h,
|
|
952
|
+
create_flags,
|
|
953
|
+
&r2d_window->sdl_window, &r2d_window->sdl_renderer
|
|
954
|
+
)) {
|
|
955
|
+
r_raise("Ruby2D: failed to create window and renderer: %s", SDL_GetError());
|
|
956
|
+
}
|
|
332
957
|
|
|
333
|
-
//
|
|
958
|
+
// Defensive check: ensure both window and renderer are non-NULL
|
|
959
|
+
if (!r2d_window->sdl_window || !r2d_window->sdl_renderer) {
|
|
960
|
+
r_raise("Ruby2D: window or renderer is NULL after creation: %s", SDL_GetError());
|
|
961
|
+
}
|
|
334
962
|
|
|
335
|
-
|
|
336
|
-
|
|
963
|
+
// Enable alpha blending for shape rendering (SDL_RenderGeometry)
|
|
964
|
+
SDL_SetRenderDrawBlendMode(r2d_window->sdl_renderer, SDL_BLENDMODE_BLEND);
|
|
965
|
+
|
|
966
|
+
// Resolve the effective fps_cap. Float::INFINITY means "uncapped" → fps_cap 0,
|
|
967
|
+
// so target_frame stays 0 (no frame delay). Read as a double so an Integer or
|
|
968
|
+
// Float::INFINITY both work. Without a user cap, fall back to the display
|
|
969
|
+
// refresh rate.
|
|
970
|
+
R_VAL fps_cap_val = r_ivar_get(obj, id_fps_cap);
|
|
971
|
+
if (r_test(fps_cap_val)) {
|
|
972
|
+
double cap = NUM2DBL(fps_cap_val);
|
|
973
|
+
r2d_window->fps_cap = isinf(cap) ? 0 : (int)cap;
|
|
974
|
+
} else {
|
|
975
|
+
r2d_window->fps_cap = (int)r2d_window->display_refresh_rate;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// On the web, pacing is applied by the tick trampoline instead of vsync or
|
|
979
|
+
// frame delays — map the cap onto a pace mode for it.
|
|
980
|
+
#ifdef __EMSCRIPTEN__
|
|
981
|
+
wasm_set_pace_mode(fps_cap_val);
|
|
337
982
|
#endif
|
|
338
983
|
|
|
339
|
-
|
|
984
|
+
#ifndef __EMSCRIPTEN__
|
|
985
|
+
// Native frame pacing: enable vsync for smooth display-synced rendering. A user
|
|
986
|
+
// cap disables vsync and paces solely via SDL_DelayPrecise; without a cap, use
|
|
987
|
+
// vsync AND a refresh-rate SDL_DelayPrecise fallback — this prevents uncapped
|
|
988
|
+
// rendering when the window is occluded, where macOS (and other platforms) may
|
|
989
|
+
// not block on vsync.
|
|
990
|
+
//
|
|
991
|
+
// On the web this is deliberately skipped. With no ASYNCIFY the renderer present
|
|
992
|
+
// can't block, so SDL's vsync setter does nothing but rewrite the Emscripten
|
|
993
|
+
// main-loop timing (vsync 0 → free-running EM_TIMING_SETTIMEOUT, vsync 1 → an
|
|
994
|
+
// EM_TIMING_RAF cadence that measured at half the refresh rate). We own that
|
|
995
|
+
// timing directly instead — see emscripten_set_main_loop_timing below.
|
|
996
|
+
SDL_SetRenderVSync(r2d_window->sdl_renderer, r_test(fps_cap_val) ? 0 : 1);
|
|
997
|
+
#endif
|
|
998
|
+
|
|
999
|
+
// Re-read the logical (user-intended) viewport dimensions from Ruby (the user
|
|
1000
|
+
// may have changed @width/@height or @viewport_* via set() after ext_create
|
|
1001
|
+
// but before show). These are the unscaled base sizes; R2D_ApplyPixelScale
|
|
1002
|
+
// derives the render dimensions from them.
|
|
1003
|
+
R_VAL vw_val = r_ivar_get(obj, id_viewport_width);
|
|
1004
|
+
R_VAL vh_val = r_ivar_get(obj, id_viewport_height);
|
|
1005
|
+
r2d_window->viewport.base_width = r_test(vw_val) ? NUM2INT(vw_val) : obj_int(obj, id_width);
|
|
1006
|
+
r2d_window->viewport.base_height = r_test(vh_val) ? NUM2INT(vh_val) : obj_int(obj, id_height);
|
|
1007
|
+
|
|
1008
|
+
// Re-read viewport mode from Ruby
|
|
1009
|
+
r2d_window->viewport.mode = R2D_ParseViewportMode(r_ivar_get(obj, id_viewport_mode));
|
|
1010
|
+
|
|
1011
|
+
// Re-read render mode from Ruby (user may have changed @render_mode via set()
|
|
1012
|
+
// between new and show)
|
|
1013
|
+
r2d_window->render_mode = R2D_ParseRenderMode(r_ivar_get(obj, id_render_mode));
|
|
1014
|
+
|
|
1015
|
+
// Logical (user-intended) window dimensions on the C struct
|
|
1016
|
+
r2d_window->orig_width = obj_int(obj, id_width);
|
|
1017
|
+
r2d_window->orig_height = obj_int(obj, id_height);
|
|
1018
|
+
|
|
1019
|
+
// Derive render (physical) dimensions from the logical sizes, scaling once
|
|
1020
|
+
// for pixel_scale on HiDPI so coordinates map 1:1 with physical pixels.
|
|
1021
|
+
r2d_window->pixel_scale = obj_bool(obj, id_pixel_scale);
|
|
1022
|
+
R2D_ApplyPixelScale(r2d_window);
|
|
1023
|
+
|
|
1024
|
+
// Apply viewport scaling mode (logical presentation)
|
|
1025
|
+
R2D_ApplyViewportMode(r2d_window);
|
|
1026
|
+
|
|
1027
|
+
// Read icon path from Ruby and apply it
|
|
1028
|
+
R_VAL icon_val = r_ivar_get(obj, id_icon);
|
|
1029
|
+
if (r_test(icon_val)) {
|
|
1030
|
+
R2D_SetIcon(r2d_window, RSTRING_PTR(icon_val));
|
|
1031
|
+
}
|
|
340
1032
|
|
|
1033
|
+
// Reset first_poll so the initial gamepad scan runs on the first tick
|
|
1034
|
+
first_poll = true;
|
|
1035
|
+
|
|
1036
|
+
// Set Main Loop Data ////////////////////////////////////////////////////////
|
|
341
1037
|
|
|
342
1038
|
frames = 0; // Total frames since start
|
|
343
|
-
frames_last_sec = 0; // Frames in the last second
|
|
344
|
-
start_ms = SDL_GetTicks(); // Elapsed time since start
|
|
345
|
-
next_second_ms = SDL_GetTicks(); // The last time plus a second
|
|
346
|
-
begin_ms = start_ms; // Time at beginning of loop
|
|
347
|
-
decay_rate = 0.5; // Determines how fast an average decays over time
|
|
348
|
-
fps = window->fps_cap; // Moving average of actual FPS, initial value a guess
|
|
349
|
-
|
|
350
|
-
// Enable VSync
|
|
351
|
-
if (window->vsync) {
|
|
352
|
-
if (!SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1")) {
|
|
353
|
-
R2D_Log(R2D_WARN, "VSync cannot be enabled");
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
1039
|
|
|
357
|
-
|
|
1040
|
+
r2d_window->close = false;
|
|
1041
|
+
|
|
1042
|
+
// Frame cap setup: always set a target_frame based on fps_cap. Without an
|
|
1043
|
+
// explicit user cap, this equals the display refresh rate (set above), acting
|
|
1044
|
+
// as a fallback when vsync is not blocking (e.g., window occluded).
|
|
1045
|
+
perf_freq = SDL_GetPerformanceFrequency();
|
|
1046
|
+
target_frame = r2d_window->fps_cap > 0
|
|
1047
|
+
? 1.0 / (double)r2d_window->fps_cap
|
|
1048
|
+
: 0.0;
|
|
1049
|
+
|
|
1050
|
+
// Center the window on the primary display and reveal it. It was created
|
|
1051
|
+
// hidden above, so this positions it before its first paint — no visible
|
|
1052
|
+
// jump. Explicit centering is honored on all native backends, unlike the
|
|
1053
|
+
// default undefined position (which lands top-left under many Linux WMs).
|
|
1054
|
+
// Skipped on the web, where the canvas has no OS-level position.
|
|
1055
|
+
#ifndef __EMSCRIPTEN__
|
|
1056
|
+
SDL_SetWindowPosition(r2d_window->sdl_window,
|
|
1057
|
+
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
1058
|
+
SDL_ShowWindow(r2d_window->sdl_window);
|
|
1059
|
+
#endif
|
|
358
1060
|
|
|
359
1061
|
// Main Loop /////////////////////////////////////////////////////////////////
|
|
1062
|
+
// CRuby: return and let Ruby own the loop (Window#show calls tick in a loop).
|
|
1063
|
+
// mruby native: C drives the loop, calling Ruby's tick each frame.
|
|
1064
|
+
// WASM: emscripten_set_main_loop calls tick via a C trampoline.
|
|
360
1065
|
|
|
361
1066
|
#ifdef __EMSCRIPTEN__
|
|
362
|
-
emscripten_set_main_loop(
|
|
363
|
-
#
|
|
364
|
-
|
|
1067
|
+
emscripten_set_main_loop(wasm_tick_trampoline, 0, true);
|
|
1068
|
+
#elif defined(MRUBY)
|
|
1069
|
+
// Arena save/restore for the same reason as wasm_tick_trampoline: each
|
|
1070
|
+
// C-driven tick would otherwise pin its return value in the GC arena.
|
|
1071
|
+
while (!r2d_window->close) {
|
|
1072
|
+
int ai = r_gc_arena_save();
|
|
1073
|
+
r_funcall(ruby2d_window, "tick", 0);
|
|
1074
|
+
r_gc_arena_restore(ai);
|
|
1075
|
+
}
|
|
365
1076
|
#endif
|
|
366
1077
|
|
|
367
|
-
return
|
|
1078
|
+
return R_TRUE;
|
|
368
1079
|
}
|
|
369
1080
|
|
|
370
1081
|
|
|
1082
|
+
// =============================================================================
|
|
1083
|
+
// Window Operations
|
|
1084
|
+
// =============================================================================
|
|
371
1085
|
|
|
372
1086
|
/*
|
|
373
1087
|
* Set the icon for the window
|
|
374
1088
|
*/
|
|
375
1089
|
void R2D_SetIcon(R2D_Window *window, const char *icon) {
|
|
376
|
-
SDL_Surface *iconSurface =
|
|
1090
|
+
SDL_Surface *iconSurface = IMG_Load(icon);
|
|
377
1091
|
if (iconSurface) {
|
|
378
1092
|
window->icon = icon;
|
|
379
|
-
SDL_SetWindowIcon(window->
|
|
380
|
-
|
|
1093
|
+
SDL_SetWindowIcon(window->sdl_window, iconSurface);
|
|
1094
|
+
SDL_DestroySurface(iconSurface);
|
|
381
1095
|
} else {
|
|
382
|
-
R2D_Log(R2D_WARN, "Could not set window icon");
|
|
1096
|
+
R2D_Log(R2D_WARN, "Could not set window icon: %s", SDL_GetError());
|
|
383
1097
|
}
|
|
384
1098
|
}
|
|
385
1099
|
|
|
@@ -388,48 +1102,106 @@ void R2D_SetIcon(R2D_Window *window, const char *icon) {
|
|
|
388
1102
|
* Take a screenshot of the window
|
|
389
1103
|
*/
|
|
390
1104
|
void R2D_Screenshot(R2D_Window *window, const char *path) {
|
|
1105
|
+
SDL_Surface *surface = SDL_RenderReadPixels(window->sdl_renderer, NULL);
|
|
1106
|
+
if (!surface) {
|
|
1107
|
+
R2D_Error("R2D_Screenshot", "%s", SDL_GetError());
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
391
1110
|
|
|
392
|
-
|
|
393
|
-
R2D_Error("R2D_Screenshot", "
|
|
394
|
-
|
|
395
|
-
// Create a surface the size of the window
|
|
396
|
-
SDL_Surface *surface = SDL_CreateRGBSurface(
|
|
397
|
-
SDL_SWSURFACE, window->width, window->height, 24,
|
|
398
|
-
0x000000FF, 0x0000FF00, 0x00FF0000, 0
|
|
399
|
-
);
|
|
1111
|
+
if (!IMG_SavePNG(surface, path)) {
|
|
1112
|
+
R2D_Error("R2D_Screenshot", "%s", SDL_GetError());
|
|
1113
|
+
}
|
|
400
1114
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
glReadPixels(0, 0, window->width, window->height, GL_RGB, GL_UNSIGNED_BYTE, surface->pixels);
|
|
1115
|
+
SDL_DestroySurface(surface);
|
|
1116
|
+
}
|
|
404
1117
|
|
|
405
|
-
// Flip image vertically
|
|
406
1118
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
1119
|
+
/*
|
|
1120
|
+
* Show the cursor over the window
|
|
1121
|
+
*/
|
|
1122
|
+
void R2D_ShowCursor() {
|
|
1123
|
+
SDL_ShowCursor();
|
|
1124
|
+
}
|
|
413
1125
|
|
|
414
|
-
int height_div_2 = (int) (surface->h * 0.5);
|
|
415
1126
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
1127
|
+
/*
|
|
1128
|
+
* Hide the cursor over the window
|
|
1129
|
+
*/
|
|
1130
|
+
void R2D_HideCursor() {
|
|
1131
|
+
SDL_HideCursor();
|
|
1132
|
+
}
|
|
421
1133
|
|
|
422
|
-
free(temp_row);
|
|
423
1134
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
1135
|
+
/*
|
|
1136
|
+
* Check if the cursor is visible
|
|
1137
|
+
*/
|
|
1138
|
+
bool R2D_CursorVisible() {
|
|
1139
|
+
return SDL_CursorVisible();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
/*
|
|
1144
|
+
* Set the system cursor by name
|
|
1145
|
+
*/
|
|
1146
|
+
bool R2D_SetSystemCursor(const char *name) {
|
|
1147
|
+
SDL_SystemCursor id;
|
|
1148
|
+
|
|
1149
|
+
if (strcmp(name, "default") == 0) id = SDL_SYSTEM_CURSOR_DEFAULT;
|
|
1150
|
+
else if (strcmp(name, "text") == 0) id = SDL_SYSTEM_CURSOR_TEXT;
|
|
1151
|
+
else if (strcmp(name, "wait") == 0) id = SDL_SYSTEM_CURSOR_WAIT;
|
|
1152
|
+
else if (strcmp(name, "crosshair") == 0) id = SDL_SYSTEM_CURSOR_CROSSHAIR;
|
|
1153
|
+
else if (strcmp(name, "progress") == 0) id = SDL_SYSTEM_CURSOR_PROGRESS;
|
|
1154
|
+
else if (strcmp(name, "nwse_resize") == 0) id = SDL_SYSTEM_CURSOR_NWSE_RESIZE;
|
|
1155
|
+
else if (strcmp(name, "nesw_resize") == 0) id = SDL_SYSTEM_CURSOR_NESW_RESIZE;
|
|
1156
|
+
else if (strcmp(name, "ew_resize") == 0) id = SDL_SYSTEM_CURSOR_EW_RESIZE;
|
|
1157
|
+
else if (strcmp(name, "ns_resize") == 0) id = SDL_SYSTEM_CURSOR_NS_RESIZE;
|
|
1158
|
+
else if (strcmp(name, "move") == 0) id = SDL_SYSTEM_CURSOR_MOVE;
|
|
1159
|
+
else if (strcmp(name, "not_allowed") == 0) id = SDL_SYSTEM_CURSOR_NOT_ALLOWED;
|
|
1160
|
+
else if (strcmp(name, "pointer") == 0) id = SDL_SYSTEM_CURSOR_POINTER;
|
|
1161
|
+
else if (strcmp(name, "nw_resize") == 0) id = SDL_SYSTEM_CURSOR_NW_RESIZE;
|
|
1162
|
+
else if (strcmp(name, "n_resize") == 0) id = SDL_SYSTEM_CURSOR_N_RESIZE;
|
|
1163
|
+
else if (strcmp(name, "ne_resize") == 0) id = SDL_SYSTEM_CURSOR_NE_RESIZE;
|
|
1164
|
+
else if (strcmp(name, "e_resize") == 0) id = SDL_SYSTEM_CURSOR_E_RESIZE;
|
|
1165
|
+
else if (strcmp(name, "se_resize") == 0) id = SDL_SYSTEM_CURSOR_SE_RESIZE;
|
|
1166
|
+
else if (strcmp(name, "s_resize") == 0) id = SDL_SYSTEM_CURSOR_S_RESIZE;
|
|
1167
|
+
else if (strcmp(name, "sw_resize") == 0) id = SDL_SYSTEM_CURSOR_SW_RESIZE;
|
|
1168
|
+
else if (strcmp(name, "w_resize") == 0) id = SDL_SYSTEM_CURSOR_W_RESIZE;
|
|
1169
|
+
else {
|
|
1170
|
+
R2D_Error("R2D_SetSystemCursor", "Unknown cursor name: %s", name);
|
|
1171
|
+
return false;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Cache one cursor per system-cursor id so repeated sets don't leak a fresh
|
|
1175
|
+
// SDL_Cursor each call (SDL_SetCursor doesn't take ownership). The cache lives
|
|
1176
|
+
// for the process — bounded by SDL_SYSTEM_CURSOR_COUNT, matching the
|
|
1177
|
+
// single-window/no-teardown design.
|
|
1178
|
+
static SDL_Cursor *cursor_cache[SDL_SYSTEM_CURSOR_COUNT] = { NULL };
|
|
1179
|
+
|
|
1180
|
+
SDL_Cursor *cursor = cursor_cache[id];
|
|
1181
|
+
if (!cursor) {
|
|
1182
|
+
cursor = SDL_CreateSystemCursor(id);
|
|
1183
|
+
if (!cursor) {
|
|
1184
|
+
R2D_Error("R2D_SetSystemCursor", "Failed to create cursor: %s", SDL_GetError());
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
cursor_cache[id] = cursor;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
SDL_SetCursor(cursor);
|
|
1191
|
+
SDL_ShowCursor();
|
|
1192
|
+
return true;
|
|
428
1193
|
}
|
|
429
1194
|
|
|
430
1195
|
|
|
431
1196
|
/*
|
|
432
1197
|
* Close the window
|
|
1198
|
+
*
|
|
1199
|
+
* This only flags the loop to stop; it does NOT destroy the SDL window or
|
|
1200
|
+
* renderer. Those are torn down in R2D_FreeWindow, which runs from the GC
|
|
1201
|
+
* finalizer. The deferral is deliberate: the window object is pinned by
|
|
1202
|
+
* DSL.window for the life of the process, so a closed window can be re-shown,
|
|
1203
|
+
* and destroying SDL resources here would double-free when the finalizer later
|
|
1204
|
+
* runs over the same pointers.
|
|
433
1205
|
*/
|
|
434
1206
|
int R2D_Close(R2D_Window *window) {
|
|
435
1207
|
if (!window->close) {
|
|
@@ -440,13 +1212,651 @@ int R2D_Close(R2D_Window *window) {
|
|
|
440
1212
|
}
|
|
441
1213
|
|
|
442
1214
|
|
|
1215
|
+
/*
|
|
1216
|
+
* Whether the renderer is still alive. Texture finalizers consult this: at
|
|
1217
|
+
* interpreter shutdown Ruby may free the Window — destroying the renderer and,
|
|
1218
|
+
* with it, every texture created from it — before an Image/Text/Canvas/
|
|
1219
|
+
* BitmapText finalizer runs, so calling SDL_DestroyTexture afterward would
|
|
1220
|
+
* double-free. R2D_FreeWindow clears r2d_window, so a NULL window means gone.
|
|
1221
|
+
*/
|
|
1222
|
+
bool R2D_RendererAlive(void) {
|
|
1223
|
+
R2D_Window *w = R2D_GetWindow();
|
|
1224
|
+
return w && w->sdl_renderer;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
|
|
443
1228
|
/*
|
|
444
1229
|
* Free all resources
|
|
445
1230
|
*/
|
|
446
1231
|
int R2D_FreeWindow(R2D_Window *window) {
|
|
447
1232
|
R2D_Close(window);
|
|
448
|
-
|
|
449
|
-
|
|
1233
|
+
R2D_FreeFpsOverlay();
|
|
1234
|
+
if (window->sdl_renderer) {
|
|
1235
|
+
SDL_DestroyRenderer(window->sdl_renderer);
|
|
1236
|
+
window->sdl_renderer = NULL;
|
|
1237
|
+
}
|
|
1238
|
+
if (window->sdl_window) {
|
|
1239
|
+
SDL_DestroyWindow(window->sdl_window);
|
|
1240
|
+
window->sdl_window = NULL;
|
|
1241
|
+
}
|
|
1242
|
+
free(window->screenshot_path);
|
|
1243
|
+
if (r2d_window == window) r2d_window = NULL;
|
|
450
1244
|
free(window);
|
|
451
1245
|
return 0;
|
|
452
1246
|
}
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
/*
|
|
1250
|
+
* Free callback for Ruby's typed data (matches R2D_DEFINE_DATA_TYPE)
|
|
1251
|
+
*/
|
|
1252
|
+
static void R2D_Window_free(void *p) {
|
|
1253
|
+
if (!p) return;
|
|
1254
|
+
R2D_FreeWindow((R2D_Window *)p);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
// =============================================================================
|
|
1259
|
+
// Ruby Method Bindings
|
|
1260
|
+
// =============================================================================
|
|
1261
|
+
|
|
1262
|
+
/*
|
|
1263
|
+
* Ruby2D::Window#ext_get_display_dimensions
|
|
1264
|
+
*/
|
|
1265
|
+
R_VAL ruby2d_ext_window_get_display_dimensions(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1266
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1267
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_get_display_dimensions expects 1 arg (window), got %d", (int)argc);
|
|
1268
|
+
R_VAL obj = argv[0];
|
|
1269
|
+
int w; int h;
|
|
1270
|
+
R2D_GetDisplayDimensions(&w, &h);
|
|
1271
|
+
r_iv_set(obj, "@display_width", INT2NUM(w));
|
|
1272
|
+
r_iv_set(obj, "@display_height", INT2NUM(h));
|
|
1273
|
+
r_iv_set(obj, "@display_pixel_width", INT2NUM(r2d_window->display_pixel_width));
|
|
1274
|
+
r_iv_set(obj, "@display_pixel_height", INT2NUM(r2d_window->display_pixel_height));
|
|
1275
|
+
return R_NIL;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
/*
|
|
1280
|
+
* Ruby2D::Window#ext_set_viewport_mode
|
|
1281
|
+
*
|
|
1282
|
+
* Reads @viewport_mode, @viewport_width, @viewport_height from Ruby and
|
|
1283
|
+
* applies the new viewport mode at runtime.
|
|
1284
|
+
*/
|
|
1285
|
+
R_VAL ruby2d_ext_window_set_viewport_mode(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1286
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1287
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_viewport_mode expects 1 arg (window), got %d", (int)argc);
|
|
1288
|
+
R_VAL obj = argv[0];
|
|
1289
|
+
if (!r2d_window) return R_FALSE;
|
|
1290
|
+
|
|
1291
|
+
// Detect an explicit viewport-dimension change from Ruby. @viewport_* is
|
|
1292
|
+
// synced to the current (possibly pixel-scaled) render viewport each frame, so
|
|
1293
|
+
// a value that differs means the user set a new size via set() — adopt it as
|
|
1294
|
+
// the logical base. A matching value (the common case) leaves the base alone,
|
|
1295
|
+
// which is what keeps repeated set() calls from compounding.
|
|
1296
|
+
R_VAL vw_val = r_ivar_get(obj, id_viewport_width);
|
|
1297
|
+
R_VAL vh_val = r_ivar_get(obj, id_viewport_height);
|
|
1298
|
+
int ruby_vw = r_test(vw_val) ? NUM2INT(vw_val) : r2d_window->width;
|
|
1299
|
+
int ruby_vh = r_test(vh_val) ? NUM2INT(vh_val) : r2d_window->height;
|
|
1300
|
+
|
|
1301
|
+
if (ruby_vw != r2d_window->viewport.width ||
|
|
1302
|
+
ruby_vh != r2d_window->viewport.height) {
|
|
1303
|
+
r2d_window->viewport.base_width = ruby_vw;
|
|
1304
|
+
r2d_window->viewport.base_height = ruby_vh;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Parse the (possibly new) viewport mode
|
|
1308
|
+
r2d_window->viewport.mode = R2D_ParseViewportMode(r_ivar_get(obj, id_viewport_mode));
|
|
1309
|
+
|
|
1310
|
+
// Re-derive the render dimensions from the logical base. Idempotent, so
|
|
1311
|
+
// toggling pixel_scale or changing viewport mode at runtime no longer doubles
|
|
1312
|
+
// the window/viewport size on each call.
|
|
1313
|
+
r2d_window->pixel_scale = obj_bool(obj, id_pixel_scale);
|
|
1314
|
+
R2D_ApplyPixelScale(r2d_window);
|
|
1315
|
+
|
|
1316
|
+
R2D_ApplyViewportMode(r2d_window);
|
|
1317
|
+
|
|
1318
|
+
return R_TRUE;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
/*
|
|
1323
|
+
* Ruby2D::Window#ext_set_icon
|
|
1324
|
+
*/
|
|
1325
|
+
R_VAL ruby2d_ext_window_set_icon(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1326
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1327
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_icon expects 1 arg (window), got %d", (int)argc);
|
|
1328
|
+
R_VAL obj = argv[0];
|
|
1329
|
+
if (!r2d_window || !r2d_window->sdl_window) return R_FALSE;
|
|
1330
|
+
|
|
1331
|
+
R_VAL icon_val = r_ivar_get(obj, id_icon);
|
|
1332
|
+
if (r_test(icon_val)) {
|
|
1333
|
+
R2D_SetIcon(r2d_window, RSTRING_PTR(icon_val));
|
|
1334
|
+
}
|
|
1335
|
+
return R_TRUE;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
|
|
1339
|
+
/*
|
|
1340
|
+
* Ruby2D::Window#ext_set_title — push @title to the live window's title bar
|
|
1341
|
+
*/
|
|
1342
|
+
R_VAL ruby2d_ext_window_set_title(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1343
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1344
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_title expects 1 arg (window), got %d", (int)argc);
|
|
1345
|
+
R_VAL obj = argv[0];
|
|
1346
|
+
if (!r2d_window || !r2d_window->sdl_window) return R_FALSE;
|
|
1347
|
+
|
|
1348
|
+
SDL_SetWindowTitle(r2d_window->sdl_window, obj_str(obj, id_title));
|
|
1349
|
+
return R_TRUE;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
|
|
1353
|
+
/*
|
|
1354
|
+
* Ruby2D::Window#ext_set_resizable — toggle the live window's resizability
|
|
1355
|
+
*/
|
|
1356
|
+
R_VAL ruby2d_ext_window_set_resizable(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1357
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1358
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_resizable expects 1 arg (window), got %d", (int)argc);
|
|
1359
|
+
R_VAL obj = argv[0];
|
|
1360
|
+
if (!r2d_window || !r2d_window->sdl_window) return R_FALSE;
|
|
1361
|
+
|
|
1362
|
+
bool resizable = r_test(r_ivar_get(obj, id_resizable));
|
|
1363
|
+
SDL_SetWindowResizable(r2d_window->sdl_window, resizable);
|
|
1364
|
+
if (resizable) r2d_window->flags |= R2D_RESIZABLE;
|
|
1365
|
+
else r2d_window->flags &= ~R2D_RESIZABLE;
|
|
1366
|
+
return R_TRUE;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
/*
|
|
1371
|
+
* Ruby2D::Window#ext_set_size — resize the live window to the logical @width/
|
|
1372
|
+
* @height, then re-derive the render/viewport dimensions (mirrors the resize-
|
|
1373
|
+
* event path). The window manager may honor a different size; the resulting
|
|
1374
|
+
* SDL_EVENT_WINDOW_RESIZED reconciles orig_width/orig_height if so.
|
|
1375
|
+
*/
|
|
1376
|
+
R_VAL ruby2d_ext_window_set_size(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1377
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1378
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_size expects 1 arg (window), got %d", (int)argc);
|
|
1379
|
+
R_VAL obj = argv[0];
|
|
1380
|
+
if (!r2d_window || !r2d_window->sdl_window) return R_FALSE;
|
|
1381
|
+
|
|
1382
|
+
r2d_window->orig_width = obj_int(obj, id_width);
|
|
1383
|
+
r2d_window->orig_height = obj_int(obj, id_height);
|
|
1384
|
+
|
|
1385
|
+
// In expand mode the viewport tracks the window size; fixed/scaled modes keep
|
|
1386
|
+
// their own logical canvas (viewport.base_*).
|
|
1387
|
+
if (r2d_window->viewport.mode == R2D_VIEWPORT_EXPAND) {
|
|
1388
|
+
r2d_window->viewport.base_width = r2d_window->orig_width;
|
|
1389
|
+
r2d_window->viewport.base_height = r2d_window->orig_height;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// SDL_CreateWindowAndRenderer was given logical * content_scale at show time;
|
|
1393
|
+
// match that so the requested logical size maps to the same physical pixels.
|
|
1394
|
+
float cs = r2d_window->display_content_scale;
|
|
1395
|
+
if (!SDL_SetWindowSize(r2d_window->sdl_window,
|
|
1396
|
+
(int)(r2d_window->orig_width * cs),
|
|
1397
|
+
(int)(r2d_window->orig_height * cs))) {
|
|
1398
|
+
R2D_Error("SDL_SetWindowSize", "%s", SDL_GetError());
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
R2D_ApplyPixelScale(r2d_window);
|
|
1402
|
+
R2D_ApplyViewportMode(r2d_window);
|
|
1403
|
+
return R_TRUE;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
/*
|
|
1408
|
+
* Ruby2D::Window#ext_diagnostics
|
|
1409
|
+
*/
|
|
1410
|
+
R_VAL ruby2d_ext_window_diagnostics(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1411
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1412
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_diagnostics expects 2 args, got %d", (int)argc);
|
|
1413
|
+
R_VAL enable = argv[1];
|
|
1414
|
+
// Set Ruby 2D native diagnostics
|
|
1415
|
+
R2D_Diagnostics(r_test(enable));
|
|
1416
|
+
return R_TRUE;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
|
|
1420
|
+
/*
|
|
1421
|
+
* Ruby2D::Window#ext_set_fps_cap
|
|
1422
|
+
*/
|
|
1423
|
+
R_VAL ruby2d_ext_window_set_fps_cap(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1424
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1425
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_set_fps_cap expects 2 args, got %d", (int)argc);
|
|
1426
|
+
R_VAL fps_cap = argv[1];
|
|
1427
|
+
if (!r2d_window || !r2d_window->sdl_renderer) return R_FALSE;
|
|
1428
|
+
|
|
1429
|
+
if (!r_test(fps_cap)) {
|
|
1430
|
+
// nil → no cap: restore vsync-driven pacing with the refresh-rate fallback.
|
|
1431
|
+
r2d_window->fps_cap = (int)r2d_window->display_refresh_rate;
|
|
1432
|
+
#ifndef __EMSCRIPTEN__
|
|
1433
|
+
SDL_SetRenderVSync(r2d_window->sdl_renderer, 1);
|
|
1434
|
+
#endif
|
|
1435
|
+
} else {
|
|
1436
|
+
// A finite cap paces via SDL_DelayPrecise; Float::INFINITY → uncapped.
|
|
1437
|
+
// (The Ruby side has already rejected 0/negative/NaN.)
|
|
1438
|
+
double cap = NUM2DBL(fps_cap);
|
|
1439
|
+
r2d_window->fps_cap = isinf(cap) ? 0 : (int)cap;
|
|
1440
|
+
#ifndef __EMSCRIPTEN__
|
|
1441
|
+
SDL_SetRenderVSync(r2d_window->sdl_renderer, 0);
|
|
1442
|
+
#endif
|
|
1443
|
+
}
|
|
1444
|
+
// On the web, vsync isn't touched: the requestAnimationFrame main loop owns the
|
|
1445
|
+
// cadence, and SDL's vsync setter would only rewrite that timing (and SDL_Delay-
|
|
1446
|
+
// Precise pacing is compiled out under Emscripten). Pacing is applied by the
|
|
1447
|
+
// tick trampoline instead — see wasm_set_pace_mode / wasm_tick_trampoline.
|
|
1448
|
+
#ifdef __EMSCRIPTEN__
|
|
1449
|
+
wasm_set_pace_mode(fps_cap);
|
|
1450
|
+
#endif
|
|
1451
|
+
target_frame = r2d_window->fps_cap > 0 ? 1.0 / (double)r2d_window->fps_cap : 0.0;
|
|
1452
|
+
return R_TRUE;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
/*
|
|
1457
|
+
* Ruby2D::Window#ext_show_fps
|
|
1458
|
+
*/
|
|
1459
|
+
R_VAL ruby2d_ext_window_show_fps(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1460
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1461
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_show_fps expects 2 args, got %d", (int)argc);
|
|
1462
|
+
R_VAL enable = argv[1];
|
|
1463
|
+
R2D_ShowFPS(r_test(enable));
|
|
1464
|
+
return R_TRUE;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
/*
|
|
1469
|
+
* Ruby2D::Window#ext_load_gamepad_mappings_file
|
|
1470
|
+
*/
|
|
1471
|
+
R_VAL ruby2d_ext_window_load_gamepad_mappings_file(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1472
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1473
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_load_gamepad_mappings_file expects 2 args, got %d", (int)argc);
|
|
1474
|
+
R_VAL path = argv[1];
|
|
1475
|
+
R2D_Log(R2D_INFO, "Loading gamepad mappings from `%s`", RSTRING_PTR(path));
|
|
1476
|
+
int n = R2D_AddGamepadMappingsFromFile(RSTRING_PTR(path));
|
|
1477
|
+
return INT2NUM(n);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
|
|
1481
|
+
/*
|
|
1482
|
+
* Ruby2D::Window#ext_add_gamepad_mapping
|
|
1483
|
+
*/
|
|
1484
|
+
R_VAL ruby2d_ext_window_add_gamepad_mapping(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1485
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1486
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_add_gamepad_mapping expects 2 args, got %d", (int)argc);
|
|
1487
|
+
R_VAL mapping = argv[1];
|
|
1488
|
+
int n = R2D_AddGamepadMapping(RSTRING_PTR(mapping));
|
|
1489
|
+
return INT2NUM(n);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
/*
|
|
1494
|
+
* Ruby2D::Window#ext_gamepad_rumble
|
|
1495
|
+
*/
|
|
1496
|
+
R_VAL ruby2d_ext_window_gamepad_rumble(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1497
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1498
|
+
if (argc != 5) r_raise("Ruby2D::Ext.window_gamepad_rumble expects 5 args, got %d", (int)argc);
|
|
1499
|
+
R_VAL id = argv[1];
|
|
1500
|
+
R_VAL low = argv[2];
|
|
1501
|
+
R_VAL high = argv[3];
|
|
1502
|
+
R_VAL duration = argv[4];
|
|
1503
|
+
bool ok = R2D_RumbleGamepad(
|
|
1504
|
+
(SDL_JoystickID)NUM2INT(id),
|
|
1505
|
+
NUM2DBL(low), NUM2DBL(high), NUM2DBL(duration)
|
|
1506
|
+
);
|
|
1507
|
+
return ok ? R_TRUE : R_FALSE;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
/*
|
|
1512
|
+
* Ruby2D::Window#ext_gamepad_rumble_triggers
|
|
1513
|
+
*/
|
|
1514
|
+
R_VAL ruby2d_ext_window_gamepad_rumble_triggers(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1515
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1516
|
+
if (argc != 5) r_raise("Ruby2D::Ext.window_gamepad_rumble_triggers expects 5 args, got %d", (int)argc);
|
|
1517
|
+
R_VAL id = argv[1];
|
|
1518
|
+
R_VAL left = argv[2];
|
|
1519
|
+
R_VAL right = argv[3];
|
|
1520
|
+
R_VAL duration = argv[4];
|
|
1521
|
+
bool ok = R2D_RumbleGamepadTriggers(
|
|
1522
|
+
(SDL_JoystickID)NUM2INT(id),
|
|
1523
|
+
NUM2DBL(left), NUM2DBL(right), NUM2DBL(duration)
|
|
1524
|
+
);
|
|
1525
|
+
return ok ? R_TRUE : R_FALSE;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
|
|
1529
|
+
/*
|
|
1530
|
+
* Ruby2D::Window#ext_gamepad_set_led
|
|
1531
|
+
*/
|
|
1532
|
+
R_VAL ruby2d_ext_window_gamepad_set_led(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1533
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1534
|
+
if (argc != 5) r_raise("Ruby2D::Ext.window_gamepad_set_led expects 5 args, got %d", (int)argc);
|
|
1535
|
+
R_VAL id = argv[1];
|
|
1536
|
+
R_VAL r = argv[2];
|
|
1537
|
+
R_VAL g = argv[3];
|
|
1538
|
+
R_VAL b = argv[4];
|
|
1539
|
+
bool ok = R2D_SetGamepadLED(
|
|
1540
|
+
(SDL_JoystickID)NUM2INT(id),
|
|
1541
|
+
NUM2INT(r), NUM2INT(g), NUM2INT(b)
|
|
1542
|
+
);
|
|
1543
|
+
return ok ? R_TRUE : R_FALSE;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
/*
|
|
1548
|
+
* Map an SDL_GamepadType to one of the high-level Ruby symbols Gamepad
|
|
1549
|
+
* exposes (`:xbox`, `:playstation`, `:nintendo`, `:generic`, `:unknown`).
|
|
1550
|
+
* Used both for the cached `pad.type` and the `real_type` debug field.
|
|
1551
|
+
*/
|
|
1552
|
+
static R_VAL ruby2d_gamepad_type_to_sym(SDL_GamepadType t) {
|
|
1553
|
+
switch (t) {
|
|
1554
|
+
case SDL_GAMEPAD_TYPE_XBOX360:
|
|
1555
|
+
case SDL_GAMEPAD_TYPE_XBOXONE:
|
|
1556
|
+
return r_char_to_sym("xbox");
|
|
1557
|
+
case SDL_GAMEPAD_TYPE_PS3:
|
|
1558
|
+
case SDL_GAMEPAD_TYPE_PS4:
|
|
1559
|
+
case SDL_GAMEPAD_TYPE_PS5:
|
|
1560
|
+
return r_char_to_sym("playstation");
|
|
1561
|
+
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
|
|
1562
|
+
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT:
|
|
1563
|
+
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT:
|
|
1564
|
+
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR:
|
|
1565
|
+
case SDL_GAMEPAD_TYPE_GAMECUBE:
|
|
1566
|
+
return r_char_to_sym("nintendo");
|
|
1567
|
+
case SDL_GAMEPAD_TYPE_STANDARD:
|
|
1568
|
+
return r_char_to_sym("generic");
|
|
1569
|
+
default:
|
|
1570
|
+
return r_char_to_sym("unknown");
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
/*
|
|
1576
|
+
* Ruby2D::Window#ext_gamepad_type
|
|
1577
|
+
*/
|
|
1578
|
+
R_VAL ruby2d_ext_window_gamepad_type(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1579
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1580
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_gamepad_type expects 2 args, got %d", (int)argc);
|
|
1581
|
+
R_VAL id = argv[1];
|
|
1582
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID((SDL_JoystickID)NUM2INT(id));
|
|
1583
|
+
if (!pad) return r_char_to_sym("unknown");
|
|
1584
|
+
return ruby2d_gamepad_type_to_sym(SDL_GetGamepadType(pad));
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
/*
|
|
1589
|
+
* Ruby2D::Window#ext_gamepad_battery
|
|
1590
|
+
*
|
|
1591
|
+
* Returns a 2-element array: [state_symbol, percent]. State is one of
|
|
1592
|
+
* :wired / :on_battery / :unknown; percent is an Integer 0..100 or -1
|
|
1593
|
+
* if SDL did not report one.
|
|
1594
|
+
*/
|
|
1595
|
+
R_VAL ruby2d_ext_window_gamepad_battery(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1596
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1597
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_gamepad_battery expects 2 args, got %d", (int)argc);
|
|
1598
|
+
R_VAL id = argv[1];
|
|
1599
|
+
int percent = -1;
|
|
1600
|
+
int state = R2D_GetGamepadBattery((SDL_JoystickID)NUM2INT(id), &percent);
|
|
1601
|
+
R_VAL state_sym;
|
|
1602
|
+
switch (state) {
|
|
1603
|
+
case R2D_GAMEPAD_BATTERY_WIRED: state_sym = r_char_to_sym("wired"); break;
|
|
1604
|
+
case R2D_GAMEPAD_BATTERY_ON_BATTERY: state_sym = r_char_to_sym("on_battery"); break;
|
|
1605
|
+
default: state_sym = r_char_to_sym("unknown"); break;
|
|
1606
|
+
}
|
|
1607
|
+
R_VAL ary = r_ary_new();
|
|
1608
|
+
r_ary_push(ary, state_sym);
|
|
1609
|
+
r_ary_push(ary, INT2NUM(percent));
|
|
1610
|
+
return ary;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
/*
|
|
1615
|
+
* Ruby2D::Window#ext_gamepad_has_button
|
|
1616
|
+
*/
|
|
1617
|
+
R_VAL ruby2d_ext_window_gamepad_has_button(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1618
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1619
|
+
if (argc != 3) r_raise("Ruby2D::Ext.window_gamepad_has_button expects 3 args, got %d", (int)argc);
|
|
1620
|
+
R_VAL id = argv[1];
|
|
1621
|
+
R_VAL button = argv[2];
|
|
1622
|
+
bool ok = R2D_GamepadHasButton(
|
|
1623
|
+
(SDL_JoystickID)NUM2INT(id), NUM2INT(button)
|
|
1624
|
+
);
|
|
1625
|
+
return ok ? R_TRUE : R_FALSE;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
/*
|
|
1630
|
+
* Ruby2D::Window#ext_gamepad_has_axis
|
|
1631
|
+
*/
|
|
1632
|
+
R_VAL ruby2d_ext_window_gamepad_has_axis(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1633
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1634
|
+
if (argc != 3) r_raise("Ruby2D::Ext.window_gamepad_has_axis expects 3 args, got %d", (int)argc);
|
|
1635
|
+
R_VAL id = argv[1];
|
|
1636
|
+
R_VAL axis = argv[2];
|
|
1637
|
+
bool ok = R2D_GamepadHasAxis(
|
|
1638
|
+
(SDL_JoystickID)NUM2INT(id), NUM2INT(axis)
|
|
1639
|
+
);
|
|
1640
|
+
return ok ? R_TRUE : R_FALSE;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
/*
|
|
1645
|
+
* Ruby2D::Window#ext_gamepad_caps
|
|
1646
|
+
*/
|
|
1647
|
+
R_VAL ruby2d_ext_window_gamepad_caps(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1648
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1649
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_gamepad_caps expects 2 args, got %d", (int)argc);
|
|
1650
|
+
R_VAL id = argv[1];
|
|
1651
|
+
int caps = R2D_GetGamepadCaps((SDL_JoystickID)NUM2INT(id));
|
|
1652
|
+
return INT2NUM(caps);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
/*
|
|
1657
|
+
* Ruby2D::Window#ext_gamepad_debug_info
|
|
1658
|
+
*
|
|
1659
|
+
* Returns nil if the pad isn't open. Otherwise returns a fixed-shape
|
|
1660
|
+
* Array (positional) that `Gamepad#debug_info` reshapes into a Hash:
|
|
1661
|
+
* [guid_str, vendor, product, version, serial_or_nil,
|
|
1662
|
+
* connection_sym, real_type_sym, num_touchpads, mapping_or_nil]
|
|
1663
|
+
*/
|
|
1664
|
+
R_VAL ruby2d_ext_window_gamepad_debug_info(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1665
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1666
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_gamepad_debug_info expects 2 args, got %d", (int)argc);
|
|
1667
|
+
R_VAL id = argv[1];
|
|
1668
|
+
SDL_JoystickID jid = (SDL_JoystickID)NUM2INT(id);
|
|
1669
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID(jid);
|
|
1670
|
+
if (!pad) return R_NIL;
|
|
1671
|
+
|
|
1672
|
+
R_VAL arr = r_ary_new();
|
|
1673
|
+
|
|
1674
|
+
// GUID — 32-char hex string
|
|
1675
|
+
SDL_GUID guid = SDL_GetGamepadGUIDForID(jid);
|
|
1676
|
+
char guid_str[33] = {0};
|
|
1677
|
+
SDL_GUIDToString(guid, guid_str, sizeof(guid_str));
|
|
1678
|
+
r_ary_push(arr, r_str_new(guid_str));
|
|
1679
|
+
|
|
1680
|
+
// Vendor / Product / Version (USB-style 16-bit IDs)
|
|
1681
|
+
r_ary_push(arr, INT2NUM(SDL_GetGamepadVendor(pad)));
|
|
1682
|
+
r_ary_push(arr, INT2NUM(SDL_GetGamepadProduct(pad)));
|
|
1683
|
+
r_ary_push(arr, INT2NUM(SDL_GetGamepadProductVersion(pad)));
|
|
1684
|
+
|
|
1685
|
+
// Serial number — frequently NULL
|
|
1686
|
+
const char *serial = SDL_GetGamepadSerial(pad);
|
|
1687
|
+
r_ary_push(arr, serial ? r_str_new(serial) : R_NIL);
|
|
1688
|
+
|
|
1689
|
+
// Connection state
|
|
1690
|
+
R_VAL conn_sym;
|
|
1691
|
+
switch (SDL_GetGamepadConnectionState(pad)) {
|
|
1692
|
+
case SDL_JOYSTICK_CONNECTION_WIRED: conn_sym = r_char_to_sym("wired"); break;
|
|
1693
|
+
case SDL_JOYSTICK_CONNECTION_WIRELESS: conn_sym = r_char_to_sym("wireless"); break;
|
|
1694
|
+
default: conn_sym = r_char_to_sym("unknown"); break;
|
|
1695
|
+
}
|
|
1696
|
+
r_ary_push(arr, conn_sym);
|
|
1697
|
+
|
|
1698
|
+
// Real (un-remapped) gamepad type — useful for spotting type overrides
|
|
1699
|
+
// applied by SDL_GameControllerDB or user mappings.
|
|
1700
|
+
r_ary_push(arr, ruby2d_gamepad_type_to_sym(SDL_GetRealGamepadType(pad)));
|
|
1701
|
+
|
|
1702
|
+
// Touchpad count (PS4/PS5 = 1; most others = 0)
|
|
1703
|
+
r_ary_push(arr, INT2NUM(SDL_GetNumGamepadTouchpads(pad)));
|
|
1704
|
+
|
|
1705
|
+
// Resolved mapping string. SDL_GetGamepadMapping returns malloc'd memory
|
|
1706
|
+
// that the caller must release via SDL_free.
|
|
1707
|
+
char *mapping = SDL_GetGamepadMapping(pad);
|
|
1708
|
+
if (mapping) {
|
|
1709
|
+
r_ary_push(arr, r_str_new(mapping));
|
|
1710
|
+
SDL_free(mapping);
|
|
1711
|
+
} else {
|
|
1712
|
+
r_ary_push(arr, R_NIL);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
return arr;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
/*
|
|
1720
|
+
* Ruby2D::Window#ext_gamepad_joystick_state
|
|
1721
|
+
*/
|
|
1722
|
+
R_VAL ruby2d_ext_window_gamepad_joystick_state(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1723
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1724
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_gamepad_joystick_state expects 2 args, got %d", (int)argc);
|
|
1725
|
+
R_VAL id = argv[1];
|
|
1726
|
+
SDL_Gamepad *pad = SDL_GetGamepadFromID((SDL_JoystickID)NUM2INT(id));
|
|
1727
|
+
if (!pad) return R_NIL;
|
|
1728
|
+
SDL_Joystick *joy = SDL_GetGamepadJoystick(pad);
|
|
1729
|
+
if (!joy) return R_NIL;
|
|
1730
|
+
|
|
1731
|
+
R_VAL btns = r_ary_new();
|
|
1732
|
+
int n_btns = SDL_GetNumJoystickButtons(joy);
|
|
1733
|
+
for (int i = 0; i < n_btns; i++) {
|
|
1734
|
+
r_ary_push(btns, SDL_GetJoystickButton(joy, i) ? R_TRUE : R_FALSE);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
R_VAL axes = r_ary_new();
|
|
1738
|
+
int n_axes = SDL_GetNumJoystickAxes(joy);
|
|
1739
|
+
for (int i = 0; i < n_axes; i++) {
|
|
1740
|
+
r_ary_push(axes, INT2NUM(SDL_GetJoystickAxis(joy, i)));
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
R_VAL hats = r_ary_new();
|
|
1744
|
+
int n_hats = SDL_GetNumJoystickHats(joy);
|
|
1745
|
+
for (int i = 0; i < n_hats; i++) {
|
|
1746
|
+
r_ary_push(hats, INT2NUM((int)SDL_GetJoystickHat(joy, i)));
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
R_VAL result = r_ary_new();
|
|
1750
|
+
r_ary_push(result, btns);
|
|
1751
|
+
r_ary_push(result, axes);
|
|
1752
|
+
r_ary_push(result, hats);
|
|
1753
|
+
return result;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
/*
|
|
1758
|
+
* Ruby2D::Window#ext_screenshot
|
|
1759
|
+
*
|
|
1760
|
+
* Defers the screenshot to the end of the current frame, after rendering but
|
|
1761
|
+
* before SDL_RenderPresent, so the back buffer contains the fully drawn scene.
|
|
1762
|
+
*/
|
|
1763
|
+
R_VAL ruby2d_ext_window_screenshot(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1764
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1765
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_screenshot expects 2 args, got %d", (int)argc);
|
|
1766
|
+
R_VAL path = argv[1];
|
|
1767
|
+
if (r2d_window) {
|
|
1768
|
+
free(r2d_window->screenshot_path);
|
|
1769
|
+
r2d_window->screenshot_path = strdup(RSTRING_PTR(path));
|
|
1770
|
+
return path;
|
|
1771
|
+
} else {
|
|
1772
|
+
return R_FALSE;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
|
|
1777
|
+
/*
|
|
1778
|
+
* Ruby2D::Window#ext_show_cursor
|
|
1779
|
+
*/
|
|
1780
|
+
R_VAL ruby2d_ext_window_show_cursor(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1781
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1782
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_show_cursor expects 1 arg (window), got %d", (int)argc);
|
|
1783
|
+
R2D_ShowCursor();
|
|
1784
|
+
return R_TRUE;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
/*
|
|
1789
|
+
* Ruby2D::Window#ext_hide_cursor
|
|
1790
|
+
*/
|
|
1791
|
+
R_VAL ruby2d_ext_window_hide_cursor(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1792
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1793
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_hide_cursor expects 1 arg (window), got %d", (int)argc);
|
|
1794
|
+
R2D_HideCursor();
|
|
1795
|
+
return R_TRUE;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
|
|
1799
|
+
/*
|
|
1800
|
+
* Ruby2D::Window#ext_cursor_visible
|
|
1801
|
+
*/
|
|
1802
|
+
R_VAL ruby2d_ext_window_cursor_visible(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1803
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1804
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_cursor_visible expects 1 arg (window), got %d", (int)argc);
|
|
1805
|
+
return R2D_CursorVisible() ? R_TRUE : R_FALSE;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
|
|
1809
|
+
/*
|
|
1810
|
+
* Ruby2D::Window#ext_set_system_cursor
|
|
1811
|
+
*/
|
|
1812
|
+
R_VAL ruby2d_ext_window_set_system_cursor(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1813
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1814
|
+
if (argc != 2) r_raise("Ruby2D::Ext.window_set_system_cursor expects 2 args, got %d", (int)argc);
|
|
1815
|
+
R_VAL name = argv[1];
|
|
1816
|
+
return R2D_SetSystemCursor(RSTRING_PTR(name)) ? R_TRUE : R_FALSE;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
/*
|
|
1821
|
+
* Ruby2D::Window#ext_close
|
|
1822
|
+
*/
|
|
1823
|
+
R_VAL ruby2d_ext_window_close(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1824
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1825
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_close expects 1 arg (window), got %d", (int)argc);
|
|
1826
|
+
R2D_Close(r2d_window);
|
|
1827
|
+
return R_NIL;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
/*
|
|
1832
|
+
* Ruby2D::Window#ext_set_render_mode
|
|
1833
|
+
*
|
|
1834
|
+
* Reads @render_mode from Ruby and updates the native render mode. Marks the
|
|
1835
|
+
* next frame pending so the app always sees at least one frame after switching
|
|
1836
|
+
* modes (particularly important when switching from on_demand → continuous or
|
|
1837
|
+
* changing modes mid-scene).
|
|
1838
|
+
*/
|
|
1839
|
+
R_VAL ruby2d_ext_window_set_render_mode(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1840
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1841
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_set_render_mode expects 1 arg (window), got %d", (int)argc);
|
|
1842
|
+
R_VAL obj = argv[0];
|
|
1843
|
+
if (!r2d_window) return R_FALSE;
|
|
1844
|
+
r2d_window->render_mode = R2D_ParseRenderMode(r_ivar_get(obj, id_render_mode));
|
|
1845
|
+
SDL_SetAtomicInt(&r2d_window->render_pending, 1);
|
|
1846
|
+
return R_TRUE;
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
|
|
1850
|
+
/*
|
|
1851
|
+
* Ruby2D::Window#ext_request_render
|
|
1852
|
+
*
|
|
1853
|
+
* Marks the next tick as needing a rendered frame. Safe to call from any
|
|
1854
|
+
* thread (the dirty flag is an SDL atomic int).
|
|
1855
|
+
*/
|
|
1856
|
+
R_VAL ruby2d_ext_window_request_render(RUBY2D_METHOD_ARGS_VARIADIC) {
|
|
1857
|
+
RUBY2D_EXTRACT_VARIADIC;
|
|
1858
|
+
if (argc != 1) r_raise("Ruby2D::Ext.window_request_render expects 1 arg (window), got %d", (int)argc);
|
|
1859
|
+
if (!r2d_window) return R_FALSE;
|
|
1860
|
+
SDL_SetAtomicInt(&r2d_window->render_pending, 1);
|
|
1861
|
+
return R_TRUE;
|
|
1862
|
+
}
|